aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cargo/config.toml3
-rw-r--r--dev_tools/Cargo.lock25
-rw-r--r--dev_tools/Cargo.toml1
-rw-r--r--dev_tools/src/bin/ci.rs95
-rw-r--r--dev_tools/src/bin/docs-code-box-fix.rs13
-rw-r--r--dev_tools/src/bin/docsify-sidebar-gen.rs8
-rw-r--r--dev_tools/src/bin/refresh-docs.rs30
-rw-r--r--dev_tools/src/lib.rs102
-rw-r--r--docs/_zh_CN/pages/2-implementing-fallbacks.md2
-rw-r--r--docs/_zh_CN/pages/3-parsing-complex-arguments.md40
-rw-r--r--docs/pages/1-creating-your-first-program.md2
-rw-r--r--docs/pages/3-parsing-complex-arguments.md90
-rw-r--r--docs/res/ci_banner.txt12
-rw-r--r--mingling/src/example_docs.rs222
-rw-r--r--mingling/src/lib.rs2
15 files changed, 455 insertions, 192 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml
index e3cd902..42e41f8 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -2,3 +2,6 @@
target-dir = "./.temp/target"
[env]
+
+[alias]
+ci = "run --manifest-path dev_tools/Cargo.toml --bin ci --quiet"
diff --git a/dev_tools/Cargo.lock b/dev_tools/Cargo.lock
index 39a1521..7bcc602 100644
--- a/dev_tools/Cargo.lock
+++ b/dev_tools/Cargo.lock
@@ -3,6 +3,15 @@
version = 4
[[package]]
+name = "colored"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
name = "just_fmt"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -21,6 +30,22 @@ dependencies = [
name = "tools"
version = "0.1.0"
dependencies = [
+ "colored",
"just_fmt",
"just_template",
]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
diff --git a/dev_tools/Cargo.toml b/dev_tools/Cargo.toml
index a28b156..7abb157 100644
--- a/dev_tools/Cargo.toml
+++ b/dev_tools/Cargo.toml
@@ -6,3 +6,4 @@ edition = "2024"
[dependencies]
just_template = "0.1.3"
just_fmt = "0.1.2"
+colored = "3.1.1"
diff --git a/dev_tools/src/bin/ci.rs b/dev_tools/src/bin/ci.rs
new file mode 100644
index 0000000..d5a108e
--- /dev/null
+++ b/dev_tools/src/bin/ci.rs
@@ -0,0 +1,95 @@
+use std::process::exit;
+
+use tools::{cargo_tomls, eprintln_cargo_style, println_cargo_style, run_cmd};
+
+fn main() {
+ #[cfg(windows)]
+ let _ = colored::control::set_virtual_terminal(true);
+ println!("{}", include_str!("../../../docs/res/ci_banner.txt"));
+
+ let needs_commit_temp = !{ run_cmd!("git diff-index --quiet HEAD --").is_ok() };
+
+ if needs_commit_temp {
+ run_cmd!("git add .").unwrap();
+ run_cmd!("git commit -m \"CI Temp\"").unwrap();
+ }
+
+ if ci().is_ok() {
+ println_cargo_style!("Done: All check passed!")
+ }
+
+ let is_worktree_clean = run_cmd!("git diff-index --quiet HEAD --").is_ok();
+ if !is_worktree_clean {
+ eprintln_cargo_style!("Documents needs refresh!");
+ if needs_commit_temp {
+ run_cmd!("git restore .").unwrap();
+ run_cmd!("git reset --soft HEAD~1").unwrap();
+ }
+ exit(1)
+ }
+
+ if needs_commit_temp {
+ run_cmd!("git restore .").unwrap();
+ run_cmd!("git reset --soft HEAD~1").unwrap();
+ }
+}
+
+fn ci() -> Result<(), i32> {
+ build_all()?;
+ clippy_all()?;
+ test_all()?;
+ docs_refresh()?;
+
+ run_cmd!("git add --renormalize .")?;
+
+ Ok(())
+}
+
+fn build_all() -> Result<(), i32> {
+ let cargo_tomls = cargo_tomls();
+ for cargo_toml in cargo_tomls {
+ println_cargo_style!("Build: {}", cargo_toml.to_string_lossy());
+ run_cmd!(
+ "cargo check --manifest-path {}",
+ cargo_toml.to_string_lossy()
+ )?;
+ }
+
+ Ok(())
+}
+
+fn clippy_all() -> Result<(), i32> {
+ let cargo_tomls = cargo_tomls();
+ for cargo_toml in cargo_tomls {
+ println_cargo_style!("Clippy: {}", cargo_toml.to_string_lossy());
+ run_cmd!(
+ "cargo clippy --manifest-path {} -- -D warnings",
+ cargo_toml.to_string_lossy()
+ )?;
+ }
+
+ Ok(())
+}
+
+fn test_all() -> Result<(), i32> {
+ let cargo_tomls = cargo_tomls();
+ for cargo_toml in cargo_tomls {
+ println_cargo_style!("Testing: {}", cargo_toml.to_string_lossy());
+ run_cmd!(
+ "cargo test --manifest-path {}",
+ cargo_toml.to_string_lossy()
+ )?;
+ }
+
+ Ok(())
+}
+
+fn docs_refresh() -> Result<(), i32> {
+ println_cargo_style!("Refresh: document at `./docs/`");
+
+ run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin docs-code-box-fix")?;
+ run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin docsify-sidebar-gen")?;
+ run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin refresh-docs")?;
+
+ Ok(())
+}
diff --git a/dev_tools/src/bin/docs-code-box-fix.rs b/dev_tools/src/bin/docs-code-box-fix.rs
index db97592..21d2cce 100644
--- a/dev_tools/src/bin/docs-code-box-fix.rs
+++ b/dev_tools/src/bin/docs-code-box-fix.rs
@@ -1,6 +1,8 @@
use std::fs;
use std::path::Path;
+use tools::println_cargo_style;
+
/// 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.
///
@@ -9,7 +11,7 @@ use std::path::Path;
const DOCS_DIR: &str = "./docs";
fn main() {
- println!("Fixing code box empty lines in docs/**/*.md ...");
+ println_cargo_style!("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);
@@ -32,15 +34,16 @@ fn main() {
let new_content = fix_code_box_empty_lines(&content);
if new_content != content {
fs::write(path, &new_content).unwrap();
- println!(" Fixed: {}", path.display());
+ println_cargo_style!("Fixed: {}", path.display());
fixed_count += 1;
}
file_count += 1;
});
- println!(
- "Done. Scanned {} files, fixed {} files.",
- file_count, fixed_count
+ println_cargo_style!(
+ "Done: Scanned {} files, fixed {} files.",
+ file_count,
+ fixed_count
);
}
diff --git a/dev_tools/src/bin/docsify-sidebar-gen.rs b/dev_tools/src/bin/docsify-sidebar-gen.rs
index ed9e9f0..e0f9370 100644
--- a/dev_tools/src/bin/docsify-sidebar-gen.rs
+++ b/dev_tools/src/bin/docsify-sidebar-gen.rs
@@ -1,13 +1,15 @@
use std::collections::BTreeMap;
use std::path::Path;
+use tools::println_cargo_style;
+
const PAGES_ROOT: &str = "./docs/pages";
const SIDEBAR_PATH: &str = "./docs/_sidebar.md";
const SIDEBAR_HEAD: &str = "- [Welcome!](README)\n";
fn main() {
- println!("Refreshing _sidebar.md");
+ println_cargo_style!("Refresh: _sidebar.md");
gen_sidebar();
gen_translation_sidebars();
}
@@ -21,7 +23,7 @@ fn gen_sidebar() {
let sidebar_path = repo_root.join(SIDEBAR_PATH);
std::fs::write(&sidebar_path, lines).unwrap();
- println!(" Generated: {}", sidebar_path.display());
+ println_cargo_style!("Generated: {}", sidebar_path.display());
}
/// Generate _sidebar.md inside translation directories
@@ -48,7 +50,7 @@ fn gen_translation_sidebars() {
let sidebar_path = path.join("_sidebar.md");
std::fs::write(&sidebar_path, lines).unwrap();
- println!(" Generated: {}", sidebar_path.display());
+ println_cargo_style!("Generated: {}", sidebar_path.display());
}
}
}
diff --git a/dev_tools/src/bin/refresh-docs.rs b/dev_tools/src/bin/refresh-docs.rs
index 32821ed..ffa80a2 100644
--- a/dev_tools/src/bin/refresh-docs.rs
+++ b/dev_tools/src/bin/refresh-docs.rs
@@ -2,6 +2,7 @@ use std::path::Path;
use just_fmt::snake_case;
use just_template::{Template, tmpl};
+use tools::println_cargo_style;
const EXAMPLE_ROOT: &str = "./examples/";
const OUTPUT_PATH: &str = "./mingling/src/example_docs.rs";
@@ -9,10 +10,7 @@ const OUTPUT_PATH: &str = "./mingling/src/example_docs.rs";
const TEMPLATE_CONTENT: &str = include_str!("../../../mingling/src/example_docs.rs.tmpl");
fn main() {
- {
- println!("Refreshing Examples");
- gen_example_doc_module();
- }
+ gen_example_doc_module();
}
fn gen_example_doc_module() {
@@ -32,6 +30,8 @@ fn gen_example_doc_module() {
}
}
+ examples.sort();
+
for example in examples {
tmpl!(template += {
examples {
@@ -43,7 +43,7 @@ fn gen_example_doc_module() {
)
}
});
- println!(" Refresh: {}", example.name);
+ println_cargo_style!("Refresh: {}", example.name);
}
let template_str = template.to_string();
@@ -63,6 +63,26 @@ struct ExampleContent {
cargo_toml: String,
}
+impl PartialOrd for ExampleContent {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for ExampleContent {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.name.cmp(&other.name)
+ }
+}
+
+impl PartialEq for ExampleContent {
+ fn eq(&self, other: &Self) -> bool {
+ self.name == other.name
+ }
+}
+
+impl Eq for ExampleContent {}
+
impl ExampleContent {
pub fn read(name: &str) -> Self {
let repo = find_git_repo().unwrap();
diff --git a/dev_tools/src/lib.rs b/dev_tools/src/lib.rs
index 8b13789..1b5dd0f 100644
--- a/dev_tools/src/lib.rs
+++ b/dev_tools/src/lib.rs
@@ -1 +1,103 @@
+use colored::Colorize;
+#[macro_export]
+macro_rules! run_cmd {
+ ($fmt:literal, $($arg:tt)*) => {
+ $crate::run_cmd(format!($fmt, $($arg)*))
+ };
+ ($cmd:expr) => {
+ $crate::run_cmd($cmd)
+ };
+}
+
+#[macro_export]
+macro_rules! println_cargo_style {
+ ($fmt:literal, $($arg:tt)*) => {
+ $crate::println_cargo_style(format!($fmt, $($arg)*))
+ };
+ ($cmd:expr) => {
+ $crate::println_cargo_style($cmd)
+ };
+}
+
+#[macro_export]
+macro_rules! eprintln_cargo_style {
+ ($fmt:literal, $($arg:tt)*) => {
+ $crate::eprintln_cargo_style(format!($fmt, $($arg)*))
+ };
+ ($cmd:expr) => {
+ $crate::eprintln_cargo_style($cmd)
+ };
+}
+
+pub fn println_cargo_style(str: impl Into<String>) {
+ let s = str.into();
+ let (prefix, content) = if let Some(pos) = s.find(':') {
+ (
+ s[..pos].trim().to_string(),
+ s[pos + 1..].trim_start().to_string(),
+ )
+ } else {
+ ("".to_string(), s.trim().to_string())
+ };
+
+ if prefix.len() > 12 {
+ panic!(
+ "prefix length exceeds 12: '{}' has length {}",
+ prefix,
+ prefix.len()
+ );
+ }
+
+ let padding = " ".repeat(12 - prefix.len());
+
+ println!(
+ "{}{} {}",
+ padding,
+ prefix.bold().bright_green(),
+ content.trim()
+ );
+}
+
+pub fn eprintln_cargo_style(str: impl Into<String>) {
+ println!("{}: {}", "error".bold().bright_red(), str.into());
+}
+
+pub fn run_cmd(cmd: impl Into<String>) -> Result<(), i32> {
+ let shell = if cfg!(target_os = "windows") {
+ "powershell"
+ } else {
+ "sh"
+ };
+ let status = std::process::Command::new(shell)
+ .arg("-c")
+ .arg(cmd.into())
+ .current_dir(std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")))
+ .status()
+ .expect("failed to execute command");
+
+ let exit_code = status.code().unwrap_or(1);
+ if exit_code == 0 {
+ Ok(())
+ } else {
+ Err(exit_code)
+ }
+}
+
+pub fn cargo_tomls() -> Vec<std::path::PathBuf> {
+ let mut cargo_tomls = Vec::new();
+ let mut dirs = vec![std::path::PathBuf::from(".")];
+ while let Some(dir) = dirs.pop() {
+ if let Ok(entries) = std::fs::read_dir(&dir) {
+ for entry in entries.flatten() {
+ let path = entry.path();
+ if path.is_dir() {
+ dirs.push(path);
+ } else if path.file_name().and_then(|n| n.to_str()) == Some("Cargo.toml") {
+ cargo_tomls.push(path);
+ }
+ }
+ }
+ }
+ cargo_tomls
+}
diff --git a/docs/_zh_CN/pages/2-implementing-fallbacks.md b/docs/_zh_CN/pages/2-implementing-fallbacks.md
index a7c04d0..e4fd3f8 100644
--- a/docs/_zh_CN/pages/2-implementing-fallbacks.md
+++ b/docs/_zh_CN/pages/2-implementing-fallbacks.md
@@ -133,7 +133,7 @@ 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
```
-
+
以上便是 **Mingling** 的回退机制,在接下来的章节中,您将学习如何使用 `Picker` 解析复杂的用户输入。
<p align="center" style="font-size: 0.85em; color: gray;">
diff --git a/docs/_zh_CN/pages/3-parsing-complex-arguments.md b/docs/_zh_CN/pages/3-parsing-complex-arguments.md
index 923218e..4ee9cec 100644
--- a/docs/_zh_CN/pages/3-parsing-complex-arguments.md
+++ b/docs/_zh_CN/pages/3-parsing-complex-arguments.md
@@ -12,7 +12,7 @@
```rust
let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
```
-
+
而本章节将会引入新的 **Mingling** 特性:`Picker`,它提供轻量且和 **Mingling** 类型路由高度契合的命令解析方案。
要启用 `Picker`,您需要修改 `Cargo.toml` ✏️
@@ -24,7 +24,7 @@ mingling = {
features = ["parser"]
}
```
-
+
好了,多的不说,让我们上手编辑代码,重写前文的解析代码 ✏️
```rust
@@ -40,7 +40,7 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
ResultGreetSomeone::new(name)
}
```
-
+
`Picker` 为所有 `Into<Vec<String>>` 实现了 `pick` `pick_or` `pick_or_route` 函数:它们可以语义化地从字符串列表中 **拾取 (Pick)** 参数,并转换为结构化数据。
对于上述示例中的代码:
@@ -48,7 +48,7 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
```rust
prev.pick_or((), "World").unpack();
```
-
+
它的语义为:
```rust
@@ -60,7 +60,7 @@ prev.pick_or((), "World").unpack();
// | |______________________ 拾取或使用默认
// |___________________________ 从前一个输入中
```
-
+
## 解析标志参数
若您的程序设计需要解析标志参数 (例如:`greet --name Alice`),可以使用如下方式:
@@ -68,7 +68,7 @@ prev.pick_or((), "World").unpack();
```rust
prev.pick_or(["--name", "-n"], "World").unpack();
```
-
+
同理,它的语义为:
```rust
@@ -80,7 +80,7 @@ prev.pick_or(["--name", "-n"], "World").unpack();
// | |____________________________________ 拾取或使用默认
// |_________________________________________ 从前一个输入中
```
-
+
## 关于 `.unpack()` 💡
您可能注意到了,`Picker` 在命令解析的最后,会执行一个 `.unpack()` 函数,它的作用是将前面解析出来的结果,转换为结构化信息。
@@ -94,10 +94,10 @@ let (name, age, id) = prev
.pick::<u8>(["--age", "-a"])
.pick::<u32>(["--id", "-I"])
.unpack();
-
+
// 可解析参数 --name Alice --age 21 --id 0711251
```
-
+
> [!IMPORTANT]
> `Picker` 对解析顺序极其敏感,特别是位置参数:因为它是顺序解析的
>
@@ -146,7 +146,7 @@ fn render_greet_someone(prev: ResultGreetSomeone) {
r_println!("Hello, {}!", *prev);
}
```
-
+
若使用 `pick_or_route`,写法会变得相对复杂:因为 `.unpack()` 不再直接返回参数,而是 `Result<Value, Route>`
不过 **Mingling** 提供了简化展开的宏 `route!`,它不复杂,只是省略了一部分样板代码:
@@ -160,7 +160,7 @@ let name = match pick_result {
Err(e) => return e,
};
```
-
+
## 提取值的后处理
在您使用 `pick` 提取了用户输入后,可以使用 `after` 或 `after_or_route` 立刻处理该参数 ✏️
@@ -182,7 +182,7 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
ResultGreetSomeone::new(name) // 此处传入的 name 已被格式化处理
}
```
-
+
同样,您可以使用 `after_or_route` 来处理输入参数的格式错误 ✏️
```rust
@@ -227,7 +227,7 @@ fn render_greet_someone(prev: ResultGreetSomeone) {
r_println!("Hello, {}!", *prev);
}
```
-
+
## 布尔值解析
`Picker` 当然也可以解析 **布尔类型**,但是布尔类型分为显式和隐式模式,
@@ -251,7 +251,7 @@ fn handle_some_entry(prev: SomeEntry) -> NextProcess {
// 其他逻辑
}
```
-
+
## 特殊用法:`usize` 解析
**Mingling** 为 `usize` 提供了一个特殊的用法:解析类似 `25G`、`32mb` 等字样 ✏️
@@ -264,7 +264,7 @@ fn parse_size() {
assert_eq!(size, 25 * 1024 * 1024);
}
```
-
+
## 自定义可解析类型
您可以使用 `Pickable` trait 使您的类型支持被 `Picker` 解析,这也是 `Picker` 拓展性的来源 ✏️
@@ -292,7 +292,7 @@ impl Pickable for Address {
}
}
```
-
+
我们为 `Address` 实现 `Pickable`:接下来我们便可以使用 `ip:port` 的方式来输入参数了 ✏️
```rust
@@ -312,14 +312,14 @@ fn render_connected(prev: ResultConnected) {
r_println!("Connected: IP: {} PORT: {}", addr.ip, addr.port);
}
```
-
+
执行效果如下:
```bash
~> your-bin connect --addr 127.0.0.1:8080
Connected: IP: 127.0.0.1 PORT: 8080
```
-
+
## 自动为枚举实现 Pickable
要为枚举类型实现 `Pickable` trait,无需手动实现:`Picker` 会为所有实现了 `PickableEnum` 的类型实现 `Pickable`,只需要该枚举类型实现了 `EnumTag` ✏️
@@ -339,7 +339,7 @@ pub enum Fruits {
// 为 Fruits 实现 PickableEnum
impl PickableEnum for Fruits {}
```
-
+
接下来您便可以直接使用 `Picker` 解析该类型 ✏️
```rust
@@ -356,7 +356,7 @@ fn render_ate_fruit(prev: ResultFruit) {
r_println!("Picked fruit: {:?}", *prev);
}
```
-
+
以上便是 `Picker` 的所有用法,在下一章节,我会介绍如何在 **Mingling** 内为命令实现帮助文档。
<p align="center" style="font-size: 0.85em; color: gray;">
diff --git a/docs/pages/1-creating-your-first-program.md b/docs/pages/1-creating-your-first-program.md
index 40690c7..0a6ff3c 100644
--- a/docs/pages/1-creating-your-first-program.md
+++ b/docs/pages/1-creating-your-first-program.md
@@ -250,7 +250,7 @@ Hello, World!
~> your-bin greet Alice
Hello, Alice!
```
-
+
At this point, you have successfully created a basic **Mingling** command-line program. The next chapter will explain how to implement a fallback mechanism for your command-line program to handle cases where a command or renderer does not exist.
<p align="center" style="font-size: 0.85em; color: gray;">
diff --git a/docs/pages/3-parsing-complex-arguments.md b/docs/pages/3-parsing-complex-arguments.md
index 8cd5503..b48b28b 100644
--- a/docs/pages/3-parsing-complex-arguments.md
+++ b/docs/pages/3-parsing-complex-arguments.md
@@ -12,7 +12,7 @@
```rust
let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
```
-
+
This chapter introduces a new **Mingling** feature: `Picker`. It provides a lightweight parsing solution that meshes well with **Mingling**'s typed routing.
To enable `Picker`, edit `Cargo.toml` ✏️
@@ -24,7 +24,7 @@ mingling = {
features = ["parser"]
}
```
-
+
Enough talk, let's get coding and rewrite the parsing logic from the prev. section ✏️
```rust
@@ -33,14 +33,14 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
// Prev. approach:
// let args = prev.inner;
// let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
-
+
// New approach with Picker
let name = prev.pick_or((), "World").unpack();
-
+
ResultGreetSomeone::new(name)
}
```
-
+
`Picker` implements `pick`, `pick_or`, and `pick_or_route` for anything `Into<Vec<String>>`. These functions let you semantically **pick** args from a string list and convert them into structured data.
In the code above:
@@ -48,7 +48,7 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
```rust
prev.pick_or((), "World").unpack();
```
-
+
Its meaning:
```rust
@@ -60,7 +60,7 @@ prev.pick_or((), "World").unpack();
// | |______________________ pick or use default
// |___________________________ from the prev. input
```
-
+
## Parsing Flag Args
If your app needs to parse flag args (e.g., `greet --name Alice`), do:
@@ -68,7 +68,7 @@ prev.pick_or((), "World").unpack();
```rust
prev.pick_or(["--name", "-n"], "World").unpack();
```
-
+
Its meaning:
```rust
@@ -80,7 +80,7 @@ prev.pick_or(["--name", "-n"], "World").unpack();
// | |____________________________________ pick or use default
// |_________________________________________ from the prev. input
```
-
+
## About `.unpack()` 💡
You may have noticed `Picker` calls `.unpack()` at the end of parsing. It converts the parsed result into structured info.
@@ -94,10 +94,10 @@ let (name, age, id) = prev
.pick::<u8>(["--age", "-a"])
.pick::<u32>(["--id", "-I"])
.unpack();
-
+
// Parses: --name Alice --age 21 --id 0711251
```
-
+
> [!IMPORTANT]
> `Picker` is very order-sensitive, esp. with positional args: it parses sequentially.
>
@@ -113,10 +113,10 @@ let (name, age, id) = prev
```rust
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
pack!(ResultGreetSomeone = String);
pack!(ErrorGreetNoNameProvided = ());
-
+
#[chain]
fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
// Use `pick_or_route` to extract the `--name` arg
@@ -128,39 +128,39 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
)
// After using any routable method, `unpack` returns `Result<Value, Route>`
.unpack();
-
+
// Use the `route!` macro to expand `pick_result`,
// If it's `Err`, the chain returns here, routing to the specified type
let name = route!(pick_result);
ResultGreetSomeone::new(name).to_chain()
}
-
+
// Handles rendering for `ErrorGreetNoNameProvided`
#[renderer]
fn render_err_greet_no_name_provided(_prev: ErrorGreetNoNameProvided) {
r_println!("Error: No name provided.")
}
-
+
#[renderer]
fn render_greet_someone(prev: ResultGreetSomeone) {
r_println!("Hello, {}!", *prev);
}
```
-
+
Using `pick_or_route` makes the code a bit more complex: `.unpack()` no longer returns the value directly, but `Result<Value, Route>`.
However, **Mingling** provides the `route!` macro to simplify expansion. It's not complex—just cuts some boilerplate:
```rust
let name = route!(pick_result);
-
+
// Expands to
let name = match pick_result {
Ok(r) => r,
Err(e) => return e,
};
```
-
+
## Post-Processing Extracted Values
After using `pick` to extract user input, you can use `after` or `after_or_route` to process the arg immediately ✏️
@@ -178,19 +178,19 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
.to_string()
})
.unpack();
-
+
ResultGreetSomeone::new(name) // name is now formatted
}
```
-
+
Similarly, use `after_or_route` to handle format errors in input args ✏️
```rust
dispatcher!("greet", GreetCommand => GreetEntry);
-
+
pack!(ResultGreetSomeone = String);
pack!(ErrorGreetNameTooLong = usize);
-
+
#[chain]
fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
let pick_result = prev
@@ -201,7 +201,7 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
.to_lowercase()
.trim()
.to_string();
-
+
// Check name length, route to error type if too long
let len = name.len();
if len < 32 {
@@ -212,22 +212,22 @@ fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
})
.unpack();
let name = route!(pick_result);
-
+
ResultGreetSomeone::new(name).to_chain()
}
-
+
#[renderer]
fn render_error_greet_name_too_long(prev: ErrorGreetNameTooLong) {
let len = *prev;
r_println!("Error: name too long (length: {} > 32)", len);
}
-
+
#[renderer]
fn render_greet_someone(prev: ResultGreetSomeone) {
r_println!("Hello, {}!", *prev);
}
```
-
+
## Parsing Booleans
`Picker` can parse **bool** types too, but with both explicit and implicit modes:
@@ -247,11 +247,11 @@ fn render_greet_someone(prev: ResultGreetSomeone) {
fn handle_some_entry(prev: SomeEntry) -> NextProcess {
let confirmed: bool = prev.pick::<Yes>(()).unpack().is_yes();
let confirm: bool = prev.pick::<bool>(["--confirm", "-C"]).unpack();
-
+
// other logic
}
```
-
+
## Special Use: `usize` Parsing
**Mingling** has a special use for `usize`: parsing strings like `25G`, `32mb`, etc. ✏️
@@ -264,7 +264,7 @@ fn parse_size() {
assert_eq!(size, 25 * 1024 * 1024);
}
```
-
+
## Custom Parsable Types
Use the `Pickable` trait to make your types parsable by `Picker`. This is where `Picker`'s extensibility comes from ✏️
@@ -276,50 +276,50 @@ pub struct Address {
ip: String,
port: u16,
}
-
+
impl Pickable for Address {
type Output = Self;
fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output> {
// Extract raw string from Argument using Flag
let raw = args.pick_argument(flag)?;
-
+
// Parse raw string into structured data
let parts: Vec<&str> = raw.split(':').collect();
let ip = parts.first()?.to_string();
let port: u16 = parts.get(1)?.parse().ok()?;
-
+
Some(Address { ip, port })
}
}
```
-
+
With `Pickable` implemented for `Address`, we can now use `ip:port` format for input ✏️
```rust
dispatcher!("connect", ConnectCommand => ConnectEntry);
-
+
pack!(ResultConnected = Address);
-
+
#[chain]
fn handle_connect_entry(prev: ConnectEntry) -> NextProcess {
let address: Address = prev.pick("--addr").unpack();
ResultConnected::new(address)
}
-
+
#[renderer]
fn render_connected(prev: ResultConnected) {
let addr = prev.inner;
r_println!("Connected: IP: {} PORT: {}", addr.ip, addr.port);
}
```
-
+
Running it:
```bash
~> your-bin connect --addr 127.0.0.1:8080
Connected: IP: 127.0.0.1 PORT: 8080
```
-
+
## Auto-Implementing Pickable for Enums
No need to manually implement `Pickable` for enums: `Picker` auto-implements it for any type that implements `PickableEnum`, as long as it also implements `EnumTag` ✏️
@@ -335,28 +335,28 @@ pub enum Fruits {
Banana,
Orange,
}
-
+
// Implement PickableEnum for Fruits
impl PickableEnum for Fruits {}
```
-
+
Now you can directly use `Picker` to parse this type ✏️
```rust
pack!(ResultFruit = Fruits);
-
+
#[chain]
fn handle_eat_fruit_entry(prev: EatFruitEntry) -> NextProcess {
let fruit: Fruits = prev.pick("--fruit").unpack();
ResultFruit::new(fruit)
}
-
+
#[renderer]
fn render_ate_fruit(prev: ResultFruit) {
r_println!("Picked fruit: {:?}", *prev);
}
```
-
+
That's all for `Picker`'s usage. In the next chapter, I'll introduce how to implement help docs for commands in **Mingling**.
<p align="center" style="font-size: 0.85em; color: gray;">
diff --git a/docs/res/ci_banner.txt b/docs/res/ci_banner.txt
new file mode 100644
index 0000000..78f574b
--- /dev/null
+++ b/docs/res/ci_banner.txt
@@ -0,0 +1,12 @@
+ __ __ __ __ __ ______ ______
+ / \ / |/ | / | / | / \ / |
+ ██ \ /██ |██/ _______ ______ ██ | ██/ _______ ______ /██████ |██████/
+ ███ \ /███ |/ |/ \ / \ ██ | / |/ \ / \ ██ | ██/ ██ |
+ ████ /████ |██ |███████ |/██████ | ██ | ██ |███████ |/██████ | ██ | ██ |
+ ██ ██ ██/██ |██ |██ | ██ |██ | ██ | ██ | ██ |██ | ██ |██ | ██ | ██ | __ ██ |
+ ██ |███/ ██ |██ |██ | ██ |██ \__██ | ██ |_____ ██ |██ | ██ |██ \__██ | ██ \__/ | _██ |_
+ ██ | █/ ██ |██ |██ | ██ |██ ██ | ██ |██ |██ | ██ |██ ██ | ██ ██/ / ██ |
+ ██/ ██/ ██/ ██/ ██/ ███████ | ████████/ ██/ ██/ ██/ ███████ | ██████/ ██████/
+ / \__██ | / \__██ |
+ ██ ██/ ██ ██/
+ ██████/ ██████/
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index aca27ce..9f1b46b 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -1,133 +1,133 @@
// Auto generated
-/// `Mingling` Example - Basic
+/// `Mingling` Example - Async
+///
+/// After enabling the `async` feature:
+/// 1. The `chain!` macro will support using **async** functions,
+/// 2. The `exec` function of `Program` will return a `Future` for you to use with an async runtime
+///
+/// ## Enable Feature
+/// Enable the `async` feature for mingling in `Cargo.toml`
+/// ```toml
+/// [dependencies]
+/// mingling = { version = "...", features = ["async"] }
+/// ```
///
/// # How to Run
/// ```bash
-/// cargo run --manifest-path ./examples/example-basic/Cargo.toml -- hello World
+/// cargo run --manifest-path ./examples/example-async/Cargo.toml -- hello World
/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
-/// name = "example-basic"
+/// name = "example-async"
/// version = "0.0.1"
/// edition = "2024"
///
/// [dependencies]
-/// mingling = { path = "../../mingling" }
+/// tokio = { version = "1", features = ["full"] }
+/// mingling = { path = "../../mingling", features = ["async"] }
/// ```
///
/// main.rs
/// ```ignore
/// use mingling::macros::{chain, dispatcher, gen_program, pack, r_println, renderer};
///
-/// // Define dispatcher `HelloCommand`, directing subcommand "hello" to `HelloEntry`
/// dispatcher!("hello", HelloCommand => HelloEntry);
///
-/// fn main() {
-/// // Create program
+/// // Use Tokio async runtime
+/// #[tokio::main]
+/// async fn main() {
/// let mut program = ThisProgram::new();
-///
-/// // Add dispatcher `HelloCommand`
/// program.with_dispatcher(HelloCommand);
///
/// // Run program
-/// program.exec();
+/// program.exec().await;
/// }
///
-/// // Register wrapper type `Hello`, setting inner to `String`
/// pack!(Hello = String);
///
-/// // Register chain to `ThisProgram`, handling logic from `HelloEntry`
+/// // You can freely use async / non-async functions to declare your Chain
+///
/// #[chain]
-/// fn parse_name(prev: HelloEntry) -> NextProcess {
-/// // Extract string from `HelloEntry` as argument
+/// // fn parse_name(prev: HelloEntry) -> NextProcess {
+/// async fn parse_name(prev: HelloEntry) -> NextProcess {
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
-///
-/// // Build `Hello` type and route to renderer
/// Hello::new(name).to_render()
/// }
///
-/// // Register renderer to `ThisProgram`, handling rendering of `Hello`
+/// // For renderers, you can still only use synchronous functions
/// #[renderer]
/// fn render_hello_who(prev: Hello) {
-/// // Print message
/// r_println!("Hello, {}!", *prev);
-///
-/// // Program ends here
/// }
///
-/// // Generate program, default is `ThisProgram`
/// gen_program!();
/// ```
-pub mod example_basic {}
-/// `Mingling` Example - Async
-///
-/// After enabling the `async` feature:
-/// 1. The `chain!` macro will support using **async** functions,
-/// 2. The `exec` function of `Program` will return a `Future` for you to use with an async runtime
-///
-/// ## Enable Feature
-/// Enable the `async` feature for mingling in `Cargo.toml`
-/// ```toml
-/// [dependencies]
-/// mingling = { version = "...", features = ["async"] }
-/// ```
+pub mod example_async {}
+/// `Mingling` Example - Basic
///
/// # How to Run
/// ```bash
-/// cargo run --manifest-path ./examples/example-async/Cargo.toml -- hello World
+/// cargo run --manifest-path ./examples/example-basic/Cargo.toml -- hello World
/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
-/// name = "example-async"
+/// name = "example-basic"
/// version = "0.0.1"
/// edition = "2024"
///
/// [dependencies]
-/// tokio = { version = "1", features = ["full"] }
-/// mingling = { path = "../../mingling", features = ["async"] }
+/// mingling = { path = "../../mingling" }
/// ```
///
/// main.rs
/// ```ignore
/// use mingling::macros::{chain, dispatcher, gen_program, pack, r_println, renderer};
///
+/// // Define dispatcher `HelloCommand`, directing subcommand "hello" to `HelloEntry`
/// dispatcher!("hello", HelloCommand => HelloEntry);
///
-/// // Use Tokio async runtime
-/// #[tokio::main]
-/// async fn main() {
+/// fn main() {
+/// // Create program
/// let mut program = ThisProgram::new();
+///
+/// // Add dispatcher `HelloCommand`
/// program.with_dispatcher(HelloCommand);
///
/// // Run program
-/// program.exec().await;
+/// program.exec();
/// }
///
+/// // Register wrapper type `Hello`, setting inner to `String`
/// pack!(Hello = String);
///
-/// // You can freely use async / non-async functions to declare your Chain
-///
+/// // Register chain to `ThisProgram`, handling logic from `HelloEntry`
/// #[chain]
-/// // fn parse_name(prev: HelloEntry) -> NextProcess {
-/// async fn parse_name(prev: HelloEntry) -> NextProcess {
+/// fn parse_name(prev: HelloEntry) -> NextProcess {
+/// // Extract string from `HelloEntry` as argument
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
+///
+/// // Build `Hello` type and route to renderer
/// Hello::new(name).to_render()
/// }
///
-/// // For renderers, you can still only use synchronous functions
+/// // Register renderer to `ThisProgram`, handling rendering of `Hello`
/// #[renderer]
/// fn render_hello_who(prev: Hello) {
+/// // Print message
/// r_println!("Hello, {}!", *prev);
+///
+/// // Program ends here
/// }
///
+/// // Generate program, default is `ThisProgram`
/// gen_program!();
/// ```
-pub mod example_async {}
+pub mod example_basic {}
/// `Mingling` Example - Completion
///
/// # How to Deploy
@@ -270,6 +270,69 @@ pub mod example_async {}
/// gen_program!();
/// ```
pub mod example_completion {}
+/// `Mingling` Example - Dispatch Tree
+///
+/// # How to Deploy
+/// 1. Enable the `dispatch_tree` feature (`comp` is optional)
+/// ```toml
+/// mingling = { version = "...", features = [
+/// "dispatch_tree", // Enable this feature
+/// "comp" // optional
+/// ] }
+/// ```
+///
+/// 2. Using `cargo expand`:
+///
+/// ```bash
+/// cargo expand --manifest-path examples/example-dispatch-tree/Cargo.toml > expanded.rs
+/// cat expanded.rs | grep dispatch_args_trie -A 264
+/// ```
+///
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-dispatch-tree"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling", features = ["dispatch_tree", "comp"] }
+/// ```
+///
+/// main.rs
+/// ```ignore
+/// #![allow(unused_mut)]
+///
+/// use mingling::macros::{dispatcher, gen_program};
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+///
+/// // After enabling `dispatch_tree`, this method will no longer exist
+/// // program.with_dispatcher(CommandGreet);
+/// //
+/// // The `CompletionDispatcher` automatically generated by `comp` will also be imported
+/// // automatically
+/// // program.with_dispatcher(CompletionDispatcher);
+///
+/// program.exec();
+/// }
+///
+/// dispatcher!("greet", CommandGreet => EntryGreet);
+/// dispatcher!("help", CommandHelp => EntryHelp);
+/// dispatcher!("quit", CommandQuit => EntryQuit);
+/// dispatcher!("list", CommandList => EntryList);
+/// dispatcher!("status", CommandStatus => EntryStatus);
+/// dispatcher!("save", CommandSave => EntrySave);
+/// dispatcher!("load", CommandLoad => EntryLoad);
+/// dispatcher!("config", CommandConfig => EntryConfig);
+/// dispatcher!("run", CommandRun => EntryRun);
+/// dispatcher!("debug", CommandDebug => EntryDebug);
+/// dispatcher!("version", CommandVersion => EntryVersion);
+///
+/// gen_program!();
+/// ```
+pub mod example_dispatch_tree {}
/// `Mingling` Example - General Renderer
///
/// ## Step1 - Enable Feature
@@ -447,66 +510,3 @@ pub mod example_general_renderer {}
/// gen_program!();
/// ```
pub mod example_picker {}
-/// `Mingling` Example - Dispatch Tree
-///
-/// # How to Deploy
-/// 1. Enable the `dispatch_tree` feature (`comp` is optional)
-/// ```toml
-/// mingling = { version = "...", features = [
-/// "dispatch_tree", // Enable this feature
-/// "comp" // optional
-/// ] }
-/// ```
-///
-/// 2. Using `cargo expand`:
-///
-/// ```bash
-/// cargo expand --manifest-path examples/example-dispatch-tree/Cargo.toml > expanded.rs
-/// cat expanded.rs | grep dispatch_args_trie -A 264
-/// ```
-///
-/// Cargo.toml
-/// ```ignore
-/// [package]
-/// name = "example-dispatch-tree"
-/// version = "0.1.0"
-/// edition = "2024"
-///
-/// [dependencies]
-/// mingling = { path = "../../mingling", features = ["dispatch_tree", "comp"] }
-/// ```
-///
-/// main.rs
-/// ```ignore
-/// #![allow(unused_mut)]
-///
-/// use mingling::macros::{dispatcher, gen_program};
-///
-/// fn main() {
-/// let mut program = ThisProgram::new();
-///
-/// // After enabling `dispatch_tree`, this method will no longer exist
-/// // program.with_dispatcher(CommandGreet);
-/// //
-/// // The `CompletionDispatcher` automatically generated by `comp` will also be imported
-/// // automatically
-/// // program.with_dispatcher(CompletionDispatcher);
-///
-/// program.exec();
-/// }
-///
-/// dispatcher!("greet", CommandGreet => EntryGreet);
-/// dispatcher!("help", CommandHelp => EntryHelp);
-/// dispatcher!("quit", CommandQuit => EntryQuit);
-/// dispatcher!("list", CommandList => EntryList);
-/// dispatcher!("status", CommandStatus => EntryStatus);
-/// dispatcher!("save", CommandSave => EntrySave);
-/// dispatcher!("load", CommandLoad => EntryLoad);
-/// dispatcher!("config", CommandConfig => EntryConfig);
-/// dispatcher!("run", CommandRun => EntryRun);
-/// dispatcher!("debug", CommandDebug => EntryDebug);
-/// dispatcher!("version", CommandVersion => EntryVersion);
-///
-/// gen_program!();
-/// ```
-pub mod example_dispatch_tree {}
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index 21a848d..79d5b19 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -5,7 +5,7 @@
//!
//! # Use
//!
-//! ```rust
+//! ```rust,ignore
//! use mingling::macros::{dispatcher, gen_program, r_println, renderer};
//!
//! #[tokio::main]