diff options
Diffstat (limited to 'mingling/src/example_docs.rs')
| -rw-r--r-- | mingling/src/example_docs.rs | 1174 |
1 files changed, 348 insertions, 826 deletions
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index c292598..055615d 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -1,129 +1,5 @@ // Auto generated -/// 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 -/// ``` -/// -/// Source code (./Cargo.toml) -/// ```toml -/// [package] -/// name = "example-argument-parse" -/// version = "0.1.0" -/// edition = "2024" -/// -/// [dependencies.mingling] -/// path = "../../mingling" -/// -/// # Enable `parser` features -/// features = ["parser", "extras"] -/// -/// [workspace] -/// ``` -/// -/// Source code (./src/main.rs) -/// ```ignore -/// 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(); -/// } -/// ``` -pub mod example_argument_parse {} /// Example Argument Picker /// /// > Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line. @@ -186,19 +62,34 @@ pub mod example_argument_parse {} /// /// // --------- IMPORTANT --------- /// -/// dispatcher!("calc", CMDCalculate => EntryCalculate); +/// dispatcher!("calc", EntryCalculate); +/// +/// #[derive(Grouped, Default)] +/// pub struct ErrorNumberANotProvided; +/// +/// #[derive(Grouped, Default)] +/// pub struct ErrorNumberBNotProvided; /// -/// pack_err!(ErrorNumberANotProvided); -/// pack_err!(ErrorNumberBNotProvided); -/// pack_err!(ErrorNumberOperatorNotProvided); -/// pack_err!(ErrorDivisionByZero); +/// #[derive(Grouped, Default)] +/// pub struct ErrorNumberOperatorNotProvided; /// -/// pack!(StateAdd = (f32, f32)); -/// pack!(StateSubtract = (f32, f32)); -/// pack!(StateMultiply = (f32, f32)); -/// pack!(StateDivide = (f32, f32)); +/// #[derive(Grouped, Default)] +/// pub struct ErrorDivisionByZero; /// -/// pack!(ResultNumber = f32); +/// #[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 { @@ -269,7 +160,6 @@ pub mod example_argument_parse {} /// program.with_resource(ResNumberDisplaySetting { round: *round }); /// // --------- IMPORTANT --------- /// -/// program.with_dispatcher(CMDCalculate); /// program.exec_and_exit(); /// } /// @@ -280,13 +170,13 @@ pub mod example_argument_parse {} /// // 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() @@ -294,7 +184,7 @@ pub mod example_argument_parse {} /// // --------- IMPORTANT --------- /// /// if operator == Operator::Slash && number_b == 0. { -/// return ErrorDivisionByZero::default().to_chain(); +/// return ErrorDivisionByZero.to_chain(); /// } /// /// StateCalculate { @@ -308,41 +198,41 @@ pub mod example_argument_parse {} /// #[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) /// } /// @@ -404,8 +294,8 @@ pub mod example_argument_picker {} /// [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] @@ -424,8 +314,6 @@ pub mod example_argument_picker {} /// 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"))); /// @@ -435,15 +323,16 @@ pub mod example_argument_picker {} /// // --------- 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() /// } /// @@ -461,7 +350,7 @@ pub mod example_argument_picker {} /// /// 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) /// } /// ``` pub mod example_async_support {} @@ -499,40 +388,38 @@ pub mod example_async_support {} /// 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()) @@ -645,7 +532,6 @@ pub mod example_basic {} /// // Capture Clap's help information and write to RenderResult /// // --------- IMPORTANT --------- /// -/// program.with_dispatcher(CMDGreet); /// program.exec_and_exit(); /// } /// @@ -657,7 +543,7 @@ pub mod example_basic {} /// // 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, @@ -709,11 +595,13 @@ pub mod example_clap_binding {} /// > Types are defined in a submodule (`sub`), and `gen_program!()` resolves /// > them automatically via pathf without explicit `use` imports. /// > -/// > **Important**: `dispatch_tree` must be enabled in BOTH `[dependencies]` -/// > AND `[build-dependencies]` so that pathf's builder can detect -/// > `__internal_dispatcher_*` types needed by the dispatch tree. +/// > **Important**: `dispatch_tree` must be enabled so that pathf's builder can +/// > detect `__internal_dispatcher_*` types needed by the dispatch tree. /// > /// > Also requires `extras` for the implicit `dispatcher!("hello")` form. +/// > +/// > With the `pathf` feature, `gen_program!()` automatically invokes +/// > `build_pathf!()` at compile time — no `build.rs` needed. /// /// Run: /// ```bash @@ -739,19 +627,6 @@ pub mod example_clap_binding {} /// "pathf", /// ] } /// -/// [build-dependencies] -/// mingling = { path = "../../mingling", features = [ -/// "builds", -/// -/// # --------- IMPORTANT --------- -/// # To use pathf under dispatch_tree -/// # **must** enable the `dispatch_tree` -/// # feature in build dependencies -/// "dispatch_tree", -/// "pathf", -/// # --------- IMPORTANT --------- -/// ] } -/// /// [workspace] /// ``` /// @@ -805,15 +680,6 @@ pub mod example_combine_pathf_dispatch_tree {} /// "pathf", /// ] /// -/// [build-dependencies.mingling] -/// path = "../../mingling" -/// features = [ -/// # Enable the `build` feature for build-time support -/// "build", -/// # `pathf` must also be enabled in build-dependencies -/// "pathf", -/// ] -/// /// [workspace] /// ``` /// @@ -824,10 +690,7 @@ pub mod example_combine_pathf_dispatch_tree {} /// 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!(); @@ -876,37 +739,33 @@ pub mod example_combine_pathf_metadata {} /// 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 --------- /// @@ -932,30 +791,15 @@ pub mod example_command_macro {} /// To make your completions work, you need to generate a completion script using Mingling's tools /// /// 1. Enable features -/// You need to enable the `build` and `comp` features for `mingling` in `[build-dependencies]` -/// -/// 2. Write `build.rs` -/// Write the following in `build.rs` -/// -/// ```rust,ignore -/// fn main() { -/// build_scripts(); -/// } -/// -/// /// Generate completion scripts -/// fn build_scripts() { -/// // `env!("CARGO_PKG_NAME")` equals the crate name, which matches the binary name. -/// // If your binary name differs from the crate name, specify it explicitly. -/// mingling::build::build_comp_scripts( -/// // Your binary name: -/// env!("CARGO_PKG_NAME"), -/// ) -/// .unwrap(); -/// } -/// ``` +/// Enable the `comp` feature for `mingling` in `[dependencies]` +/// +/// 2. Generate completion scripts +/// When the `comp` feature is enabled, `gen_program!()` automatically invokes +/// `build_comp!()` at compile time, which generates the completion scripts +/// (named after `CARGO_PKG_NAME`) into `target/mingling/`. /// /// 3. Verify -/// Build your project with `cargo build --release`. The completion scripts will be generated in `target/release/` +/// Build your project with `cargo build`. The completion scripts will be generated in `target/mingling/` /// /// Execute the script or have it be automatically sourced by your Shell /// @@ -982,19 +826,7 @@ pub mod example_command_macro {} /// features = [ /// # Enable `comp` features /// "comp", -/// "parser", -/// ] -/// -/// [build-dependencies.mingling] -/// path = "../../mingling" -/// -/// features = [ -/// # Enable `comp` features -/// "comp", -/// -/// # If you want to build completion scripts, -/// # enable `build` features -/// "build", +/// "picker", /// ] /// /// [workspace] @@ -1002,19 +834,11 @@ pub mod example_command_macro {} /// /// Source code (./src/main.rs) /// ```ignore -/// 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. @@ -1022,12 +846,12 @@ pub mod example_command_macro {} /// } /// /// // --------- 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 @@ -1040,18 +864,22 @@ pub mod example_command_macro {} /// } /// /// // 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 @@ -1062,15 +890,16 @@ pub mod example_command_macro {} /// } /// // --------- 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() /// } @@ -1078,7 +907,7 @@ pub mod example_command_macro {} /// /// 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 { @@ -1091,167 +920,14 @@ pub mod example_command_macro {} /// gen_program!(); /// ``` pub mod example_completion {} -/// 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 -/// ``` -/// -/// Source code (./Cargo.toml) -/// ```toml -/// [package] -/// name = "example-custom-pickable" -/// version = "0.1.0" -/// edition = "2024" -/// -/// [dependencies.mingling] -/// path = "../../mingling" -/// -/// features = ["parser", "extras"] -/// -/// [workspace] -/// ``` -/// -/// Source code (./src/main.rs) -/// ```ignore -/// 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 -/// ) -/// } -/// } -/// ``` -pub mod example_custom_pickable {} /// Example Dispatch Tree /// /// > 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 @@ -1287,30 +963,24 @@ pub mod example_custom_pickable {} /// /// // --------- 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(); /// } /// @@ -1354,7 +1024,7 @@ pub mod example_dispatch_tree {} /// /// features = [ /// "comp", -/// "parser" +/// "picker" /// ] /// /// [workspace] @@ -1363,8 +1033,10 @@ pub mod example_dispatch_tree {} /// Source code (./src/main.rs) /// ```ignore /// 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; /// @@ -1404,9 +1076,7 @@ pub mod example_dispatch_tree {} /// #[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] @@ -1415,17 +1085,39 @@ pub mod example_dispatch_tree {} /// } /// /// // --------- 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() /// } /// @@ -1439,7 +1131,7 @@ pub mod example_dispatch_tree {} /// } /// /// #[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) /// } @@ -1447,10 +1139,7 @@ pub mod example_dispatch_tree {} /// 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(); /// } /// ``` pub mod example_enum_tag {} @@ -1497,38 +1186,44 @@ pub mod example_enum_tag {} /// // 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. @@ -1567,21 +1262,14 @@ pub mod example_enum_tag {} /// #[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(); /// } /// ``` pub mod example_error_handling {} @@ -1630,27 +1318,29 @@ pub mod example_error_handling {} /// /// // --------- IMPORTANT --------- /// // Register `ExitCodeSetup` for the program to enable exit codes -/// program.with_setup(ExitCodeSetup::default()); +/// program.with_setup(ExitCodeSetup); /// // --------- IMPORTANT --------- /// -/// program.with_dispatcher(CMDHello); /// program.exec_and_exit(); /// } /// -/// dispatcher!("hello", CMDHello => EntryHello); +/// dispatcher!("hello", EntryHello); +/// +/// #[derive(Grouped)] +/// pub struct ErrorNoNameProvided; /// -/// pack!(ErrorNoNameProvided = ()); -/// pack!(ResultName = String); +/// #[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. @@ -1719,7 +1409,7 @@ pub mod example_exitcode {} /// 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]` @@ -1739,8 +1429,6 @@ pub mod example_exitcode {} /// program.with_setup(BasicProgramSetup); /// // --------- IMPORTANT --------- /// -/// program.with_dispatcher(CMDGreet); -/// /// program.exec_and_exit(); /// } /// @@ -1790,7 +1478,7 @@ pub mod example_help {} /// }; /// use std::io::Write; /// -/// dispatcher!("greet", CMDGreet => EntryGreet); +/// dispatcher!("greet", EntryGreet); /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -1814,16 +1502,16 @@ pub mod example_help {} /// ); /// // --------- 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()) @@ -1865,20 +1553,13 @@ pub mod example_hook {} /// ```ignore /// 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!(); @@ -1951,10 +1632,11 @@ pub mod example_implicit_dispatcher {} /// 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(); @@ -1966,7 +1648,6 @@ pub mod example_implicit_dispatcher {} /// program.with_resource(ResLargeData::lazy_init(init_res_large_data)); /// // --------- IMPORTANT --------- /// -/// program.with_dispatcher(CMDShow).with_dispatcher(CMDNone); /// program.exec_and_exit(); /// } /// @@ -2042,20 +1723,16 @@ pub mod example_lazy_resources {} /// 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. @@ -2077,14 +1754,17 @@ pub mod example_lazy_resources {} /// } /// // --------- 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()) @@ -2102,7 +1782,7 @@ pub mod example_lazy_resources {} /// 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. @@ -2115,7 +1795,7 @@ pub mod example_lazy_resources {} /// 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. @@ -2200,7 +1880,8 @@ pub mod example_metadata {} /// // 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` /// /// @@ -2208,9 +1889,9 @@ pub mod example_metadata {} /// /// 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(), /// } /// } @@ -2247,190 +1928,12 @@ pub mod example_metadata {} /// } /// /// 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!(); /// ``` pub mod example_outside_type {} -/// 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"} -/// ``` -/// -/// Source code (./Cargo.toml) -/// ```toml -/// [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] -/// ``` -/// -/// Source code (./src/main.rs) -/// ```ignore -/// 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(); -/// } -/// ``` -pub mod example_pack_err {} /// Example Panic Unwind /// /// > This example introduces how to catch Panic in the Mingling program loop @@ -2457,7 +1960,7 @@ pub mod example_pack_err {} /// /// [dependencies.mingling] /// path = "../../mingling" -/// features = ["parser"] +/// features = ["picker"] /// /// # Enable panic unwinding in release builds /// [profile.release] @@ -2476,12 +1979,13 @@ pub mod example_pack_err {} /// 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 @@ -2499,13 +2003,13 @@ pub mod example_pack_err {} /// /// #[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(), /// } /// } /// @@ -2553,17 +2057,6 @@ pub mod example_panic_unwind {} /// "pathf", /// ] /// -/// [build-dependencies.mingling] -/// path = "../../mingling" -/// -/// features = [ -/// # Enable `pathf` features -/// "pathf", -/// -/// # Enable the `build` feature for build-time support -/// "build", -/// ] -/// /// [workspace] /// ``` /// @@ -2572,12 +2065,9 @@ pub mod example_panic_unwind {} /// 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!(); @@ -2601,7 +2091,7 @@ pub mod example_pathfinder {} /// /// [dependencies.mingling] /// path = "../../mingling" -/// features = ["repl", "parser", "extras"] +/// features = ["repl", "picker", "extras"] /// /// [dependencies] /// just_fmt = "0.1.2" @@ -2641,12 +2131,6 @@ pub mod example_pathfinder {} /// // 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); @@ -2680,25 +2164,28 @@ pub mod example_pathfinder {} /// } /// /// // 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 @@ -2706,12 +2193,12 @@ pub mod example_pathfinder {} /// 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; @@ -2736,14 +2223,14 @@ pub mod example_pathfinder {} /// .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 @@ -2770,12 +2257,7 @@ pub mod example_pathfinder {} /// #[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 /// } /// @@ -2816,7 +2298,7 @@ pub mod example_repl_basic {} /// /// [dependencies.mingling] /// path = "../../mingling" -/// features = ["parser"] +/// features = ["picker"] /// /// [workspace] /// ``` @@ -2846,14 +2328,11 @@ pub mod example_repl_basic {} /// }); /// // --------- 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 /// // / @@ -2861,7 +2340,7 @@ pub mod example_repl_basic {} /// 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() /// } /// @@ -2885,7 +2364,8 @@ pub mod example_repl_basic {} pub mod example_resources {} /// 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`. /// /// Source code (./Cargo.toml) /// ```toml @@ -2903,6 +2383,25 @@ pub mod example_resources {} /// Source code (./src/main.rs) /// ```ignore /// 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(); @@ -2917,22 +2416,46 @@ pub mod example_resources {} /// /// // --------- 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!(); /// ``` @@ -2971,7 +2494,7 @@ pub mod example_setup {} /// features = [ /// "structural_renderer", /// "yaml_serde_fmt", -/// "parser", +/// "picker", /// ] /// /// [workspace] @@ -2979,23 +2502,23 @@ pub mod example_setup {} /// /// Source code (./src/main.rs) /// ```ignore -/// 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 @@ -3011,17 +2534,17 @@ pub mod example_setup {} /// } /// // 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() /// } /// @@ -3089,60 +2612,66 @@ pub mod example_structural_renderer {} /// 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); +/// +/// #[derive(Grouped)] +/// pub struct ErrorNoNameProvided; +/// +/// #[derive(Grouped, Wrap)] +/// pub struct ErrorNameTooLong(u16); /// -/// pack!(ErrorNoNameProvided = ()); -/// pack!(ErrorNameTooLong = u16); -/// pack!(ErrorNameNotAvailable = ()); +/// #[derive(Grouped)] +/// pub struct ErrorNameNotAvailable; /// -/// pack!(ResultName = String); +/// #[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. @@ -3181,21 +2710,14 @@ pub mod example_structural_renderer {} /// #[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(); /// } /// ``` pub mod example_unit_test {} |
