diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-17 05:49:19 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-17 05:49:19 +0800 |
| commit | 57c53affe3542cb6bd4e79ee4c18f20a1bd76b2d (patch) | |
| tree | 1cd4aef44cb7a45a8cd9d520b598f5f181e24c76 /examples | |
| parent | ef23cd944402939605c78a4a853ef6e33af02c21 (diff) | |
refactor!: replace pack! macros with derive-based pipeline types
Remove the `pack!`, `pack_err!`, `pack_structural!`, and
`pack_err_structural!` macros, replacing all pipeline type definitions
with `#[derive(Grouped)]` and `#[derive(Grouped, Wrap)]` attributes.
This changes the generated struct shape from named-field structs with an
`inner` field to tuple structs accessed via `.0`, and removes the
auto-generated `name` and `info` fields from error types.
Diffstat (limited to 'examples')
25 files changed, 191 insertions, 587 deletions
diff --git a/examples/example-argument-picker/src/main.rs b/examples/example-argument-picker/src/main.rs index 99eee30..aefb86e 100644 --- a/examples/example-argument-picker/src/main.rs +++ b/examples/example-argument-picker/src/main.rs @@ -44,17 +44,32 @@ use mingling::setup::picker::BasicProgramSetup; dispatcher!("calc", EntryCalculate); -pack_err!(ErrorNumberANotProvided); -pack_err!(ErrorNumberBNotProvided); -pack_err!(ErrorNumberOperatorNotProvided); -pack_err!(ErrorDivisionByZero); +#[derive(Grouped, Default)] +pub struct ErrorNumberANotProvided; -pack!(StateAdd = (f32, f32)); -pack!(StateSubtract = (f32, f32)); -pack!(StateMultiply = (f32, f32)); -pack!(StateDivide = (f32, f32)); +#[derive(Grouped, Default)] +pub struct ErrorNumberBNotProvided; -pack!(ResultNumber = f32); +#[derive(Grouped, Default)] +pub struct ErrorNumberOperatorNotProvided; + +#[derive(Grouped, Default)] +pub struct ErrorDivisionByZero; + +#[derive(Grouped, Wrap)] +pub struct StateAdd((f32, f32)); + +#[derive(Grouped, Wrap)] +pub struct StateSubtract((f32, f32)); + +#[derive(Grouped, Wrap)] +pub struct StateMultiply((f32, f32)); + +#[derive(Grouped, Wrap)] +pub struct StateDivide((f32, f32)); + +#[derive(Grouped, Wrap)] +pub struct ResultNumber(f32); #[derive(Grouped)] struct StateCalculate { @@ -135,13 +150,13 @@ fn handle_calc(args: EntryCalculate) -> Next { // Use the arg! macro to define a positional argument of type f32 // | // vvvvvvvvvv - args.pick_or_route(&arg![f32], || ErrorNumberANotProvided::default().to_chain()) + args.pick_or_route(&arg![f32], || ErrorNumberANotProvided.to_chain()) .pick_or_route(&arg![Operator], || { - ErrorNumberOperatorNotProvided::default().to_chain() + ErrorNumberOperatorNotProvided.to_chain() }) // Returns a routable type when not found or fails to parse // | - // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - .pick_or_route(&arg![f32], || ErrorNumberBNotProvided::default().to_chain()) + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + .pick_or_route(&arg![f32], || ErrorNumberBNotProvided.to_chain()) // Use `to_result` to parse arguments // and convert to Result<(Tuple, ...), Route> type .to_result() @@ -149,7 +164,7 @@ fn handle_calc(args: EntryCalculate) -> Next { // --------- IMPORTANT --------- if operator == Operator::Slash && number_b == 0. { - return ErrorDivisionByZero::default().to_chain(); + return ErrorDivisionByZero.to_chain(); } StateCalculate { @@ -163,41 +178,41 @@ fn handle_calc(args: EntryCalculate) -> Next { #[chain] fn handle_state_calculate(state: StateCalculate) -> Next { match (state.operator, state.number_a, state.number_b) { - (Operator::Plus, a, b) => StateAdd::new((a, b)).to_chain(), - (Operator::Dash, a, b) => StateSubtract::new((a, b)).to_chain(), - (Operator::Slash, a, b) => StateDivide::new((a, b)).to_chain(), - (Operator::Star, a, b) => StateMultiply::new((a, b)).to_chain(), + (Operator::Plus, a, b) => StateAdd((a, b)).to_chain(), + (Operator::Dash, a, b) => StateSubtract((a, b)).to_chain(), + (Operator::Slash, a, b) => StateDivide((a, b)).to_chain(), + (Operator::Star, a, b) => StateMultiply((a, b)).to_chain(), } } #[chain] fn handle_state_add(state_add: StateAdd) -> ResultNumber { - let (a, b) = state_add.inner; - ResultNumber::new(a + b) + let (a, b) = state_add.0; + ResultNumber(a + b) } #[chain] fn handle_state_subtract(state_subtract: StateSubtract) -> ResultNumber { - let (a, b) = state_subtract.inner; - ResultNumber::new(a - b) + let (a, b) = state_subtract.0; + ResultNumber(a - b) } #[chain] fn handle_state_multiply(state_multiply: StateMultiply) -> ResultNumber { - let (a, b) = state_multiply.inner; - ResultNumber::new(a * b) + let (a, b) = state_multiply.0; + ResultNumber(a * b) } #[chain] fn handle_state_divide(state_divide: StateDivide) -> ResultNumber { - let (a, b) = state_divide.inner; - ResultNumber::new(a / b) + let (a, b) = state_divide.0; + ResultNumber(a / b) } #[renderer] fn render_result_number(result: ResultNumber, setting: &ResNumberDisplaySetting) -> String { let round = setting.round; - let result = if round { result.round() } else { result.inner }; + let result = if round { result.round() } else { result.0 }; format!("Result: {}", result) } diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs index 090602a..7e212d9 100644 --- a/examples/example-async-support/src/main.rs +++ b/examples/example-async-support/src/main.rs @@ -41,7 +41,8 @@ async fn main() { dispatcher!("download", EntryDownload); -pack!(ResultDownloaded = String); +#[derive(Grouped, Wrap)] +pub struct ResultDownloaded(String); // --------- IMPORTANT --------- #[chain] @@ -65,5 +66,5 @@ gen_program!(); async fn fake_download(file_name: String) -> ResultDownloaded { tokio::time::sleep(std::time::Duration::from_secs(1)).await; - ResultDownloaded::new(file_name) + ResultDownloaded(file_name) } diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs index 44736ad..ce41b2d 100644 --- a/examples/example-basic/src/main.rs +++ b/examples/example-basic/src/main.rs @@ -17,11 +17,11 @@ use mingling::prelude::*; use std::io::Write; // Define the `greet` subcommand -// _____________________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm") -// / _____________________ dispatcher name -// | / _________ entry, records raw arguments -// | | / ^^^^^^^^^^^^^ -// vvvvv vvvvvvvv vvvvvvvvvv \_ equivalent to pack!(EntryGreet = Vec<String>) +// _________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm") +// / +// | _________ entry, records raw arguments +// | / ^^^^^^^^^^^^^ +// vvvvv vvvvvvvvvv \_ a newtype wrapper around Vec<String> dispatcher!("greet", EntryGreet); fn main() { @@ -33,21 +33,22 @@ fn main() { } // Quickly wrap a type into a type recognizable by the current program -// ____________________ Wrapped type name -// / _______ Wrapped type inner value -// | / -// vvvvvvvvvv vvvvvv -pack!(ResultName = String); +// ___________________ Registers this type into ThisProgram +// / _______ Adds DerefMut, Deref, Into, From wrappers +// | / +// vvvvvvvvvv vvvvv +#[derive(Grouped, Wrap)] +pub struct ResultName(String); // Define the `handle_greet` chain for parsing input text // ____________________ Previous type: // / Mingling deduces types at runtime and routes them to this function // | _____ will be expanded to: -// | / impl Into<mingling::ChainProcess<ThisProgram>> +// | / ChainProcess<ThisProgram> #[chain] // vvvvvvvvvv vvvv fn handle_greet(args: EntryGreet) -> Next { let name: ResultName = args - .inner + .0 .first() .cloned() .unwrap_or_else(|| "World".to_string()) diff --git a/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs b/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs index 2b7aba9..b04c683 100644 --- a/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs +++ b/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs @@ -4,12 +4,13 @@ use std::io::Write; dispatcher!("hello"); -pack!(ResultMessage = String); +#[derive(Grouped, Wrap)] +pub struct ResultMessage(String); #[chain] pub fn handle_my(args: EntryHello) -> Next { let name: ResultMessage = args - .inner + .0 .first() .cloned() .unwrap_or_else(|| "World".to_string()) diff --git a/examples/example-combine-pathf-metadata/src/sub/mod.rs b/examples/example-combine-pathf-metadata/src/sub/mod.rs index ba56285..38efa2f 100644 --- a/examples/example-combine-pathf-metadata/src/sub/mod.rs +++ b/examples/example-combine-pathf-metadata/src/sub/mod.rs @@ -7,6 +7,7 @@ use std::io::Write; // Implicit dispatcher form — creates `CMDHello` / `EntryHello` in this module dispatcher!("hello"); + // Creates `CMDDescription` / `EntryDescription` in this module dispatcher!("desc", EntryDescription); @@ -27,14 +28,17 @@ pub fn hello_desc() -> Description { } } -pack!(ResultName = String); -pack!(DescResult = String); +#[derive(Grouped, Wrap)] +pub struct ResultName(String); + +#[derive(Grouped, Wrap)] +pub struct DescResult(String); /// Chain for `hello` — reads the name and produces a `ResultName`. #[chain] pub fn handle_hello(args: EntryHello) -> Next { let name: ResultName = args - .inner + .0 .first() .cloned() .unwrap_or_else(|| "World".to_string()) @@ -51,7 +55,7 @@ pub fn handle_desc(_args: EntryDescription) -> Next { None => "EntryHello has no description".to_string(), }; // --------- IMPORTANT --------- - DescResult::new(msg).to_render() + DescResult(msg).to_render() } /// Renders the greeting message with the provided name. diff --git a/examples/example-command-macro/src/main.rs b/examples/example-command-macro/src/main.rs index e525098..6740518 100644 --- a/examples/example-command-macro/src/main.rs +++ b/examples/example-command-macro/src/main.rs @@ -22,27 +22,30 @@ fn main() { ThisProgram::new().exec_and_exit(); } -pack!(ResultGreeting = String); -pack!(ResultGoodbye = ()); +#[derive(Grouped, Wrap)] +pub struct ResultGreeting(String); + +#[derive(Grouped)] +pub struct ResultGoodbye; // --------- IMPORTANT --------- // Auto-generates dispatcher!("hello.world", EntryHelloWorld); #[command] fn hello_world() -> ResultGreeting { - ResultGreeting::new("World".to_string()) + ResultGreeting("World".to_string()) } // Auto-generates dispatcher!("hello-world", EntryGreetSomeone); #[command(node = "greet-someone")] fn greet_someone(args: Vec<String>) -> ResultGreeting { let name = args.pick_or(&arg![String], || "World".to_string()).unwrap(); - ResultGreeting::new(name) + ResultGreeting(name) } // Auto-generates dispatcher!("goodbye", EntryGoodBye); #[command(entry = EntryGoodBye)] fn goodbye() -> ResultGoodbye { - ResultGoodbye::default() + ResultGoodbye } // --------- IMPORTANT --------- diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs index d363697..e14326f 100644 --- a/examples/example-completion/src/main.rs +++ b/examples/example-completion/src/main.rs @@ -101,7 +101,8 @@ fn complete_greet_entry(ctx: &ShellContext) -> Suggest { // --------- IMPORTANT --------- dispatcher!("greet", EntryGreet); -pack!(ResultName = (u8, String)); +#[derive(Grouped, Wrap)] +pub struct ResultName((u8, String)); #[chain] fn handle_greet(args: EntryGreet) -> Next { @@ -116,7 +117,7 @@ fn handle_greet(args: EntryGreet) -> Next { /// Renders the greeting with the result name and repeat count. #[renderer] fn render_name(result: ResultName) -> RenderResult { - let (repeat, name) = result.inner; + let (repeat, name) = result.0; let mut render_result = RenderResult::new(); let mut parts = Vec::with_capacity(repeat as usize); for _ in 0..repeat { diff --git a/examples/example-error-handling/src/main.rs b/examples/example-error-handling/src/main.rs index 05b451b..ec7e6c9 100644 --- a/examples/example-error-handling/src/main.rs +++ b/examples/example-error-handling/src/main.rs @@ -29,35 +29,41 @@ use std::io::Write; dispatcher!("hello", EntryHello); // Define error types -pack!(ErrorNoNameProvided = ()); -pack!(ErrorNameTooLong = u16); -pack!(ErrorNameNotAvailable = ()); +#[derive(Grouped)] +pub struct ErrorNoNameProvided; + +#[derive(Grouped, Wrap)] +pub struct ErrorNameTooLong(u16); + +#[derive(Grouped)] +pub struct ErrorNameNotAvailable; // Define success type -pack!(ResultName = String); +#[derive(Grouped, Wrap)] +pub struct ResultName(String); // Pre-registered names static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"]; #[chain] fn handle_hello(args: EntryHello) -> Next { - let Some(name) = args.inner.first().cloned() else { + let Some(name) = args.0.first().cloned() else { // If no name is provided, pass ErrorNoNameProvided - return ErrorNoNameProvided::default().to_render(); + return ErrorNoNameProvided.to_render(); }; if name.len() > 10 { // If the name is too long, pass ErrorNameTooLong - return ErrorNameTooLong::new(name.len() as u16).to_render(); + return ErrorNameTooLong(name.len() as u16).to_render(); } if VEC_REGISTERED_NAMES.contains(&name.as_str()) { // If the name already exists, pass ErrorNameNotAvailable - return ErrorNameNotAvailable::default().to_render(); + return ErrorNameNotAvailable.to_render(); } // If the name is valid, pass ResultName - ResultName::new(name).to_render() + ResultName(name).to_render() } /// Renders a successful greeting with the given name. @@ -96,12 +102,7 @@ fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult { #[renderer] fn render_entry_fallback(err: EntryFallback) -> RenderResult { let mut render_result = RenderResult::new(); - writeln!( - render_result, - "Command not found: \"{}\"", - err.inner.join(" ") - ) - .ok(); + writeln!(render_result, "Command not found: \"{}\"", err.0.join(" ")).ok(); render_result } diff --git a/examples/example-exitcode/src/main.rs b/examples/example-exitcode/src/main.rs index d3e035e..c7f731d 100644 --- a/examples/example-exitcode/src/main.rs +++ b/examples/example-exitcode/src/main.rs @@ -36,18 +36,21 @@ fn main() { dispatcher!("hello", EntryHello); -pack!(ErrorNoNameProvided = ()); -pack!(ResultName = String); +#[derive(Grouped)] +pub struct ErrorNoNameProvided; + +#[derive(Grouped, Wrap)] +pub struct ResultName(String); #[chain] fn handle_hello(args: EntryHello) -> Next { - let Some(name) = args.inner.first().cloned() else { + let Some(name) = args.0.first().cloned() else { // If no name is provided, pass ErrorNoNameProvided - return ErrorNoNameProvided::default().to_render(); + return ErrorNoNameProvided.to_render(); }; // If the name is valid, pass ResultName - ResultName::new(name).to_render() + ResultName(name).to_render() } /// Renders a successful greeting with the given name. diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs index 1807e5e..721cea0 100644 --- a/examples/example-hook/src/main.rs +++ b/examples/example-hook/src/main.rs @@ -53,12 +53,13 @@ fn main() { program.exec_and_exit(); } -pack!(ResultName = String); +#[derive(Grouped, Wrap)] +pub struct ResultName(String); #[chain] fn handle_greet(args: EntryGreet) -> Next { let name: ResultName = args - .inner + .0 .first() .cloned() .unwrap_or_else(|| "World".to_string()) diff --git a/examples/example-lazy-resources/src/main.rs b/examples/example-lazy-resources/src/main.rs index cc1604a..2243f14 100644 --- a/examples/example-lazy-resources/src/main.rs +++ b/examples/example-lazy-resources/src/main.rs @@ -52,7 +52,8 @@ fn init_res_large_data() -> ResLargeData { dispatcher!("show", EntryShow); dispatcher!("none", EntryNone); -pack!(ResultShow = BTreeMap<Key, Value>); +#[derive(Grouped, Wrap)] +pub struct ResultShow(BTreeMap<Key, Value>); fn main() { let mut program = ThisProgram::new(); diff --git a/examples/example-metadata/src/main.rs b/examples/example-metadata/src/main.rs index 94facb4..99a8aae 100644 --- a/examples/example-metadata/src/main.rs +++ b/examples/example-metadata/src/main.rs @@ -56,14 +56,17 @@ pub fn greet_desc() -> Description { } // --------- IMPORTANT --------- -pack!(ResultName = String); -pack!(DescResult = String); +#[derive(Grouped, Wrap)] +pub struct ResultName(String); + +#[derive(Grouped, Wrap)] +pub struct DescResult(String); /// Chain for `greet` — reads the name and produces a `ResultName`. #[chain] fn handle_greet(args: EntryGreet) -> Next { let name: ResultName = args - .inner + .0 .first() .cloned() .unwrap_or_else(|| "World".to_string()) @@ -81,7 +84,7 @@ fn handle_desc(_args: EntryDescription) -> Next { None => "EntryGreet has no description".to_string(), }; // --------- IMPORTANT --------- - DescResult::new(msg).to_render() + DescResult(msg).to_render() } /// Chain for `nodoc` — asks for metadata on an entry that has none. @@ -94,7 +97,7 @@ fn handle_nodoc(_args: EntryNoDescription) -> Next { None => "EntryDescription has no description".to_string(), }; // --------- IMPORTANT --------- - DescResult::new(msg).to_render() + DescResult(msg).to_render() } /// Renders the greeting message with the provided name. diff --git a/examples/example-outside-type/src/main.rs b/examples/example-outside-type/src/main.rs index a04727f..8721d9a 100644 --- a/examples/example-outside-type/src/main.rs +++ b/examples/example-outside-type/src/main.rs @@ -43,7 +43,8 @@ group!(ErrorIo = std::io::Error); // you can use this syntax to create an alias simultaneously // --------- IMPORTANT --------- -pack!(ParsedNumber = i32); +#[derive(Grouped, Wrap)] +pub struct ParsedNumber(i32); /// Parse the first argument as an `i32` /// @@ -51,9 +52,9 @@ pack!(ParsedNumber = i32); /// On failure, routes to `render_parse_error` via the registered outside type. #[chain] fn parse_number(args: EntryParse) -> Next { - let input = args.inner.first().cloned().unwrap_or_default(); + let input = args.0.first().cloned().unwrap_or_default(); match input.parse::<i32>() { - Ok(num) => ParsedNumber::new(num).to_chain(), + Ok(num) => ParsedNumber(num).to_chain(), Err(e) => e.to_chain(), } } diff --git a/examples/example-pack-err/Cargo.lock b/examples/example-pack-err/Cargo.lock deleted file mode 100644 index bcfbb41..0000000 --- a/examples/example-pack-err/Cargo.lock +++ /dev/null @@ -1,235 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "example-pack-err" -version = "0.1.0" -dependencies = [ - "mingling", - "serde", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "just_fmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96" - -[[package]] -name = "memchr" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" - -[[package]] -name = "might_be_async" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca871cea620b07bd2e6da0c883891a25bead698c43a9ab64b0fd663a7a78d5f" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "toml", -] - -[[package]] -name = "mingling" -version = "0.5.0" -dependencies = [ - "mingling_core", - "mingling_macros", - "serde", -] - -[[package]] -name = "mingling_core" -version = "0.5.0" -dependencies = [ - "just_fmt", - "might_be_async", - "serde", - "serde_json", -] - -[[package]] -name = "mingling_macros" -version = "0.5.0" -dependencies = [ - "just_fmt", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "syn" -version = "2.0.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/examples/example-pack-err/Cargo.toml b/examples/example-pack-err/Cargo.toml deleted file mode 100644 index ddab6fd..0000000 --- a/examples/example-pack-err/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "example-pack-err" -version = "0.1.0" -edition = "2024" - -[dependencies] -serde = { version = "1.0.228", features = ["derive"] } - -[dependencies.mingling] -path = "../../mingling" -features = [ - "structural_renderer", - "extras", -] - -[workspace] diff --git a/examples/example-pack-err/page.toml b/examples/example-pack-err/page.toml deleted file mode 100644 index 7423087..0000000 --- a/examples/example-pack-err/page.toml +++ /dev/null @@ -1,10 +0,0 @@ -[example] -id = "example-pack-err" -name = "Pack an Error" -icon = "🛑" -category = "macros" -desc = """ -Demonstrates how to use the `pack_err!` macro to define error types with automatic `name` field (snake_case at compile time) and optional `info` field. Also shows `--json` serialization when `structural_renderer` is enabled. -""" -tags = ["pack_err!", "extras", "structural_renderer", "--json"] -files = ["src/main.rs", "Cargo.toml"] diff --git a/examples/example-pack-err/src/main.rs b/examples/example-pack-err/src/main.rs deleted file mode 100644 index e30e4cb..0000000 --- a/examples/example-pack-err/src/main.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! Example `pack_err!` -//! -//! > This example demonstrates how to use the `pack_err!` macro to define error types -//! > with automatic `name` field (set to snake_case at compile time) and optional `info` field. -//! > Also demonstrates `--json` serialization when `structural_renderer` is enabled. -//! -//! Run: -//! ```bash -//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find -//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find Cargo.toml -//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find src -//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find-structural --json -//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find-structural Cargo.toml --json -//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find-structural src --json -//! ``` -//! -//! Output: -//! ```plaintext -//! Search path not provided -//! Not a directory: Cargo.toml -//! Found directory: src -//! {"name":"error_not_found"} -//! {"name":"error_not_dir","info":"Cargo.toml"} -//! {"inner":"src"} -//! {"name":"error_not_found_structural"} -//! {"name":"error_not_dir_structural","info":"Cargo.toml"} -//! ``` - -use mingling::prelude::*; -use mingling::setup::StructuralRendererSetup; -use std::io::Write; -use std::path::PathBuf; - -dispatcher!("find", EntryFind); -dispatcher!("find-structural", EntryFindStructural); - -// --------- IMPORTANT --------- -// `pack_err!` is a convenient macro for defining error types. -// -// Simple form: pack_err!(ErrorNotFound); -// Typed form: pack_err!(ErrorNotDir = PathBuf); -// -// The simple form generates a struct with `name: String` and `impl Default`. -// name = "error_not_found" (automatically snake_cased at compile time) -// -// The typed form additionally generates `pub fn new(info)`. -// name = "error_not_dir" -// -// When `structural_renderer` is enabled, the struct also gets -// `#[derive(serde::Serialize)]` for --json / --yaml output. -// --------- IMPORTANT --------- - -// Simple form — name = "error_not_found" -pack_err!(ErrorNotFound); - -// Typed form — name = "error_not_dir" -pack_err!(ErrorNotDir = PathBuf); - -// Simple form — with StructuralData support for --json / --yaml -pack_err_structural!(ErrorNotFoundStructural); - -// Typed form — with StructuralData support for --json / --yaml -pack_err_structural!(ErrorNotDirStructural = PathBuf); - -// Success type with StructuralData support -pack_structural!(ResultPath = PathBuf); - -#[chain] -fn handle_find(args: EntryFind) -> Next { - let Some(path_str) = args.inner.first().cloned() else { - // No path provided → use the simple error form (Default) - return ErrorNotFound::default().to_render(); - }; - - let path = PathBuf::from(&path_str); - if path.is_dir() { - // Is a directory → success - ResultPath::new(path).to_render() - } else { - // Not a directory (or doesn't exist) → use the typed error form - ErrorNotDir::new(path).to_render() - } -} - -#[chain] -fn handle_find_structural(args: EntryFindStructural) -> Next { - let Some(path_str) = args.inner.first().cloned() else { - // No path provided → use the simple error form (Default) - return ErrorNotFoundStructural::default().to_render(); - }; - - let path = PathBuf::from(&path_str); - if path.is_dir() { - // Is a directory → success - ResultPath::new(path).to_render() - } else { - // Not a directory (or doesn't exist) → use the typed error form - ErrorNotDirStructural::new(path).to_render() - } -} - -/// Renders the successful result with the found directory path. -#[renderer] -fn render_result_path(path: ResultPath) -> RenderResult { - let mut render_result = RenderResult::new(); - writeln!(render_result, "Found directory: {}", path.display()).ok(); - render_result -} - -/// Renders the error when no search path is provided. -#[renderer] -fn render_error_not_found(_: ErrorNotFound) -> RenderResult { - let mut render_result = RenderResult::new(); - writeln!(render_result, "Search path not provided").ok(); - render_result -} - -/// Renders the error when the given path is not a directory. -#[renderer] -fn render_error_not_dir(err: ErrorNotDir) -> RenderResult { - let mut render_result = RenderResult::new(); - writeln!(render_result, "Not a directory: {}", err.info.display()).ok(); - render_result -} - -/// Renders the structural error when no search path is provided. -#[renderer] -fn render_error_not_found_structural(_: ErrorNotFoundStructural) -> RenderResult { - let mut render_result = RenderResult::new(); - writeln!(render_result, "Search path not provided").ok(); - render_result -} - -/// Renders the structural error when the given path is not a directory. -#[renderer] -fn render_error_not_dir_structural(err: ErrorNotDirStructural) -> RenderResult { - let mut render_result = RenderResult::new(); - writeln!(render_result, "Not a directory: {}", err.info.display()).ok(); - render_result -} - -gen_program!(); - -fn main() { - let mut program = ThisProgram::new(); - - // Add StructuralRendererSetup to support --json / --yaml flags - program.with_setup(StructuralRendererSetup); - - let _ = program.exec(); -} diff --git a/examples/example-pack-err/test.toml b/examples/example-pack-err/test.toml deleted file mode 100644 index c4509cb..0000000 --- a/examples/example-pack-err/test.toml +++ /dev/null @@ -1,35 +0,0 @@ -[[runs]] -input = [ "find" ] - -expect.exit-code = 0 -expect.result = "Search path not provided" - -[[runs]] -input = [ "find", "Cargo.toml" ] - -expect.exit-code = 0 -expect.result = "Not a directory: Cargo.toml" - -[[runs]] -input = [ "find", "examples" ] - -expect.exit-code = 0 -expect.result = "Found directory: examples" - -[[runs]] -input = [ "find-structural", "--json" ] - -expect.exit-code = 0 -expect.result = "{\"name\":\"error_not_found_structural\"}" - -[[runs]] -input = [ "find-structural", "Cargo.toml", "--json" ] - -expect.exit-code = 0 -expect.result = "{\"name\":\"error_not_dir_structural\",\"info\":\"Cargo.toml\"}" - -[[runs]] -input = [ "find-structural", "examples", "--json" ] - -expect.exit-code = 0 -expect.result = "{\"inner\":\"examples\"}" diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs index 59adf07..f91b0f0 100644 --- a/examples/example-panic-unwind/src/main.rs +++ b/examples/example-panic-unwind/src/main.rs @@ -20,7 +20,9 @@ use mingling::{hook::ProgramHook, prelude::*}; use std::io::Write; dispatcher!("panic", EntryPanic); -pack!(NotPanic = ()); + +#[derive(Grouped)] +pub struct NotPanic; fn main() { let mut program = ThisProgram::new(); @@ -47,7 +49,7 @@ fn handle_panic(prev: EntryPanic) -> Next { // Panic happens here, will be caught panic!("{}", s) } - None => NotPanic::default().into(), + None => NotPanic.into(), } } diff --git a/examples/example-pathfinder/src/sub/mod.rs b/examples/example-pathfinder/src/sub/mod.rs index cf0a97a..b90c618 100644 --- a/examples/example-pathfinder/src/sub/mod.rs +++ b/examples/example-pathfinder/src/sub/mod.rs @@ -3,12 +3,14 @@ use mingling::prelude::*; use std::io::Write; dispatcher!("greet", EntryGreet); -pack!(ResultName = String); + +#[derive(Grouped, Wrap)] +pub struct ResultName(String); #[chain] pub fn handle_greet(args: EntryGreet) -> Next { let name: ResultName = args - .inner + .0 .first() .cloned() .unwrap_or_else(|| "World".to_string()) diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs index d0fad6d..9b81ced 100644 --- a/examples/example-repl-basic/src/main.rs +++ b/examples/example-repl-basic/src/main.rs @@ -70,7 +70,8 @@ fn main() { } // Create error route -pack!(ErrorDirectoryNotExist = PathBuf); +#[derive(Grouped, Wrap)] +pub struct ErrorDirectoryNotExist(PathBuf); // Create commands: cd ls exit dispatcher!("cd", EntryCd); @@ -79,16 +80,18 @@ dispatcher!("exit", EntryExit); dispatcher!("clear", EntryClear); // Define data needed for the cd command's execution phase -pack!(StateChangeDirectory = String); +#[derive(Grouped, Wrap)] +pub struct StateChangeDirectory(String); // Define data needed for the ls command's rendering phase -pack!(ResultList = Vec<String>); +#[derive(Grouped, Wrap)] +pub struct ResultList(Vec<String>); // Parse cd command arguments #[chain] fn parse_cd_args(prev: EntryCd) -> Next { let join = prev.pick_or_default(&arg![String]).unwrap(); - StateChangeDirectory::new(join).into() + StateChangeDirectory(join).into() } // Execute directory change @@ -96,12 +99,12 @@ fn parse_cd_args(prev: EntryCd) -> Next { fn handle_cd(prev: StateChangeDirectory, current_dir: &mut ResCurrentDir) -> Next { use just_fmt::fmt_path::fmt_path; - let join = prev.inner; + let join = prev.0; let new_dir = fmt_path(current_dir.dir.join(join)).unwrap_or_default(); // If the path is not found, route to error handling if !new_dir.exists() { - return ErrorDirectoryNotExist::new(new_dir).to_render(); + return ErrorDirectoryNotExist(new_dir).to_render(); } current_dir.dir = new_dir; @@ -126,14 +129,14 @@ fn handle_ls(_prev: EntryLs, current_dir: &ResCurrentDir) -> Next { .collect(); // Render ResultList - ResultList::new(entries).to_render() + ResultList(entries).to_render() } /// Render ResultList data #[renderer] fn render_list(list: ResultList) -> RenderResult { let mut render_result = RenderResult::new(); - for item in list.inner { + for item in list.0 { writeln!(render_result, "{}", item).ok(); } render_result @@ -160,12 +163,7 @@ fn handle_clear(_prev: EntryClear) { #[renderer] fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) -> RenderResult { let mut render_result = RenderResult::new(); - writeln!( - render_result, - "Directory not found: {}", - err.inner.display() - ) - .ok(); + writeln!(render_result, "Directory not found: {}", err.0.display()).ok(); render_result } diff --git a/examples/example-setup/src/main.rs b/examples/example-setup/src/main.rs index 523a567..59c503f 100644 --- a/examples/example-setup/src/main.rs +++ b/examples/example-setup/src/main.rs @@ -55,13 +55,14 @@ fn custom_setup(program: &mut Program<ThisProgram>) { dispatcher!("greet", EntryGreet); -pack!(ResultGreeting = String); +#[derive(Grouped, Wrap)] +pub struct ResultGreeting(String); /// Chain: reads the `ResAppName` and `ResAppVersion` resources. #[chain] fn handle_greet(args: EntryGreet, app: &ResAppName, version: &ResAppVersion) -> Next { let who = args - .inner + .0 .first() .cloned() .unwrap_or_else(|| "World".to_string()); diff --git a/examples/example-structural-renderer/src/main.rs b/examples/example-structural-renderer/src/main.rs index a1bddbd..c1cfd69 100644 --- a/examples/example-structural-renderer/src/main.rs +++ b/examples/example-structural-renderer/src/main.rs @@ -32,7 +32,8 @@ fn main() { } // --------- IMPORTANT --------- -// For beautiful output structure, do not use `pack!` to wrap the types that need to be output. +// For beautiful output structure, do not wrap the types that need to be output +// in a newtype; instead, use a named struct. // Instead, manually implement // ____________________________________ Mark as structured data so it can be rendered // / ____________________ Implement serde::Serialize @@ -48,7 +49,7 @@ struct Info { } // This will output: {"member_name":"name","member_age":32} structure -// If using pack!(Info = (String, i32)); +// If wrapping with a tuple newtype (e.g. `#[derive(Grouped, Wrap)] pub struct Info((String, i32));`) // Output: {"inner":["name", 32]} // --------- IMPORTANT --------- diff --git a/examples/example-unit-test/src/main.rs b/examples/example-unit-test/src/main.rs index 29ff9da..e4c6504 100644 --- a/examples/example-unit-test/src/main.rs +++ b/examples/example-unit-test/src/main.rs @@ -36,30 +36,30 @@ mod tests { let hello_with_valid_name = handle_hello(entry!("Peter")).into(); assert_render_result!(hello_with_valid_name); let result_name = unpack_chain_process!(hello_with_valid_name, ResultName); - assert_eq!(result_name.inner, "Peter"); + assert_eq!(result_name.0, "Peter"); } #[test] fn test_render_result_name() { - let r = render_result_name(ResultName::new("Peter".into())); + let r = render_result_name(ResultName("Peter".into())); assert_eq!(r.to_string().as_str(), "Hello, Peter!") } #[test] fn test_render_error_no_name_provided() { - let r = render_error_no_name_provided(ErrorNoNameProvided::default()); + let r = render_error_no_name_provided(ErrorNoNameProvided); assert_eq!(r.to_string().as_str(), "No name provided") } #[test] fn test_render_error_name_not_available() { - let r = render_error_name_not_available(ErrorNameNotAvailable::default()); + let r = render_error_name_not_available(ErrorNameNotAvailable); assert_eq!(r.to_string().as_str(), "Name not available") } #[test] fn test_render_error_name_too_long() { - let r = render_error_name_too_long(ErrorNameTooLong::new(17)); + let r = render_error_name_too_long(ErrorNameTooLong(17)); assert_eq!(r.to_string().as_str(), "Name too long: 17 > 10") } // --------- IMPORTANT --------- @@ -67,29 +67,35 @@ mod tests { dispatcher!("hello", EntryHello); -pack!(ErrorNoNameProvided = ()); -pack!(ErrorNameTooLong = u16); -pack!(ErrorNameNotAvailable = ()); +#[derive(Grouped)] +pub struct ErrorNoNameProvided; -pack!(ResultName = String); +#[derive(Grouped, Wrap)] +pub struct ErrorNameTooLong(u16); + +#[derive(Grouped)] +pub struct ErrorNameNotAvailable; + +#[derive(Grouped, Wrap)] +pub struct ResultName(String); static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"]; #[chain] fn handle_hello(args: EntryHello) -> Next { - let Some(name) = args.inner.first().cloned() else { - return ErrorNoNameProvided::default().to_render(); + let Some(name) = args.0.first().cloned() else { + return ErrorNoNameProvided.to_render(); }; if name.len() > 10 { - return ErrorNameTooLong::new(name.len() as u16).to_render(); + return ErrorNameTooLong(name.len() as u16).to_render(); } if VEC_REGISTERED_NAMES.contains(&name.as_str()) { - return ErrorNameNotAvailable::default().to_render(); + return ErrorNameNotAvailable.to_render(); } - ResultName::new(name).to_render() + ResultName(name).to_render() } /// Renders a successful greeting with the given name. @@ -128,12 +134,7 @@ fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult { #[renderer] fn render_entry_fallback(err: EntryFallback) -> RenderResult { let mut render_result = RenderResult::new(); - writeln!( - render_result, - "Command not found: \"{}\"", - err.inner.join(" ") - ) - .ok(); + writeln!(render_result, "Command not found: \"{}\"", err.0.join(" ")).ok(); render_result } diff --git a/examples/full-todolist/src/main.rs b/examples/full-todolist/src/main.rs index eea417a..9c7bfe8 100644 --- a/examples/full-todolist/src/main.rs +++ b/examples/full-todolist/src/main.rs @@ -36,15 +36,25 @@ dispatcher!("clean"); // Define states -pack!(StateAddTodo = String); -pack!(StateCompleteTodo = i32); -pack!(StateListTodo = bool); +#[derive(Grouped, Wrap)] +pub struct StateAddTodo(String); + +#[derive(Grouped, Wrap)] +pub struct StateCompleteTodo(i32); + +#[derive(Grouped, Wrap)] +pub struct StateListTodo(bool); // Define errors -pack!(ErrorNoTaskDescriptionProvided = ()); -pack!(ErrorNoIndexProvided = ()); -pack!(ErrorIndexOutOfBounds = ()); +#[derive(Grouped)] +pub struct ErrorNoTaskDescriptionProvided; + +#[derive(Grouped)] +pub struct ErrorNoIndexProvided; + +#[derive(Grouped)] +pub struct ErrorIndexOutOfBounds; fn main() { let mut program = ThisProgram::new(); @@ -76,11 +86,11 @@ fn main() { fn handle_add(args: EntryAdd) -> Next { let task: String = route! { args.pick_or_route(&arg![String], || { - ErrorNoTaskDescriptionProvided::new(()).to_chain() + ErrorNoTaskDescriptionProvided.to_chain() }) .to_result() }; - StateAddTodo::new(task).to_chain() + StateAddTodo(task).to_chain() } #[chain] @@ -92,7 +102,7 @@ fn handle_state_add_todo( let todolist = todolist.get_mut(); // Unpack state and read description - let description = state.inner; + let description = state.0; todolist.items.push(Todo { item: description, @@ -117,10 +127,10 @@ fn handle_list(_args: EntryList, todolist: &mut LazyRes<ResTodoList>) -> Next { #[chain] fn handle_complete(args: EntryComplete) -> Next { let index: i32 = route! { - args.pick_or_route(&arg![i32], || ErrorNoIndexProvided::new(()).to_chain()) + args.pick_or_route(&arg![i32], || ErrorNoIndexProvided.to_chain()) .to_result() }; - StateCompleteTodo::new(index).to_chain() + StateCompleteTodo(index).to_chain() } #[chain] @@ -129,12 +139,12 @@ fn handle_state_complete_todo( todolist: &mut LazyRes<ResTodoList>, ) -> Next { let todolist = todolist.get_mut(); - let index = state.inner as usize; + let index = state.0 as usize; if index < todolist.items.len() { todolist.items[index].completed = true; todolist.clone().to_render() } else { - ErrorIndexOutOfBounds::new(()).to_render() + ErrorIndexOutOfBounds.to_render() } } |
