aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dev_tools/src/bin/docs-code-box-fix.rs165
-rw-r--r--docs/_zh_CN/pages/1-creating-your-first-program.md54
-rw-r--r--docs/_zh_CN/pages/2-implementing-fallbacks.md40
-rw-r--r--docs/pages/1-creating-your-first-program.md56
-rw-r--r--docs/pages/2-implementing-fallbacks.md46
5 files changed, 263 insertions, 98 deletions
diff --git a/dev_tools/src/bin/docs-code-box-fix.rs b/dev_tools/src/bin/docs-code-box-fix.rs
new file mode 100644
index 0000000..0212259
--- /dev/null
+++ b/dev_tools/src/bin/docs-code-box-fix.rs
@@ -0,0 +1,165 @@
+use std::fs;
+use std::path::Path;
+
+/// Docsify code blocks require that blank lines before and after code blocks are not completely empty,
+/// but must contain at least one space, otherwise code block rendering will have issues.
+///
+/// This tool scans all `.md` files in the docs directory,
+/// and replaces completely empty lines before and after code blocks with blank lines containing a single space.
+
+const DOCS_DIR: &str = "./docs";
+
+fn main() {
+ println!("Fixing code box empty lines in docs/**/*.md ...");
+ let repo_root = find_git_repo().expect("Cannot find git repo root");
+ let docs_dir = repo_root.join(DOCS_DIR);
+
+ let mut fixed_count = 0;
+ let mut file_count = 0;
+
+ collect_md_files(&docs_dir, &mut |path| {
+ if let Some(name) = path.file_name() {
+ let name = name.to_string_lossy();
+ if name.to_lowercase() == "_sidebar.md" {
+ return;
+ }
+ }
+
+ let content = fs::read_to_string(path).unwrap_or_default();
+ if content.is_empty() {
+ return;
+ }
+
+ let new_content = fix_code_box_empty_lines(&content);
+ if new_content != content {
+ fs::write(path, &new_content).unwrap();
+ println!(" Fixed: {}", path.display());
+ fixed_count += 1;
+ }
+ file_count += 1;
+ });
+
+ println!(
+ "Done. Scanned {} files, fixed {} files.",
+ file_count, fixed_count
+ );
+}
+
+fn fix_code_box_empty_lines(content: &str) -> String {
+ let mut result = String::new();
+ let lines: Vec<&str> = content.lines().collect();
+ let len = lines.len();
+
+ let mut i = 0;
+ while i < len {
+ let line = lines[i];
+
+ // detect beginning of code block: beginning with ```
+ if line.trim_start().starts_with("```") {
+ // record the beginning line of the code block
+ result.push_str(line);
+ result.push('\n');
+ i += 1;
+
+ // find the end of the code block
+ let mut found_end = false;
+ let code_start = i; // record starting position of code content
+ let mut code_end = len; // index of code block end line
+
+ while i < len {
+ let cline = lines[i];
+ if cline.trim_start().starts_with("```") && cline.trim() != "" {
+ // this is the closing marker
+ code_end = i;
+ found_end = true;
+ break;
+ }
+ i += 1;
+ }
+
+ // check the blank line before the code block
+ // if result ends with \n\n, add a space to turn it into \n \n
+ ensure_space_before_code_block(&mut result);
+
+ // output code content
+ for j in code_start..code_end {
+ let code_line = lines[j];
+ if code_line.is_empty() {
+ result.push(' ');
+ } else {
+ result.push_str(code_line);
+ }
+ result.push('\n');
+ }
+
+ if found_end {
+ result.push_str(lines[code_end]);
+ result.push('\n');
+ i += 1;
+
+ // check the blank line after the code block
+ // if the next line is blank, change it to one with a space
+ if i < len && lines[i].trim().is_empty() && lines[i].is_empty() {
+ // skip the original blank line, write " \n"
+ result.push(' ');
+ result.push('\n');
+ i += 1;
+ }
+ }
+ } else {
+ result.push_str(line);
+ result.push('\n');
+ i += 1;
+ }
+ }
+
+ // remove trailing newlines
+ while result.ends_with('\n') {
+ result.pop();
+ }
+ result.push('\n');
+
+ result
+}
+
+/// ensure there is a blank line with a space before the code block
+fn ensure_space_before_code_block(result: &mut String) {
+ // if result ends with \n\n,
+ // turn it into \n \n
+ let len = result.len();
+ if len >= 2 && result[len - 2..] == *"\n\n" {
+ // insert a space before the last \n
+ result.insert(len - 1, ' ');
+ }
+}
+
+/// recursively collect all .md files in the docs directory
+fn collect_md_files(dir: &Path, callback: &mut dyn FnMut(&Path)) {
+ if let Ok(entries) = fs::read_dir(dir) {
+ for entry in entries.flatten() {
+ let path = entry.path();
+ if path.is_dir() {
+ collect_md_files(&path, callback);
+ } else if path.extension().is_some_and(|ext| ext == "md") {
+ callback(&path);
+ }
+ }
+ }
+}
+
+fn find_git_repo() -> Option<std::path::PathBuf> {
+ let mut current_dir = std::env::current_dir().ok()?;
+
+ loop {
+ let git_dir = current_dir.join(".git");
+ if git_dir.exists() && git_dir.is_dir() {
+ return Some(current_dir);
+ }
+
+ if !current_dir.pop() {
+ break;
+ }
+ }
+
+ None
+}
diff --git a/docs/_zh_CN/pages/1-creating-your-first-program.md b/docs/_zh_CN/pages/1-creating-your-first-program.md
index 27a7f6d..96932ba 100644
--- a/docs/_zh_CN/pages/1-creating-your-first-program.md
+++ b/docs/_zh_CN/pages/1-creating-your-first-program.md
@@ -26,11 +26,11 @@
```toml
[dependencies]
mingling = "0.1.7"
-
+
# 如果您要尝鲜,可以试试 Github 上托管的版本
mingling = { git = "https://github.com/catilgrass/mingling", branch = "main" }
```
-
+
> [!NOTE]
>
> 该版本基于文档编写时的 **Mingling** 版本,您可以前往 [crates.io](https://crates.io/crates/mingling) 查看最新的版本!😄
@@ -48,12 +48,12 @@ fn main() {
// 创建 ThisProgram,并执行
ThisProgram::new().exec();
}
-
+
// gen_program! 宏将会收集 *它之前* 的所有组件、类型
// 然后生成程序 `ThisProgram`
mingling::macros::gen_program!();
```
-
+
> [!TIP]
>
> `gen_Sprogram!()` 宏展开时,会收集在它之前展开的其他组件、类型的信息,这意味着您需要将 `gen_program!()` 放在整个 crate 中最后被展开的位置
@@ -70,15 +70,15 @@ mingling::macros::gen_program!();
fn main() {
// ...
}
-
+
// 创建分发器,并将 GreetCommand 绑定在 "greet" 子命令
// 在用户指定该命令时,向调度器发送 GreetEntry
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
// ...
gen_program!();
```
-
+
不要被突然多出来的一个宏和两个类型所吓到!我来逐一解释这个宏干了什么:
##### 关于 `dispatcher!` 宏 💡
@@ -112,7 +112,7 @@ fn main() {
program.exec();
}
```
-
+
这样,`ThisProgram` 就认得 `"greet"` 子命令了,但是框架还不知道 `"greet"` 的行为是怎样的。此时我们便需要实现具体的逻辑:
@@ -124,24 +124,24 @@ fn main() {
```rust
// ...
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
// 声明渲染器 `render_greet`,并表示前一个类型是 `GreetEntry`
#[renderer]
fn render_greet(_prev: GreetEntry) {
r_println!("Hello, World!");
}
-
+
// ...
gen_program!(); // 渲染器会被注册到程序
```
-
+
对于 `#[renderer]` 属性宏标记的函数,**Mingling** 严格规定只允许使用一种函数签名:
```rust
#[renderer]
fn renderer_name (_prev: PreviousType) { }
```
-
+
宏会读取到第一个参数的类型,并告诉 `gen_program!` 该函数用来渲染该类型。
##### 关于 `r_println!()` 💡
@@ -158,10 +158,10 @@ fn renderer_name (_prev: PreviousType) { }
```rust
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
// 包装中间类型 `ResultGreetSomeone`
pack!(ResultGreetSomeone = String);
-
+
#[chain]
fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
let args = prev.inner;
@@ -169,18 +169,18 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
-
+
// 包装为中间类型
ResultGreetSomeone::new(name)
}
-
+
#[renderer]
fn render_greet_someone(prev: ResultGreetSomeone) {
// 解引用 prev 拿到原始类型
r_println!("Hello, {}!", *prev);
}
```
-
+
像 `#[renderer]` 一样,我们创建了一个 `#[chain]`,它处理类型 `GreetEntr`,输出 `ResultGreetSomeone`
这样我们就在原本的 `Dispatcher` 和 `Renderer` 中间插入了一个 `Chain`:它可以将用户输入的参数提取出来(或回退到默认值 "World"),再交由渲染器打印到终端。
@@ -204,7 +204,7 @@ fn render_greet_someone(prev: ResultGreetSomeone) {
```rust
pack!(PackedType = RawType);
```
-
+
不过请注意:`pack!` 宏不支持带有生命周期的类型包装,因为类型在调度器之间的流转方式永远都是 `move` 而非 `borrow`。
@@ -215,33 +215,33 @@ pack!(PackedType = RawType);
```rust
use mingling::macros::{chain, dispatcher, gen_program, pack, r_println, renderer};
-
+
fn main() {
let mut program = ThisProgram::new();
program.with_dispatcher(GreetCommand);
program.exec();
}
-
+
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
pack!(ResultGreetSomeone = String);
-
+
#[chain]
fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
let args = prev.inner;
let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
-
+
ResultGreetSomeone::new(name)
}
-
+
#[renderer]
fn render_greet_someone(prev: ResultGreetSomeone) {
r_println!("Hello, {}!", *prev);
}
-
+
gen_program!();
```
-
+
运行结果:
```bash
@@ -250,7 +250,7 @@ Hello, World!
~> your-bin greet Alice
Hello, Alice!
```
-
+
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
</p>
diff --git a/docs/_zh_CN/pages/2-implementing-fallbacks.md b/docs/_zh_CN/pages/2-implementing-fallbacks.md
index 071a5d1..5ed7242 100644
--- a/docs/_zh_CN/pages/2-implementing-fallbacks.md
+++ b/docs/_zh_CN/pages/2-implementing-fallbacks.md
@@ -13,7 +13,7 @@
~> your-bin hello
~> your-bin hello Alice
```
-
+
**它没有任何反应!** 👆
让我来解释为什么:**Mingling** 不自作主张,无论发生什么它都不会输出内容到终端(除了 `unwind` 下的 `panic!`)
@@ -34,20 +34,20 @@
```rust
// 1. 定义 `greet` 命令
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
fn main() {
// ->> 用户输入 "hello Alice"
let mut program = ThisProgram::new();
-
+
// 2. 导入 `greet` 命令
program.with_dispatcher(GreetCommand);
-
+
// 3. 执行程序
program.exec();
}
-
+
// ...
-
+
// 5. 接收 DispatcherNotFound 调度
#[renderer]
fn dispatcher_not_found(prev: DispatcherNotFound) {
@@ -57,7 +57,7 @@ fn dispatcher_not_found(prev: DispatcherNotFound) {
prev.join(" ")
);
}
-
+
// 4. 无法匹配到任何名为 `hello` 的分发器
// 将用户参数原样分发到 DispatcherNotFound
gen_program!();
@@ -68,11 +68,11 @@ gen_program!();
```bash
~> omg hello
Cannot match any command! Current input: "hello"
-
+
~> omg hello Alice
Cannot match any command! Current input: "hello Alice"
```
-
+
现在若用户输入了不匹配的命令,**Mingling** 将会输出对应的内容!
## `RendererNotFound` 类型
@@ -86,30 +86,30 @@ Cannot match any command! Current input: "hello Alice"
```rust
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
fn main() {
let mut program = ThisProgram::new();
-
+
program.with_dispatcher(GreetCommand);
program.exec();
}
-
+
pack!(ResultGreetSomeone = String);
-
+
#[chain]
fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
let args = prev.inner;
let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
-
+
ResultGreetSomeone::new(name)
}
-
+
// 让我们故意去除 `ResultGreetSomeone` 类型的渲染器实现
// #[renderer]
// fn render_greet_someone(prev: ResultGreetSomeone) {
// r_println!("Hello, {}!", *prev);
// }
-
+
#[renderer]
fn renderer_not_found(prev: RendererNotFound) {
if *prev == "DispatcherNotFound" {
@@ -119,16 +119,16 @@ fn renderer_not_found(prev: RendererNotFound) {
// 当未找到渲染器时触发 `panic!`
panic!("Renderer \"{}\" not found!", *prev);
}
-
+
gen_program!();
-
+
```
-
+
上述程序的运行效果为:
```bash
~> your-bin greet Alice
-
+
thread 'main' (90772) panicked at src/bin/your-bin.rs:30:5:
Renderer "ResultGreetSomeone" not found!
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/docs/pages/1-creating-your-first-program.md b/docs/pages/1-creating-your-first-program.md
index f905d8a..75c5081 100644
--- a/docs/pages/1-creating-your-first-program.md
+++ b/docs/pages/1-creating-your-first-program.md
@@ -26,11 +26,11 @@
```toml
[dependencies]
mingling = "0.1.7"
-
+
# If you want the latest, try the version hosted on Github
mingling = { git = "https://github.com/catilgrass/mingling", branch = "main" }
```
-
+
> [!NOTE]
>
> This version matches the **Mingling** version used when writing this doc. Check [crates.io](https://crates.io/crates/mingling) for the latest release! 😄
@@ -48,12 +48,12 @@ fn main() {
// Create ThisProgram and run it
ThisProgram::new().exec();
}
-
+
// The gen_program! macro collects *all preceding* components & types
// then generates the `ThisProgram` struct
mingling::macros::gen_program!();
```
-
+
> [!TIP]
>
> When `gen_program!()` expands, it gathers info from other components & types that were expanded before it. This means you must place `gen_program!()` at the very last expansion point in the crate.
@@ -70,15 +70,15 @@ mingling::macros::gen_program!();
fn main() {
// ...
}
-
+
// Create a dispatcher, binding GreetCommand to the "greet" sub-command
// When the user specifies this command, send GreetEntry to the dispatcher
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
// ...
gen_program!();
```
-
+
Don't be scared by the sudden macro and two new types! Let me explain what this macro does:
##### About the `dispatcher!` macro 💡
@@ -106,13 +106,13 @@ gen_program!();
```rust
fn main() {
let mut program = ThisProgram::new();
-
+
// Register the dispatcher
program.with_dispatcher(GreetCommand);
program.exec();
}
```
-
+
Now `ThisProgram` recognizes the `"greet"` sub-command, but the framework still doesn't know what `"greet"` should do. That's where we implement the actual logic:
@@ -124,24 +124,24 @@ fn main() {
```rust
// ...
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
// Declare a renderer `render_greet`, specifying the previous type as `GreetEntry`
#[renderer]
fn render_greet(_prev: GreetEntry) {
r_println!("Hello, World!");
}
-
+
// ...
gen_program!(); // The renderer will be registered with the program
```
-
+
For functions marked with `#[renderer]`, **Mingling** strictly enforces only one function signature:
```rust
#[renderer]
fn renderer_name (_prev: PreviousType) { }
```
-
+
The macro reads the type of the first param and tells `gen_program!` that this function renders that type.
##### About `r_println!()` 💡
@@ -158,10 +158,10 @@ fn renderer_name (_prev: PreviousType) { }
```rust
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
// Wrap the intermediate type `ResultGreetSomeone`
pack!(ResultGreetSomeone = String);
-
+
#[chain]
fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
let args = prev.inner;
@@ -169,18 +169,18 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
-
+
// Wrap into intermediate type
ResultGreetSomeone::new(name)
}
-
+
#[renderer]
fn render_greet_someone(prev: ResultGreetSomeone) {
// Deref prev to get the raw type
r_println!("Hello, {}!", *prev);
}
```
-
+
Just like `#[renderer]`, we created a `#[chain]` that processes type `GreetEntry` and outputs `ResultGreetSomeone`.
This inserts a `Chain` between the original `Dispatcher` and `Renderer`: it extracts the user's input params (or falls back to "World"), then passes them to the renderer to print to the terminal.
@@ -204,7 +204,7 @@ fn render_greet_someone(prev: ResultGreetSomeone) {
```rust
pack!(PackedType = RawType);
```
-
+
Note: `pack!` doesn't support types with lifetimes, because types are always moved (not borrowed) between dispatchers.
@@ -215,33 +215,33 @@ pack!(PackedType = RawType);
```rust
use mingling::macros::{chain, dispatcher, gen_program, pack, r_println, renderer};
-
+
fn main() {
let mut program = ThisProgram::new();
program.with_dispatcher(GreetCommand);
program.exec();
}
-
+
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
pack!(ResultGreetSomeone = String);
-
+
#[chain]
fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
let args = prev.inner;
let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
-
+
ResultGreetSomeone::new(name)
}
-
+
#[renderer]
fn render_greet_someone(prev: ResultGreetSomeone) {
r_println!("Hello, {}!", *prev);
}
-
+
gen_program!();
```
-
+
Output:
```bash
@@ -250,7 +250,7 @@ Hello, World!
~> your-bin greet Alice
Hello, Alice!
```
-
+
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
</p>
diff --git a/docs/pages/2-implementing-fallbacks.md b/docs/pages/2-implementing-fallbacks.md
index a376580..a820fa1 100644
--- a/docs/pages/2-implementing-fallbacks.md
+++ b/docs/pages/2-implementing-fallbacks.md
@@ -13,7 +13,7 @@
~> your-bin hello
~> your-bin hello Alice
```
-
+
**It does nothing!** 👆
Let me explain why: **Mingling** doesn't presume to act; it will not output anything to the terminal no matter what happens (except for `panic!` under `unwind`)
@@ -34,20 +34,20 @@
```rust
// 1. Define the `greet` command
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
fn main() {
// ->> User enters "hello Alice"
let mut program = ThisProgram::new();
-
+
// 2. Import the `greet` command
program.with_dispatcher(GreetCommand);
-
+
// 3. Execute the program
program.exec();
}
-
+
// ...
-
+
// 5. Receive the DispatcherNotFound dispatch
#[renderer]
fn dispatcher_not_found(prev: DispatcherNotFound) {
@@ -57,22 +57,22 @@ fn dispatcher_not_found(prev: DispatcherNotFound) {
prev.join(" ")
);
}
-
+
// 4. Cannot match any dispatcher named `hello`
// Forward the user's arguments as-is to DispatcherNotFound
gen_program!();
```
-
+
The output of the above program is:
```bash
~> omg hello
Cannot match any command! Current input: "hello"
-
+
~> omg hello Alice
Cannot match any command! Current input: "hello Alice"
```
-
+
Now, if the user enters a command that doesn't match, **Mingling** will output the appropriate message!
## The `RendererNotFound` Type
@@ -86,54 +86,54 @@ Cannot match any command! Current input: "hello Alice"
```rust
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
fn main() {
let mut program = ThisProgram::new();
-
+
program.with_dispatcher(GreetCommand);
program.exec();
}
-
+
pack!(ResultGreetSomeone = String);
-
+
#[chain]
fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
let args = prev.inner;
let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
-
+
ResultGreetSomeone::new(name)
}
-
+
// Let's intentionally remove the renderer implementation for `ResultGreetSomeone`
// #[renderer]
// fn render_greet_someone(prev: ResultGreetSomeone) {
// r_println!("Hello, {}!", *prev);
// }
-
+
#[renderer]
fn renderer_not_found(prev: RendererNotFound) {
if *prev == "DispatcherNotFound" {
return; // Exclude the "DispatcherNotFound" type
}
-
+
// Trigger `panic!` when a renderer is not found
panic!("Renderer \"{}\" not found!", *prev);
}
-
+
gen_program!();
-
+
```
-
+
The output of the above program is:
```bash
~> your-bin greet Alice
-
+
thread 'main' (90772) panicked at src/bin/your-bin.rs:30:5:
Renderer "ResultGreetSomeone" not found!
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
-
+
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
</p>