diff options
Diffstat (limited to 'examples')
78 files changed, 613 insertions, 1716 deletions
diff --git a/examples/example-argument-parse/Cargo.lock b/examples/example-argument-parse/Cargo.lock deleted file mode 100644 index 8debd86..0000000 --- a/examples/example-argument-parse/Cargo.lock +++ /dev/null @@ -1,223 +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-argument-parse" -version = "0.1.0" -dependencies = [ - "mingling", -] - -[[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 = "just_fmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96" - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[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 2.0.118", - "toml", -] - -[[package]] -name = "mingling" -version = "0.4.0" -dependencies = [ - "mingling_core", - "mingling_macros", - "size", -] - -[[package]] -name = "mingling_core" -version = "0.4.0" -dependencies = [ - "just_fmt", - "might_be_async", -] - -[[package]] -name = "mingling_macros" -version = "0.4.0" -dependencies = [ - "just_fmt", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[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.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[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 = "syn" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" -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", -] diff --git a/examples/example-argument-parse/Cargo.toml b/examples/example-argument-parse/Cargo.toml deleted file mode 100644 index e08adcd..0000000 --- a/examples/example-argument-parse/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "example-argument-parse" -version = "0.1.0" -edition = "2024" - -[dependencies.mingling] -path = "../../mingling" - -# Enable `parser` features -features = ["parser", "extras"] - -[workspace] diff --git a/examples/example-argument-parse/page.toml b/examples/example-argument-parse/page.toml deleted file mode 100644 index fc4d646..0000000 --- a/examples/example-argument-parse/page.toml +++ /dev/null @@ -1,10 +0,0 @@ -[example] -id = "example-argument-parse" -name = "Argument Parse" -icon = "📋" -category = "parsing" -desc = """ -Shows how to use Mingling's `parser` feature with a `Picker` to extract and validate typed arguments from the command line. -""" -tags = ["pick", "Pickable"] -files = ["src/main.rs", "Cargo.toml"] diff --git a/examples/example-argument-parse/src/main.rs b/examples/example-argument-parse/src/main.rs deleted file mode 100644 index eb07672..0000000 --- a/examples/example-argument-parse/src/main.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Example Argument Parse -//! -//! > This example demonstrates how to use the `parser` feature to parse user input -//! -//! Run: -//! ```bash -//! cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- transfer README.md --size 32kib -//! cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- transfer src/ --dir -//! cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- strict-transfer README.md -//! cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- strict-transfer --dir -//! ``` -//! -//! Output: -//! ```plaintext -//! file: README.md (32768) -//! dir: src/ (1048576) -//! file: README.md (1048576) -//! Error: name is not provided -//! ``` - -use mingling::{macros::route, prelude::*}; -use std::io::Write; - -dispatcher!("transfer", CMDTransfer => EntryTransfer); -dispatcher!("strict-transfer", CMDStrictTransfer => EntryStrictTransfer); - -pack!(ResultFile = (bool, usize, String)); // (IsDir, Size, Name) - -#[chain] -fn handle_transfer_parse(args: EntryTransfer) -> Next { - // --------- IMPORTANT --------- - // First parse flag arguments (like --dir/-D), then positional arguments - let result: ResultFile = args - // Name --dir --size 20mib - // ^^^^^^^^^^^^_ first - .pick::<bool>(["--dir", "-D"]) - // Name --dir - // ^^^^^_ second (or `-D`) - .pick_or::<usize>("--size", 1024 * 1024_usize) - // Name - // ^^^^_ finally, pick positional arg - .pick::<String>(()) - .after(|str| str.trim().replace(' ', "")) - // Unpack to tuple (is_dir, size, name) - .unpack() - // Convert into ResultFile - .into(); - // --------- IMPORTANT --------- - result.into() -} - -pack!(ErrorNoNameProvided = ()); - -#[chain] -fn handle_strict_transfer_parse(args: EntryStrictTransfer) -> Next { - // --------- IMPORTANT --------- - // Strict parsing: error immediately if the name is not provided - let result: ResultFile = route! { // Use `route!` to wrap a Picker that contains `or_route` - args - .pick::<bool>(["--dir", "-D"]) - .pick_or::<usize>("--size", 1024 * 1024_usize) - // Finally parse the positional argument; if not found, route to `ErrorNoNameProvided` - .pick_or_route::<String, _>((), ErrorNoNameProvided::default()) - .after(|str| str.trim().replace(' ', "")) - .unpack() - } - // Convert into ResultFile - .into(); - // --------- IMPORTANT --------- - result.to_chain() -} - -/// Renders the parsed transfer result (file/dir, size, name). -#[renderer] -fn render_result_file(result: ResultFile) -> RenderResult { - let (is_dir, size, name) = result.into(); - let mut result = RenderResult::new(); - writeln!( - result, - "{}: {} ({})", - if is_dir { "dir" } else { "file" }, - name, - size - ) - .ok(); - result -} - -/// Renders the error when no name is provided. -#[renderer] -fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { - let mut result = RenderResult::new(); - writeln!(result, "Error: name is not provided").ok(); - result -} - -gen_program!(); - -fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDTransfer); - program.with_dispatcher(CMDStrictTransfer); - program.exec_and_exit(); -} diff --git a/examples/example-argument-parse/test.toml b/examples/example-argument-parse/test.toml deleted file mode 100644 index a405bd9..0000000 --- a/examples/example-argument-parse/test.toml +++ /dev/null @@ -1,29 +0,0 @@ -[[runs]] -input = [ "transfer" ] - -expect.exit-code = 0 -expect.result = "file: (1048576)" - -[[runs]] -input = [ "transfer", "--dir", "src" ] - -expect.exit-code = 0 -expect.result = "dir: src (1048576)" - -[[runs]] -input = [ "transfer", "--size", "500", "myfile.txt" ] - -expect.exit-code = 0 -expect.result = "file: myfile.txt (500)" - -[[runs]] -input = [ "strict-transfer", "README.md" ] - -expect.exit-code = 0 -expect.result = "file: README.md (1048576)" - -[[runs]] -input = [ "strict-transfer" ] - -expect.exit-code = 0 -expect.result = "Error: name is not provided" diff --git a/examples/example-argument-picker/Cargo.lock b/examples/example-argument-picker/Cargo.lock index 9977771..f7969b6 100644 --- a/examples/example-argument-picker/Cargo.lock +++ b/examples/example-argument-picker/Cargo.lock @@ -74,7 +74,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "arg-picker", "mingling_core", @@ -83,7 +83,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-argument-picker/src/main.rs b/examples/example-argument-picker/src/main.rs index 7fcc5db..aefb86e 100644 --- a/examples/example-argument-picker/src/main.rs +++ b/examples/example-argument-picker/src/main.rs @@ -42,19 +42,34 @@ use mingling::setup::picker::BasicProgramSetup; // --------- IMPORTANT --------- -dispatcher!("calc", CMDCalculate => EntryCalculate); +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 { @@ -125,7 +140,6 @@ fn main() { program.with_resource(ResNumberDisplaySetting { round: *round }); // --------- IMPORTANT --------- - program.with_dispatcher(CMDCalculate); program.exec_and_exit(); } @@ -136,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() @@ -150,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 { @@ -164,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/Cargo.lock b/examples/example-async-support/Cargo.lock index 67cd6b6..f122f6d 100644 --- a/examples/example-async-support/Cargo.lock +++ b/examples/example-async-support/Cargo.lock @@ -3,6 +3,23 @@ version = 4 [[package]] +name = "arg-picker" +version = "0.2.0" +dependencies = [ + "arg-picker-macros", + "just_fmt", +] + +[[package]] +name = "arg-picker-macros" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -58,16 +75,16 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "arg-picker", "mingling_core", "mingling_macros", - "size", ] [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -75,7 +92,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", @@ -146,12 +163,6 @@ dependencies = [ ] [[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[package]] name = "syn" version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-async-support/Cargo.toml b/examples/example-async-support/Cargo.toml index d1b952d..a0d2a75 100644 --- a/examples/example-async-support/Cargo.toml +++ b/examples/example-async-support/Cargo.toml @@ -6,8 +6,8 @@ edition = "2024" [dependencies.mingling] path = "../../mingling" -# Enable `parser` features -features = ["async", "parser"] +# Enable `picker` features +features = ["async", "picker"] # Import any async runtime, e.g. Tokio [dependencies.tokio] diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs index af86408..7e212d9 100644 --- a/examples/example-async-support/src/main.rs +++ b/examples/example-async-support/src/main.rs @@ -30,8 +30,6 @@ use std::io::Write; async fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDDownload); - // Add a hook to display when the download begins program.with_hook(ProgramHook::empty().on_begin::<_, ()>(|_| println!("Download begin"))); @@ -41,15 +39,16 @@ async fn main() { // --------- IMPORTANT --------- } -dispatcher!("download", CMDDownload => EntryDownload); +dispatcher!("download", EntryDownload); -pack!(ResultDownloaded = String); +#[derive(Grouped, Wrap)] +pub struct ResultDownloaded(String); // --------- IMPORTANT --------- #[chain] // vvvvv_ `async` keyword can be used directly here pub async fn handle_download(args: EntryDownload) -> Next { - let file_name = args.pick(()).unpack(); + let file_name = args.pick_or_default(&arg![String]).unwrap(); fake_download(file_name).await.into() } @@ -67,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/Cargo.lock b/examples/example-basic/Cargo.lock index 6be449c..e38b91d 100644 --- a/examples/example-basic/Cargo.lock +++ b/examples/example-basic/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs index 17077e2..ce41b2d 100644 --- a/examples/example-basic/src/main.rs +++ b/examples/example-basic/src/main.rs @@ -17,40 +17,38 @@ 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>) -dispatcher!("greet", CMDGreet => EntryGreet); +// _________________ 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() { // Create a new ThisProgram - let mut program = ThisProgram::new(); - - // Add the CMDGreet dispatcher - program.with_dispatcher(CMDGreet); + let program = ThisProgram::new(); // Run the program, then exit the process program.exec_and_exit(); } // 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-clap-binding/Cargo.lock b/examples/example-clap-binding/Cargo.lock index 46d334e..1eb895f 100644 --- a/examples/example-clap-binding/Cargo.lock +++ b/examples/example-clap-binding/Cargo.lock @@ -166,7 +166,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -174,7 +174,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -182,7 +182,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-clap-binding/src/main.rs b/examples/example-clap-binding/src/main.rs index d25c5f3..6ff963b 100644 --- a/examples/example-clap-binding/src/main.rs +++ b/examples/example-clap-binding/src/main.rs @@ -60,7 +60,6 @@ fn main() { // Capture Clap's help information and write to RenderResult // --------- IMPORTANT --------- - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } @@ -72,7 +71,7 @@ fn main() { // vvvvvvv vvvvvvvvvvvv vvvvvvv #[derive(Default, clap::Parser, Grouped)] #[dispatcher_clap( - "greet", CMDGreet, // Bind EntryGreet to "greet" command + "greet", // Bind EntryGreet to "greet" command help = true, // Generate clap help for EntryGreet error = ErrorGreetParsed, // Generate and bind error type for parse failure // ^^^^^\__ Using `error` intercepts parse failure information into the specified type, diff --git a/examples/example-combine-pathf-dispatch-tree/Cargo.lock b/examples/example-combine-pathf-dispatch-tree/Cargo.lock index 8560b73..9fdbf4f 100644 --- a/examples/example-combine-pathf-dispatch-tree/Cargo.lock +++ b/examples/example-combine-pathf-dispatch-tree/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -74,7 +74,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "mingling_pathf" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", 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/Cargo.lock b/examples/example-combine-pathf-metadata/Cargo.lock index 745167a..0e1782e 100644 --- a/examples/example-combine-pathf-metadata/Cargo.lock +++ b/examples/example-combine-pathf-metadata/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -74,7 +74,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "mingling_pathf" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-combine-pathf-metadata/src/main.rs b/examples/example-combine-pathf-metadata/src/main.rs index a68eac5..894e235 100644 --- a/examples/example-combine-pathf-metadata/src/main.rs +++ b/examples/example-combine-pathf-metadata/src/main.rs @@ -24,10 +24,7 @@ mod sub; use mingling::prelude::*; fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(sub::CMDHello); - program.with_dispatcher(sub::CMDDescription); - program.exec_and_exit(); + ThisProgram::new().exec_and_exit(); } gen_program!(); diff --git a/examples/example-combine-pathf-metadata/src/sub/mod.rs b/examples/example-combine-pathf-metadata/src/sub/mod.rs index 2e6776e..38efa2f 100644 --- a/examples/example-combine-pathf-metadata/src/sub/mod.rs +++ b/examples/example-combine-pathf-metadata/src/sub/mod.rs @@ -7,8 +7,9 @@ use std::io::Write; // Implicit dispatcher form — creates `CMDHello` / `EntryHello` in this module dispatcher!("hello"); + // Creates `CMDDescription` / `EntryDescription` in this module -dispatcher!("desc", CMDDescription => EntryDescription); +dispatcher!("desc", EntryDescription); /// The metadata type attached to an entry (`DataType`). #[derive(Debug, PartialEq, Eq)] @@ -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/Cargo.lock b/examples/example-command-macro/Cargo.lock index cd1babf..53d6f18 100644 --- a/examples/example-command-macro/Cargo.lock +++ b/examples/example-command-macro/Cargo.lock @@ -74,7 +74,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "arg-picker", "mingling_core", @@ -83,7 +83,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-command-macro/src/main.rs b/examples/example-command-macro/src/main.rs index 2bf932a..6740518 100644 --- a/examples/example-command-macro/src/main.rs +++ b/examples/example-command-macro/src/main.rs @@ -19,37 +19,33 @@ use mingling::{macros::buffer, picker::IntoPicker, prelude::*}; fn main() { - let mut program = ThisProgram::new(); - - // Import the dispatchers generated by the `#[command]` macro - program.with_dispatcher(CMDHelloWorld); - program.with_dispatcher(CMDGreetSomeone); - program.with_dispatcher(CMDGoodBye); - - program.exec_and_exit(); + 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", CMDHelloWorld => EntryHelloWorld); +// 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", CMDGreetSomeone => EntryGreetSomeone); +// 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", CMDGoodBye => EntryGoodBye); -#[command(name = CMDGoodBye, entry = EntryGoodBye)] +// Auto-generates dispatcher!("goodbye", EntryGoodBye); +#[command(entry = EntryGoodBye)] fn goodbye() -> ResultGoodbye { - ResultGoodbye::default() + ResultGoodbye } // --------- IMPORTANT --------- diff --git a/examples/example-completion/Cargo.lock b/examples/example-completion/Cargo.lock index dab3f37..d4b2f33 100644 --- a/examples/example-completion/Cargo.lock +++ b/examples/example-completion/Cargo.lock @@ -3,6 +3,23 @@ version = 4 [[package]] +name = "arg-picker" +version = "0.2.0" +dependencies = [ + "arg-picker-macros", + "just_fmt 0.2.0", +] + +[[package]] +name = "arg-picker-macros" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -84,16 +101,16 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "arg-picker", "mingling_core", "mingling_macros", - "size", ] [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt 0.2.0", "just_template", @@ -102,7 +119,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt 0.2.0", "proc-macro2", @@ -167,12 +184,6 @@ dependencies = [ ] [[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[package]] name = "syn" version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-completion/Cargo.toml b/examples/example-completion/Cargo.toml index 00c7f8c..9884f4e 100644 --- a/examples/example-completion/Cargo.toml +++ b/examples/example-completion/Cargo.toml @@ -9,7 +9,7 @@ path = "../../mingling" features = [ # Enable `comp` features "comp", - "parser", + "picker", ] [build-dependencies.mingling] diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs index d65be49..389cf74 100644 --- a/examples/example-completion/src/main.rs +++ b/examples/example-completion/src/main.rs @@ -44,19 +44,11 @@ //! Hello, Alice, Alice, Alice! //! ``` -use mingling::{macros::suggest, prelude::*, ShellContext, Suggest}; +use mingling::{ShellContext, Suggest, macros::suggest, prelude::*}; use std::io::Write; fn main() { - let mut program = ThisProgram::new(); - - program.with_dispatcher(CMDGreet); - - // --------- IMPORTANT --------- - // The `comp` feature makes `gen_program!()` generate a CMDCompletion automatically - // It adds a hidden `__comp` subcommand for communication with the completion script - program.with_dispatcher(crate::CMDCompletion); - // --------- IMPORTANT --------- + let program = ThisProgram::new(); // TIP: Note that the completion script reads stdout, // so make sure no output is produced before the CMDCompletion is dispatched. @@ -64,12 +56,12 @@ fn main() { } // --------- IMPORTANT --------- -// __________________________________________ Entry point bound to completion behavior -// / _________________________ Shell context for obtaining user input state -// | / ________ Suggest, used to return completion results -// vvvvvvvvvv | / -#[completion(EntryGreet)] // vvvvvvvvvvvv vvvvvvv -fn complete_greet_entry(ctx: &ShellContext) -> Suggest { +// _________________________________________ Entry point bound to completion behavior +// / _________________________ Shell context for obtaining user input state +// | / ________ Suggest, used to return completion results +// vvvvvvvvvv | / +#[completion(EntryGreet)] // vvvvvvvvvvvv vvvvvvv +fn complete_greet_entry(ctx: ShellContext) -> Suggest { // When the previous word is `greet` (the current command being typed) if ctx.previous_word == "greet" { // Return suggestions @@ -82,18 +74,22 @@ fn complete_greet_entry(ctx: &ShellContext) -> Suggest { } // When the user is typing `--repeat` - if ctx.filling_argument(["-r", "--repeat"]) { + if ctx.previous_word == "-r" || ctx.previous_word == "--repeat" { return suggest! {}; // Don't suggest anything } // When the user is typing `-` - if ctx.typing_argument() { - return suggest! { + if ctx.current_word.starts_with('-') { + // Remove arguments that have already been typed by the user + let typed: Vec<&str> = ctx.all_words.iter().map(String::as_str).collect(); + let mut set = suggest! { "-r": "Number of repetitions", "--repeat": "Number of repetitions", + }; + if let Suggest::Suggest(items) = &mut set { + items.retain(|item| !typed.contains(&item.suggest().as_str())); } - // Remove arguments that have already been typed by the user - .strip_typed_argument(ctx); + return set; } // Otherwise, suggest nothing @@ -104,15 +100,16 @@ fn complete_greet_entry(ctx: &ShellContext) -> Suggest { } // --------- IMPORTANT --------- -dispatcher!("greet", CMDGreet => EntryGreet); -pack!(ResultName = (u8, String)); +dispatcher!("greet", EntryGreet); +#[derive(Grouped, Wrap)] +pub struct ResultName((u8, String)); #[chain] fn handle_greet(args: EntryGreet) -> Next { let result: ResultName = args - .pick_or(["-r", "--repeat"], 1) - .pick_or((), "World") - .unpack() + .pick_or(&arg![repeat: u8, 'r'], || 1) + .pick_or(&arg![String], || "World".to_string()) + .unwrap() .into(); result.into() } @@ -120,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-custom-pickable/Cargo.lock b/examples/example-custom-pickable/Cargo.lock deleted file mode 100644 index 11f417a..0000000 --- a/examples/example-custom-pickable/Cargo.lock +++ /dev/null @@ -1,223 +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-custom-pickable" -version = "0.1.0" -dependencies = [ - "mingling", -] - -[[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 = "just_fmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96" - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[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 2.0.118", - "toml", -] - -[[package]] -name = "mingling" -version = "0.4.0" -dependencies = [ - "mingling_core", - "mingling_macros", - "size", -] - -[[package]] -name = "mingling_core" -version = "0.4.0" -dependencies = [ - "just_fmt", - "might_be_async", -] - -[[package]] -name = "mingling_macros" -version = "0.4.0" -dependencies = [ - "just_fmt", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[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.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[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 = "syn" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" -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", -] diff --git a/examples/example-custom-pickable/Cargo.toml b/examples/example-custom-pickable/Cargo.toml deleted file mode 100644 index 9324bd6..0000000 --- a/examples/example-custom-pickable/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "example-custom-pickable" -version = "0.1.0" -edition = "2024" - -[dependencies.mingling] -path = "../../mingling" - -features = ["parser", "extras"] - -[workspace] diff --git a/examples/example-custom-pickable/page.toml b/examples/example-custom-pickable/page.toml deleted file mode 100644 index 1c3bfa0..0000000 --- a/examples/example-custom-pickable/page.toml +++ /dev/null @@ -1,10 +0,0 @@ -[example] -id = "example-custom-pickable" -name = "Custom Pickable" -icon = "🎯" -category = "parsing" -desc = """ -Shows how to implement the `Pickable` trait on custom types for seamless extraction from CLI arguments via Picker. -""" -tags = ["Pickable", "custom"] -files = ["src/main.rs", "Cargo.toml"] diff --git a/examples/example-custom-pickable/src/main.rs b/examples/example-custom-pickable/src/main.rs deleted file mode 100644 index b163278..0000000 --- a/examples/example-custom-pickable/src/main.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Example Custom Pickable -//! -//! > This example demonstrates how to use the Pickable trait to add parsing for your types -//! -//! Run: -//! ```bash -//! cargo run --manifest-path examples/example-custom-pickable/Cargo.toml --quiet -- connect 127.0.0.1:5012 -//! cargo run --manifest-path examples/example-custom-pickable/Cargo.toml --quiet -- connect 127.0.0.1 -//! ``` -//! -//! Output: -//! ```plaintext -//! Connected to "127.0.0.1:5012" -//! Failed to parse address -//! ``` - -use mingling::{macros::route, parser::Pickable, prelude::*, Grouped}; -use std::io::Write; - -// Define types that can be recognized by Mingling -// ________________________ `Pickable` trait needs to implement Default -// / ________ The Grouped derive macro registers an ID for this type -// | / Mingling uses this ID to identify the type -// vvvvvvv vvvvvvv -#[derive(Debug, Default, Clone, Grouped)] -pub struct Address { - pub ip: [u8; 4], - pub port: u16, -} - -// --------- IMPORTANT --------- -impl Pickable for Address { - type Output = Address; - fn pick(args: &mut mingling::parser::Argument, flag: mingling::Flag) -> Option<Self::Output> { - // Extract the raw string from Argument using the Flag - let raw: String = args.pick_argument(flag)?.clone(); - - // Use TryFrom to parse the address - Address::try_from(raw).ok() - } -} -// --------- IMPORTANT --------- - -dispatcher!("connect", CMDConnect => EntryConnect); -pack!(ErrorParseAddressFailed = ()); - -#[chain] -fn handle_connect(prev: EntryConnect) -> Next { - let connect: Address = - route! { prev.pick_or_route((), ErrorParseAddressFailed::default()).unpack() }; - connect.to_chain() -} - -/// Renders the connected address. -#[renderer] -pub fn render_address(addr: Address) -> RenderResult { - let mut render_result = RenderResult::new(); - write!(render_result, "Connected to \"{}\"", addr).ok(); - render_result -} - -/// Renders the error message when address parsing fails. -#[renderer] -pub fn render_error_parse_address_failed(_: ErrorParseAddressFailed) -> RenderResult { - let mut render_result = RenderResult::new(); - write!(render_result, "Failed to parse address").ok(); - render_result -} - -gen_program!(); - -fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDConnect); - program.exec_and_exit(); -} - -// Address conversion - -impl TryFrom<String> for Address { - type Error = String; - - fn try_from(raw: String) -> Result<Self, Self::Error> { - // Expected format: "192.168.1.1:8080" - let parts: Vec<&str> = raw.split(':').collect(); - if parts.len() != 2 { - return Err("Invalid format: expected 'IP:PORT'".to_string()); - } - - let ip_str = parts[0]; - let port_str = parts[1]; - - // Parse IP address (4 octets separated by dots) - let ip_parts: Vec<&str> = ip_str.split('.').collect(); - if ip_parts.len() != 4 { - return Err("Invalid IP address format".to_string()); - } - - let mut ip = [0u8; 4]; - for (i, part) in ip_parts.iter().enumerate() { - ip[i] = part - .parse::<u8>() - .map_err(|_| format!("Invalid IP octet: {part}"))?; - } - - // Parse port - let port = port_str - .parse::<u16>() - .map_err(|_| format!("Invalid port: {port_str}"))?; - - Ok(Address { ip, port }) - } -} - -impl From<Address> for String { - fn from(addr: Address) -> String { - format!( - "{}.{}.{}.{}:{}", - addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port - ) - } -} - -impl std::fmt::Display for Address { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}.{}.{}.{}:{}", - self.ip[0], self.ip[1], self.ip[2], self.ip[3], self.port - ) - } -} diff --git a/examples/example-custom-pickable/test.toml b/examples/example-custom-pickable/test.toml deleted file mode 100644 index 8fc20eb..0000000 --- a/examples/example-custom-pickable/test.toml +++ /dev/null @@ -1,17 +0,0 @@ -[[runs]] -input = [ "connect", "192.168.1.1:8080" ] - -expect.exit-code = 0 -expect.result = "Connected to \"192.168.1.1:8080\"" - -[[runs]] -input = [ "connect" ] - -expect.exit-code = 0 -expect.result = "Failed to parse address" - -[[runs]] -input = [ "connect", "invalid" ] - -expect.exit-code = 0 -expect.result = "Failed to parse address" diff --git a/examples/example-dispatch-tree/Cargo.lock b/examples/example-dispatch-tree/Cargo.lock index 015d536..b80a18e 100644 --- a/examples/example-dispatch-tree/Cargo.lock +++ b/examples/example-dispatch-tree/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-dispatch-tree/src/main.rs b/examples/example-dispatch-tree/src/main.rs index 9f76f15..bba9c2e 100644 --- a/examples/example-dispatch-tree/src/main.rs +++ b/examples/example-dispatch-tree/src/main.rs @@ -3,11 +3,9 @@ //! > This example will introduce how to use `dispatch_tree` //! > to optimize your command line lookup efficiency //! -//! When the number of commands in your project increases, you can use `dispatch_tree` to complete command registration at compile time. -//! It will generate a trie for quickly finding related commands by prefix. -//! -//! Therefore, after enabling this feature, -//! `Program` will no longer store a Dispatcher list internally, and the `with_dispatcher` function will not be compiled. +//! When the number of commands in your project increases, you can enable +//! `dispatch_tree` to switch command matching from a linear scan to a +//! character-level trie. //! //! Run: //! ```bash @@ -25,30 +23,24 @@ use std::io::Write; // --------- IMPORTANT --------- // You have a large number of subcommands -dispatcher!("cmd1", CMD1 => Entry1); -dispatcher!("cmd2.sub1", CMD2Sub1 => Entry2Sub1); -dispatcher!("cmd2.sub2", CMD2Sub2 => Entry2Sub2); -dispatcher!("cmd3.sub1.leaf1", CMD3Sub1Leaf1 => Entry3Sub1Leaf1); -dispatcher!("cmd3.sub1.leaf2", CMD3Sub1Leaf2 => Entry3Sub1Leaf2); -dispatcher!("cmd3.sub2", CMD3Sub2 => Entry3Sub2); -dispatcher!("cmd4.sub1.subsub1.deep", CMD4Deep => Entry4Deep); -dispatcher!("cmd4.sub1.subsub2", CMD4SubSub2 => Entry4SubSub2); -dispatcher!("cmd5", CMD5 => Entry5); -dispatcher!("cmd5.extra", CMD5Extra => Entry5Extra); -dispatcher!("nested.a.b.c", CMDA => EntryA); -dispatcher!("nested.a.b.d", CMDB => EntryB); -dispatcher!("nested.a.e", CMDC => EntryC); -dispatcher!("nested.f", CMDD => EntryD); +dispatcher!("cmd1", Entry1); +dispatcher!("cmd2.sub1", Entry2Sub1); +dispatcher!("cmd2.sub2", Entry2Sub2); +dispatcher!("cmd3.sub1.leaf1", Entry3Sub1Leaf1); +dispatcher!("cmd3.sub1.leaf2", Entry3Sub1Leaf2); +dispatcher!("cmd3.sub2", Entry3Sub2); +dispatcher!("cmd4.sub1.subsub1.deep", Entry4Deep); +dispatcher!("cmd4.sub1.subsub2", Entry4SubSub2); +dispatcher!("cmd5", Entry5); +dispatcher!("cmd5.extra", Entry5Extra); +dispatcher!("nested.a.b.c", EntryA); +dispatcher!("nested.a.b.d", EntryB); +dispatcher!("nested.a.e", EntryC); +dispatcher!("nested.f", EntryD); // --------- IMPORTANT --------- fn main() { let program = ThisProgram::new(); - - // --------- IMPORTANT --------- - // // You no longer need to use `with_dispatcher` anymore; - // // it'll be collected automatically once the `dispatch_tree` feature is enabled - // program.with_dispatcher(...); - program.exec_and_exit(); } diff --git a/examples/example-enum-tag/Cargo.lock b/examples/example-enum-tag/Cargo.lock index b9de59b..492d708 100644 --- a/examples/example-enum-tag/Cargo.lock +++ b/examples/example-enum-tag/Cargo.lock @@ -3,6 +3,23 @@ version = 4 [[package]] +name = "arg-picker" +version = "0.2.0" +dependencies = [ + "arg-picker-macros", + "just_fmt 0.2.0", +] + +[[package]] +name = "arg-picker-macros" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -84,16 +101,16 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "arg-picker", "mingling_core", "mingling_macros", - "size", ] [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt 0.2.0", "just_template", @@ -102,7 +119,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt 0.2.0", "proc-macro2", @@ -167,12 +184,6 @@ dependencies = [ ] [[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[package]] name = "syn" version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-enum-tag/Cargo.toml b/examples/example-enum-tag/Cargo.toml index 9c0a0ff..489f401 100644 --- a/examples/example-enum-tag/Cargo.toml +++ b/examples/example-enum-tag/Cargo.toml @@ -8,7 +8,7 @@ path = "../../mingling" features = [ "comp", - "parser" + "picker" ] [workspace] diff --git a/examples/example-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs index b57511d..63ac3ba 100644 --- a/examples/example-enum-tag/src/main.rs +++ b/examples/example-enum-tag/src/main.rs @@ -16,8 +16,10 @@ //! ``` use mingling::{ - macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Grouped, ShellContext, - Suggest, + EnumTag, Grouped, ShellContext, Suggest, + macros::suggest_enum, + picker::{PickerArgResult, SinglePickable}, + prelude::*, }; use std::io::Write; @@ -57,9 +59,7 @@ pub enum ProgrammingLanguages { #[enum_desc("A general-purpose programming language with clean syntax, known for readability")] Python, - #[enum_desc( - "An object-oriented scripting language, famous for its concise and elegant syntax" - )] + #[enum_desc("An object-oriented scripting language, famous for its concise and elegant syntax")] Ruby, #[default] @@ -68,17 +68,39 @@ pub enum ProgrammingLanguages { } // --------- IMPORTANT --------- -// Implement the PickableEnum trait for ProgrammingLanguages, -// so that `Picker` can parse this enum -impl PickableEnum for ProgrammingLanguages {} +// NOTE: Due to the migration from the legacy `parser` to `picker`, the `EnumTag` -> `Picker` path +// is not yet complete, so a manual implementation is used for now. +// Once that path is complete, `#[derive(EnumTag)]` can automatically implement `SinglePickable`, +// replacing this manual implementation. +impl SinglePickable for ProgrammingLanguages { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + let Some(str) = str else { + return PickerArgResult::NotFound; + }; + let lang = match str.to_lowercase().as_str() { + "c" => Self::C, + "c++" | "cpp" => Self::CPlusPlus, + "c#" | "csharp" => Self::Csharp, + "java" => Self::Java, + "javascript" | "js" => Self::JavaScript, + "kotlin" => Self::Kotlin, + "ocaml" => Self::OCaml, + "python" => Self::Python, + "ruby" => Self::Ruby, + "rust" => Self::Rust, + _ => return PickerArgResult::NotFound, + }; + PickerArgResult::Parsed(lang) + } +} // --------- IMPORTANT --------- -dispatcher!("lang-select", CMDLanguageSelection => EntryLanguageSelection); +dispatcher!("lang-select", EntryLanguageSelection); #[chain] fn handle_language_selection(args: EntryLanguageSelection) -> Next { // You can use Picker to directly parse ProgrammingLanguages - let lang: ProgrammingLanguages = args.pick(()).unpack(); + let lang: ProgrammingLanguages = args.pick_or_default(&arg![ProgrammingLanguages]).unwrap(); lang.into() } @@ -92,7 +114,7 @@ pub fn render_programming_language(lang: ProgrammingLanguages) -> RenderResult { } #[completion(EntryLanguageSelection)] -fn complete_language_selection(_: &ShellContext) -> Suggest { +fn complete_language_selection(_: ShellContext) -> Suggest { // Use `suggest_enum!` directly to generate enum suggestions suggest_enum!(ProgrammingLanguages) } @@ -100,8 +122,5 @@ fn complete_language_selection(_: &ShellContext) -> Suggest { gen_program!(); fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDCompletion); - program.with_dispatcher(CMDLanguageSelection); - program.exec_and_exit(); + ThisProgram::new().exec_and_exit(); } diff --git a/examples/example-error-handling/Cargo.lock b/examples/example-error-handling/Cargo.lock index f03ca4d..3110ae9 100644 --- a/examples/example-error-handling/Cargo.lock +++ b/examples/example-error-handling/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-error-handling/src/main.rs b/examples/example-error-handling/src/main.rs index 0cd973a..ec7e6c9 100644 --- a/examples/example-error-handling/src/main.rs +++ b/examples/example-error-handling/src/main.rs @@ -26,38 +26,44 @@ use std::io::Write; // In Mingling, instead of using ? to propagate errors upward, // errors are treated as branches that continue execution. -dispatcher!("hello", CMDHello => EntryHello); +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,19 +102,12 @@ 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 } gen_program!(); fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDHello); - program.exec_and_exit(); + ThisProgram::new().exec_and_exit(); } diff --git a/examples/example-exitcode/Cargo.lock b/examples/example-exitcode/Cargo.lock index 2924262..37170f2 100644 --- a/examples/example-exitcode/Cargo.lock +++ b/examples/example-exitcode/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-exitcode/src/main.rs b/examples/example-exitcode/src/main.rs index 6b22eae..c7f731d 100644 --- a/examples/example-exitcode/src/main.rs +++ b/examples/example-exitcode/src/main.rs @@ -31,24 +31,26 @@ fn main() { program.with_setup(ExitCodeSetup::default()); // --------- IMPORTANT --------- - program.with_dispatcher(CMDHello); program.exec_and_exit(); } -dispatcher!("hello", CMDHello => EntryHello); +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-help/Cargo.lock b/examples/example-help/Cargo.lock index 5a482a5..9c39a41 100644 --- a/examples/example-help/Cargo.lock +++ b/examples/example-help/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-help/src/main.rs b/examples/example-help/src/main.rs index 3282683..75170c7 100644 --- a/examples/example-help/src/main.rs +++ b/examples/example-help/src/main.rs @@ -16,7 +16,7 @@ use mingling::{macros::help, prelude::*, setup::BasicProgramSetup}; use std::io::Write; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); // Define help _________ When `program.user_context.help` is `true` // / the command will not enter `#[chain]` / `#[renderer]` @@ -36,8 +36,6 @@ fn main() { program.with_setup(BasicProgramSetup); // --------- IMPORTANT --------- - program.with_dispatcher(CMDGreet); - program.exec_and_exit(); } diff --git a/examples/example-hook/Cargo.lock b/examples/example-hook/Cargo.lock index 588e6ef..e4d68ce 100644 --- a/examples/example-hook/Cargo.lock +++ b/examples/example-hook/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs index da92045..721cea0 100644 --- a/examples/example-hook/src/main.rs +++ b/examples/example-hook/src/main.rs @@ -26,7 +26,7 @@ use mingling::{ }; use std::io::Write; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); fn main() { let mut program = ThisProgram::new(); @@ -50,16 +50,16 @@ fn main() { ); // --------- IMPORTANT --------- - program.with_dispatcher(CMDGreet); 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-implicit-dispatcher/Cargo.lock b/examples/example-implicit-dispatcher/Cargo.lock index f9f8838..9ca0f10 100644 --- a/examples/example-implicit-dispatcher/Cargo.lock +++ b/examples/example-implicit-dispatcher/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-implicit-dispatcher/src/main.rs b/examples/example-implicit-dispatcher/src/main.rs index 340d585..89a5ec3 100644 --- a/examples/example-implicit-dispatcher/src/main.rs +++ b/examples/example-implicit-dispatcher/src/main.rs @@ -4,20 +4,13 @@ use mingling::prelude::*; -// When using implicit syntax, the entry and dispatcher names will be automatically derived -dispatcher!("remote.add" /*, CMDRemoteAdd => EntryRemoteAdd */); -dispatcher!("remote.remove", CMDRemoteRemove => EntryRemoteRemove); +// When using implicit syntax, the entry name will be automatically derived +// from the command name (the dispatcher struct is generated internally) +dispatcher!("remote.add" /* => EntryRemoteAdd */); +dispatcher!("remote.remove", EntryRemoteRemove); fn main() { - let mut program = ThisProgram::new(); - - // --------- IMPORTANT --------- - program.with_dispatcher(CMDRemoteAdd); - // ^^^^^^^^^^^^\_ CMDRemoteAdd is implicitly created - // --------- IMPORTANT --------- - - program.with_dispatcher(CMDRemoteRemove); - program.exec_and_exit(); + ThisProgram::new().exec_and_exit(); } gen_program!(); diff --git a/examples/example-lazy-resources/Cargo.lock b/examples/example-lazy-resources/Cargo.lock index 0786373..21cb251 100644 --- a/examples/example-lazy-resources/Cargo.lock +++ b/examples/example-lazy-resources/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-lazy-resources/src/main.rs b/examples/example-lazy-resources/src/main.rs index 7dda658..2243f14 100644 --- a/examples/example-lazy-resources/src/main.rs +++ b/examples/example-lazy-resources/src/main.rs @@ -49,10 +49,11 @@ fn init_res_large_data() -> ResLargeData { ResLargeData { data } } -dispatcher!("show", CMDShow => EntryShow); -dispatcher!("none", CMDNone => EntryNone); +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(); @@ -64,7 +65,6 @@ fn main() { program.with_resource(ResLargeData::lazy_init(init_res_large_data)); // --------- IMPORTANT --------- - program.with_dispatcher(CMDShow).with_dispatcher(CMDNone); program.exec_and_exit(); } diff --git a/examples/example-metadata/Cargo.lock b/examples/example-metadata/Cargo.lock index 8517b6a..bfa1d01 100644 --- a/examples/example-metadata/Cargo.lock +++ b/examples/example-metadata/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-metadata/src/main.rs b/examples/example-metadata/src/main.rs index 855241e..99a8aae 100644 --- a/examples/example-metadata/src/main.rs +++ b/examples/example-metadata/src/main.rs @@ -25,20 +25,16 @@ use mingling::{macros::metadata, prelude::*}; use std::io::Write; // Define the `greet` subcommand -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); // Define the `desc` subcommand, which queries metadata bound to EntryGreet -dispatcher!("desc", CMDDescription => EntryDescription); +dispatcher!("desc", EntryDescription); // Define the `nodoc` subcommand, which queries metadata for an entry that has none -dispatcher!("nodoc", CMDNoDescription => EntryNoDescription); +dispatcher!("nodoc", EntryNoDescription); fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); - program.with_dispatcher(CMDDescription); - program.with_dispatcher(CMDNoDescription); - program.exec_and_exit(); + ThisProgram::new().exec_and_exit(); } /// The metadata type attached to an entry. @@ -60,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()) @@ -85,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. @@ -98,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/Cargo.lock b/examples/example-outside-type/Cargo.lock index a12fc80..49c8c6f 100644 --- a/examples/example-outside-type/Cargo.lock +++ b/examples/example-outside-type/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-outside-type/src/main.rs b/examples/example-outside-type/src/main.rs index 3159d19..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(), } } @@ -90,10 +91,7 @@ fn render_error_io(err: ErrorIo) -> RenderResult { } fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDParse); - program.with_dispatcher(CMDError); - program.exec_and_exit(); + ThisProgram::new().exec_and_exit(); } gen_program!(); diff --git a/examples/example-pack-err/Cargo.lock b/examples/example-pack-err/Cargo.lock deleted file mode 100644 index 1bc9022..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.4.0" -dependencies = [ - "mingling_core", - "mingling_macros", - "serde", -] - -[[package]] -name = "mingling_core" -version = "0.4.0" -dependencies = [ - "just_fmt", - "might_be_async", - "serde", - "serde_json", -] - -[[package]] -name = "mingling_macros" -version = "0.4.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 5bf5066..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", CMDFind => EntryFind); -dispatcher!("find-structural", CMDFindStructural => 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); - program.with_dispatcher(CMDFind); - program.with_dispatcher(CMDFindStructural); - 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/Cargo.lock b/examples/example-panic-unwind/Cargo.lock index 6481eb4..20671f5 100644 --- a/examples/example-panic-unwind/Cargo.lock +++ b/examples/example-panic-unwind/Cargo.lock @@ -3,6 +3,23 @@ version = 4 [[package]] +name = "arg-picker" +version = "0.2.0" +dependencies = [ + "arg-picker-macros", + "just_fmt", +] + +[[package]] +name = "arg-picker-macros" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -57,16 +74,16 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "arg-picker", "mingling_core", "mingling_macros", - "size", ] [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -74,7 +91,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", @@ -139,12 +156,6 @@ dependencies = [ ] [[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[package]] name = "syn" version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-panic-unwind/Cargo.toml b/examples/example-panic-unwind/Cargo.toml index 559bdb9..463a288 100644 --- a/examples/example-panic-unwind/Cargo.toml +++ b/examples/example-panic-unwind/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies.mingling] path = "../../mingling" -features = ["parser"] +features = ["picker"] # Enable panic unwinding in release builds [profile.release] diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs index 2c432eb..f91b0f0 100644 --- a/examples/example-panic-unwind/src/main.rs +++ b/examples/example-panic-unwind/src/main.rs @@ -19,12 +19,13 @@ use mingling::config::PanicSilence; use mingling::{hook::ProgramHook, prelude::*}; use std::io::Write; -dispatcher!("panic", CMDPanic => EntryPanic); -pack!(NotPanic = ()); +dispatcher!("panic", EntryPanic); + +#[derive(Grouped)] +pub struct NotPanic; fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDPanic); // --------- IMPORTANT --------- // Enable silence_panic to suppress automatic Panic output @@ -42,13 +43,13 @@ fn main() { #[chain] fn handle_panic(prev: EntryPanic) -> Next { - let panic_info = prev.pick::<Option<String>>(()).unpack(); + let panic_info = prev.pick_or_default(&arg![Option<String>]).unwrap(); match panic_info { Some(s) => { // Panic happens here, will be caught panic!("{}", s) } - None => NotPanic::default().into(), + None => NotPanic.into(), } } diff --git a/examples/example-pathfinder/Cargo.lock b/examples/example-pathfinder/Cargo.lock index e00aa91..b93e443 100644 --- a/examples/example-pathfinder/Cargo.lock +++ b/examples/example-pathfinder/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -74,7 +74,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "mingling_pathf" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-pathfinder/src/main.rs b/examples/example-pathfinder/src/main.rs index 0f93a8d..cd7cc7d 100644 --- a/examples/example-pathfinder/src/main.rs +++ b/examples/example-pathfinder/src/main.rs @@ -19,12 +19,9 @@ mod sub; use mingling::macros::gen_program; -use crate::sub::CMDGreet; fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); - program.exec_and_exit(); + ThisProgram::new().exec_and_exit(); } gen_program!(); diff --git a/examples/example-pathfinder/src/sub/mod.rs b/examples/example-pathfinder/src/sub/mod.rs index 8cbc1c5..b90c618 100644 --- a/examples/example-pathfinder/src/sub/mod.rs +++ b/examples/example-pathfinder/src/sub/mod.rs @@ -2,13 +2,15 @@ use crate::Next; use mingling::prelude::*; use std::io::Write; -dispatcher!("greet", CMDGreet => EntryGreet); -pack!(ResultName = String); +dispatcher!("greet", EntryGreet); + +#[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/Cargo.lock b/examples/example-repl-basic/Cargo.lock index d4d5bd9..9764a85 100644 --- a/examples/example-repl-basic/Cargo.lock +++ b/examples/example-repl-basic/Cargo.lock @@ -3,6 +3,23 @@ version = 4 [[package]] +name = "arg-picker" +version = "0.2.0" +dependencies = [ + "arg-picker-macros", + "just_fmt 0.2.0", +] + +[[package]] +name = "arg-picker-macros" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -64,16 +81,16 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "arg-picker", "mingling_core", "mingling_macros", - "size", ] [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt 0.2.0", "might_be_async", @@ -81,7 +98,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt 0.2.0", "proc-macro2", @@ -146,12 +163,6 @@ dependencies = [ ] [[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[package]] name = "syn" version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-repl-basic/Cargo.toml b/examples/example-repl-basic/Cargo.toml index 3289939..7f2e463 100644 --- a/examples/example-repl-basic/Cargo.toml +++ b/examples/example-repl-basic/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies.mingling] path = "../../mingling" -features = ["repl", "parser", "extras"] +features = ["repl", "picker", "extras"] [dependencies] just_fmt = "0.1.2" diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs index 361488d..9b81ced 100644 --- a/examples/example-repl-basic/src/main.rs +++ b/examples/example-repl-basic/src/main.rs @@ -37,12 +37,6 @@ fn main() { // Resource program.with_resource(ResCurrentDir::default()); - // Dispatchers - program.with_dispatcher(CMDCd); - program.with_dispatcher(CMDLs); - program.with_dispatcher(CMDExit); - program.with_dispatcher(CMDClear); - // Setups // Enable basic std::io::stdin().read_line(&mut input) program.with_setup(BasicREPLReadlineSetup); @@ -76,25 +70,28 @@ fn main() { } // Create error route -pack!(ErrorDirectoryNotExist = PathBuf); +#[derive(Grouped, Wrap)] +pub struct ErrorDirectoryNotExist(PathBuf); // Create commands: cd ls exit -dispatcher!("cd", CMDCd => EntryCd); -dispatcher!("ls", CMDLs => EntryLs); -dispatcher!("exit", CMDExit => EntryExit); -dispatcher!("clear", CMDClear => EntryClear); +dispatcher!("cd", EntryCd); +dispatcher!("ls", EntryLs); +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(()).unpack(); - StateChangeDirectory::new(join).into() + let join = prev.pick_or_default(&arg![String]).unwrap(); + StateChangeDirectory(join).into() } // Execute directory change @@ -102,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; @@ -132,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 @@ -166,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-resources/Cargo.lock b/examples/example-resources/Cargo.lock index a559bdb..bfc04f0 100644 --- a/examples/example-resources/Cargo.lock +++ b/examples/example-resources/Cargo.lock @@ -3,6 +3,23 @@ version = 4 [[package]] +name = "arg-picker" +version = "0.2.0" +dependencies = [ + "arg-picker-macros", + "just_fmt", +] + +[[package]] +name = "arg-picker-macros" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -57,16 +74,16 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "arg-picker", "mingling_core", "mingling_macros", - "size", ] [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -74,7 +91,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", @@ -139,12 +156,6 @@ dependencies = [ ] [[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[package]] name = "syn" version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-resources/Cargo.toml b/examples/example-resources/Cargo.toml index faf24e4..72a530e 100644 --- a/examples/example-resources/Cargo.toml +++ b/examples/example-resources/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" [dependencies.mingling] path = "../../mingling" -features = ["parser"] +features = ["picker"] [workspace] diff --git a/examples/example-resources/src/main.rs b/examples/example-resources/src/main.rs index 38e4561..4d792b3 100644 --- a/examples/example-resources/src/main.rs +++ b/examples/example-resources/src/main.rs @@ -37,14 +37,11 @@ fn main() { }); // --------- IMPORTANT --------- - program - .with_dispatcher(CMDCurrent) - .with_dispatcher(CMDModifyCurrent); program.exec_and_exit(); } -dispatcher!("current", CMDCurrent => EntryCurrent); -dispatcher!("modify-current", CMDModifyCurrent => EntryModifyCurrent); +dispatcher!("current", EntryCurrent); +dispatcher!("modify-current", EntryModifyCurrent); // Define chain for modifying current directory _________________ Injected muttable resource // / @@ -52,7 +49,7 @@ dispatcher!("modify-current", CMDModifyCurrent => EntryModifyCurrent); fn render_modify_current(args: EntryModifyCurrent, current_dir: &mut ResCurrentDir) -> Next { current_dir.current_dir = current_dir .current_dir - .join(args.pick::<String>(()).unpack()); + .join(args.pick_or_default(&arg![String]).unwrap()); EntryCurrent::default().into() } diff --git a/examples/example-setup/Cargo.lock b/examples/example-setup/Cargo.lock index 47d36e5..c8e0e34 100644 --- a/examples/example-setup/Cargo.lock +++ b/examples/example-setup/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-setup/src/main.rs b/examples/example-setup/src/main.rs index bf610ca..59c503f 100644 --- a/examples/example-setup/src/main.rs +++ b/examples/example-setup/src/main.rs @@ -1,8 +1,28 @@ //! Example Setup //! -//! > This example demonstrates how to build a custom Setup for modular management of project components +//! > This example demonstrates how to build a custom Setup that encapsulates a +//! > group of related resources and registers them with `with_resource`. use mingling::{Program, macros::program_setup, prelude::*}; +use std::io::Write; + +// A group of related resources — here, the demo app's identity. +// Resource types are plain structs: any `Default + Clone + Send + Sync` type +// can be used as a resource, and it is identified by its type. +#[derive(Default, Clone)] +struct ResAppName { + name: String, +} + +#[derive(Default, Clone)] +struct ResAppVersion { + version: String, +} + +#[derive(Default, Clone)] +struct ResGreetingPrefix { + prefix: String, +} fn main() { let mut program = ThisProgram::new(); @@ -17,21 +37,45 @@ fn main() { // --------- IMPORTANT --------- // Define `CustomSetup` (inferred from `custom_setup`) -// Package part of the program construction logic into this type for modular management +// Package part of the program construction logic into this type for modular +// management — e.g. register a group of related resources here. #[program_setup] fn custom_setup(program: &mut Program<ThisProgram>) { - program.with_dispatcher(CMD1); - program.with_dispatcher(CMD2); - program.with_dispatcher(CMD3); - program.with_dispatcher(CMD4); - program.with_dispatcher(CMD5); + program.with_resource(ResAppName { + name: "mingling".to_string(), + }); + program.with_resource(ResAppVersion { + version: "0.5.0".to_string(), + }); + program.with_resource(ResGreetingPrefix { + prefix: "Hello".to_string(), + }); } // --------- IMPORTANT --------- -dispatcher!("1", CMD1 => Entry1); -dispatcher!("2", CMD2 => Entry2); -dispatcher!("3", CMD3 => Entry3); -dispatcher!("4", CMD4 => Entry4); -dispatcher!("5", CMD5 => Entry5); +dispatcher!("greet", EntryGreet); + +#[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 + .0 + .first() + .cloned() + .unwrap_or_else(|| "World".to_string()); + let greeting: ResultGreeting = format!("{} from {} v{}", who, app.name, version.version).into(); + greeting.into() +} + +/// Renderer: injects the `ResGreetingPrefix` resource to decorate the output. +#[renderer] +fn render_greet(greeting: ResultGreeting, prefix: &ResGreetingPrefix) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "{}, {}!", prefix.prefix, *greeting).ok(); + render_result +} gen_program!(); diff --git a/examples/example-setup/test.toml b/examples/example-setup/test.toml index 811a108..c9b7ed8 100644 --- a/examples/example-setup/test.toml +++ b/examples/example-setup/test.toml @@ -1,29 +1,11 @@ [[runs]] -input = [ "1" ] +input = [ "greet" ] expect.exit-code = 0 -expect.result = "" +expect.result = "Hello, World from mingling v0.5.0!" [[runs]] -input = [ "2" ] +input = [ "greet", "Alice" ] expect.exit-code = 0 -expect.result = "" - -[[runs]] -input = [ "3" ] - -expect.exit-code = 0 -expect.result = "" - -[[runs]] -input = [ "4" ] - -expect.exit-code = 0 -expect.result = "" - -[[runs]] -input = [ "5" ] - -expect.exit-code = 0 -expect.result = "" +expect.result = "Hello, Alice from mingling v0.5.0!" diff --git a/examples/example-structural-renderer/Cargo.lock b/examples/example-structural-renderer/Cargo.lock index c3f24c7..cf2cfe3 100644 --- a/examples/example-structural-renderer/Cargo.lock +++ b/examples/example-structural-renderer/Cargo.lock @@ -3,6 +3,23 @@ version = 4 [[package]] +name = "arg-picker" +version = "0.2.0" +dependencies = [ + "arg-picker-macros", + "just_fmt", +] + +[[package]] +name = "arg-picker-macros" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -64,17 +81,17 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "arg-picker", "mingling_core", "mingling_macros", "serde", - "size", ] [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -85,7 +102,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", @@ -183,12 +200,6 @@ dependencies = [ ] [[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[package]] name = "syn" version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-structural-renderer/Cargo.toml b/examples/example-structural-renderer/Cargo.toml index e29e9c3..2851303 100644 --- a/examples/example-structural-renderer/Cargo.toml +++ b/examples/example-structural-renderer/Cargo.toml @@ -11,7 +11,7 @@ path = "../../mingling" features = [ "structural_renderer", "yaml_serde_fmt", - "parser", + "picker", ] [workspace] diff --git a/examples/example-structural-renderer/src/main.rs b/examples/example-structural-renderer/src/main.rs index 070e75d..c1cfd69 100644 --- a/examples/example-structural-renderer/src/main.rs +++ b/examples/example-structural-renderer/src/main.rs @@ -17,23 +17,23 @@ //! member_age: 22 //! ``` -use mingling::prelude::*; -use mingling::{parser::Picker, setup::StructuralRendererSetup, Grouped, StructuralData}; +use mingling::setup::picker::StructuralRendererSetup; +use mingling::{Grouped, StructuralData, prelude::*}; use serde::Serialize; use std::io::Write; -dispatcher!("render", CMDRender => EntryRender); +dispatcher!("render", EntryRender); fn main() { let mut program = ThisProgram::new(); // Add `StructuralRendererSetup` to receive user input `--json` `--yaml` parameters program.with_setup(StructuralRendererSetup); - program.with_dispatcher(CMDRender); let _ = program.exec(); } // --------- 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 @@ -49,17 +49,17 @@ 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 --------- #[chain] fn parse_render(prev: EntryRender) -> Next { - let (name, age) = Picker::new(prev.inner) - .pick::<String>(()) - .pick::<i32>(()) - .unpack(); + let (name, age) = prev + .pick_or_default(&arg![String]) + .pick_or_default(&arg![i32]) + .unwrap(); Info { name, age }.to_render() } diff --git a/examples/example-unit-test/Cargo.lock b/examples/example-unit-test/Cargo.lock index a1b4e59..455fa9d 100644 --- a/examples/example-unit-test/Cargo.lock +++ b/examples/example-unit-test/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ "mingling_core", "mingling_macros", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", diff --git a/examples/example-unit-test/src/main.rs b/examples/example-unit-test/src/main.rs index 33ddde0..e4c6504 100644 --- a/examples/example-unit-test/src/main.rs +++ b/examples/example-unit-test/src/main.rs @@ -36,60 +36,66 @@ 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 --------- } -dispatcher!("hello", CMDHello => EntryHello); +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,19 +134,12 @@ 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 } gen_program!(); fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDHello); - program.exec_and_exit(); + ThisProgram::new().exec_and_exit(); } diff --git a/examples/full-todolist/Cargo.lock b/examples/full-todolist/Cargo.lock index 28d551b..ad9f475 100644 --- a/examples/full-todolist/Cargo.lock +++ b/examples/full-todolist/Cargo.lock @@ -3,6 +3,23 @@ version = 4 [[package]] +name = "arg-picker" +version = "0.2.0" +dependencies = [ + "arg-picker-macros", + "just_fmt", +] + +[[package]] +name = "arg-picker-macros" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -65,17 +82,17 @@ dependencies = [ [[package]] name = "mingling" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "arg-picker", "mingling_core", "mingling_macros", "serde", - "size", ] [[package]] name = "mingling_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "might_be_async", @@ -85,7 +102,7 @@ dependencies = [ [[package]] name = "mingling_macros" -version = "0.4.0" +version = "0.5.0" dependencies = [ "just_fmt", "proc-macro2", @@ -164,12 +181,6 @@ dependencies = [ ] [[package]] -name = "size" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" - -[[package]] name = "syn" version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/full-todolist/Cargo.toml b/examples/full-todolist/Cargo.toml index 9d6f1a7..fb3d3be 100644 --- a/examples/full-todolist/Cargo.toml +++ b/examples/full-todolist/Cargo.toml @@ -10,7 +10,7 @@ serde_json = "1.0.150" [dependencies.mingling] path = "../../mingling" features = [ - "parser", + "picker", "extras", "structural_renderer", ] diff --git a/examples/full-todolist/src/main.rs b/examples/full-todolist/src/main.rs index 0748832..9c7bfe8 100644 --- a/examples/full-todolist/src/main.rs +++ b/examples/full-todolist/src/main.rs @@ -9,9 +9,10 @@ use mingling::{ LazyInit, LazyRes, macros::route, + picker::value::Flag, prelude::*, res::ResExitCode, - setup::{ExitCodeSetup, HelpFlagSetup, StructuralRendererSetup}, + setup::{ExitCodeSetup, picker::HelpFlagSetup, picker::StructuralRendererSetup}, }; use std::io::Write; @@ -35,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(); @@ -51,7 +62,7 @@ fn main() { // Setups program.with_setup(ExitCodeSetup::default()); program.with_setup(StructuralRendererSetup); - program.with_setup(HelpFlagSetup::new(["--help", "-h"])); + program.with_setup(HelpFlagSetup::new(&arg![help: Flag, 'h'])); // Flags let all = program.pick_global_flag(["-A", "--all"]); @@ -65,12 +76,7 @@ fn main() { ); program.with_resource(ResProgramFlags { all }); - // Dispatchers - program.with_dispatcher(CMDAdd); - program.with_dispatcher(CMDComplete); - program.with_dispatcher(CMDList); - program.with_dispatcher(CMDClean); - + // Execute program.exec_and_exit(); } @@ -79,11 +85,12 @@ fn main() { #[chain] fn handle_add(args: EntryAdd) -> Next { let task: String = route! { - args - .pick_or_route((), ErrorNoTaskDescriptionProvided::new(())) - .unpack() + args.pick_or_route(&arg![String], || { + ErrorNoTaskDescriptionProvided.to_chain() + }) + .to_result() }; - StateAddTodo::new(task).to_chain() + StateAddTodo(task).to_chain() } #[chain] @@ -95,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, @@ -120,9 +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((), ErrorNoIndexProvided::new(())).unpack() + args.pick_or_route(&arg![i32], || ErrorNoIndexProvided.to_chain()) + .to_result() }; - StateCompleteTodo::new(index).to_chain() + StateCompleteTodo(index).to_chain() } #[chain] @@ -131,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() } } |
