diff options
Diffstat (limited to 'mingling/src')
| -rw-r--r-- | mingling/src/docs/lib.md | 10 | ||||
| -rw-r--r-- | mingling/src/example_docs.rs | 1174 | ||||
| -rw-r--r-- | mingling/src/features.rs | 66 | ||||
| -rw-r--r-- | mingling/src/gen_program.rs | 34 | ||||
| -rw-r--r-- | mingling/src/lib.rs | 39 | ||||
| -rw-r--r-- | mingling/src/parser.rs | 12 | ||||
| -rw-r--r-- | mingling/src/parser/args.rs | 177 | ||||
| -rw-r--r-- | mingling/src/parser/picker.rs | 815 | ||||
| -rw-r--r-- | mingling/src/parser/picker/bools.rs | 143 | ||||
| -rw-r--r-- | mingling/src/parser/picker/builtin.rs | 113 | ||||
| -rw-r--r-- | mingling/src/parser/picker/path.rs | 145 | ||||
| -rw-r--r-- | mingling/src/parser/picker/path/rule.rs | 231 | ||||
| -rw-r--r-- | mingling/src/parser/test.rs | 731 | ||||
| -rw-r--r-- | mingling/src/setups/dirs.rs | 70 | ||||
| -rw-r--r-- | mingling/src/setups/exit_code.rs | 56 |
15 files changed, 458 insertions, 3358 deletions
diff --git a/mingling/src/docs/lib.md b/mingling/src/docs/lib.md index 697f6c5..3b1f328 100644 --- a/mingling/src/docs/lib.md +++ b/mingling/src/docs/lib.md @@ -22,20 +22,20 @@ Here is a basic project written using **Mingling**: ```rust use mingling::prelude::*; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); + let program = ThisProgram::new(); 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/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 {} diff --git a/mingling/src/features.rs b/mingling/src/features.rs index 2925f03..8a3ce75 100644 --- a/mingling/src/features.rs +++ b/mingling/src/features.rs @@ -31,50 +31,6 @@ pub const MINGLING_ASYNC: bool = false; #[cfg(feature = "async")] #[allow(unused)] pub const MINGLING_ASYNC: bool = true; -/// Whether the `build` feature is enabled -/// Current: `disabled` -#[cfg(not(feature = "build"))] -#[allow(unused)] -pub const MINGLING_BUILD: bool = false; - -/// Whether the `build` feature is enabled -/// Current: `enabled` -#[cfg(feature = "build")] -#[allow(unused)] -pub const MINGLING_BUILD: bool = true; -/// Whether the `build_advanced` feature is enabled -/// Current: `disabled` -#[cfg(not(feature = "build_advanced"))] -#[allow(unused)] -pub const MINGLING_BUILD_ADVANCED: bool = false; - -/// Whether the `build_advanced` feature is enabled -/// Current: `enabled` -#[cfg(feature = "build_advanced")] -#[allow(unused)] -pub const MINGLING_BUILD_ADVANCED: bool = true; -/// Whether the `build_full` feature is enabled -/// Current: `disabled` -#[cfg(not(feature = "build_full"))] -#[allow(unused)] -pub const MINGLING_BUILD_FULL: bool = false; - -/// Whether the `build_full` feature is enabled -/// Current: `enabled` -#[cfg(feature = "build_full")] -#[allow(unused)] -pub const MINGLING_BUILD_FULL: bool = true; -/// Whether the `builds` feature is enabled -/// Current: `disabled` -#[cfg(not(feature = "builds"))] -#[allow(unused)] -pub const MINGLING_BUILDS: bool = false; - -/// Whether the `builds` feature is enabled -/// Current: `enabled` -#[cfg(feature = "builds")] -#[allow(unused)] -pub const MINGLING_BUILDS: bool = true; /// Whether the `clap` feature is enabled /// Current: `disabled` #[cfg(not(feature = "clap"))] @@ -152,17 +108,6 @@ pub const MINGLING_DOCS_RS: bool = false; #[cfg(feature = "docs_rs")] #[allow(unused)] pub const MINGLING_DOCS_RS: bool = true; -/// Whether the `extra_macros` feature is enabled -/// Current: `disabled` -#[cfg(not(feature = "extra_macros"))] -#[allow(unused)] -pub const MINGLING_EXTRA_MACROS: bool = false; - -/// Whether the `extra_macros` feature is enabled -/// Current: `enabled` -#[cfg(feature = "extra_macros")] -#[allow(unused)] -pub const MINGLING_EXTRA_MACROS: bool = true; /// Whether the `extras` feature is enabled /// Current: `disabled` #[cfg(not(feature = "extras"))] @@ -229,17 +174,6 @@ pub const MINGLING_NIGHTLY: bool = false; #[cfg(feature = "nightly")] #[allow(unused)] pub const MINGLING_NIGHTLY: bool = true; -/// Whether the `parser` feature is enabled -/// Current: `disabled` -#[cfg(not(feature = "parser"))] -#[allow(unused)] -pub const MINGLING_PARSER: bool = false; - -/// Whether the `parser` feature is enabled -/// Current: `enabled` -#[cfg(feature = "parser")] -#[allow(unused)] -pub const MINGLING_PARSER: bool = true; /// Whether the `pathf` feature is enabled /// Current: `disabled` #[cfg(not(feature = "pathf"))] diff --git a/mingling/src/gen_program.rs b/mingling/src/gen_program.rs index 6a0b144..ed60668 100644 --- a/mingling/src/gen_program.rs +++ b/mingling/src/gen_program.rs @@ -4,7 +4,6 @@ use mingling_core::ChainProcess; use mingling_core::Dispatcher; use mingling_core::Grouped; -use mingling_core::Node; use mingling_core::Program; use mingling_core::ProgramCollect; @@ -13,7 +12,7 @@ pub type Next = ChainProcess<ThisProgram>; /// The generic program entry point. /// -/// This type is created by the `pack!` macro as a variant of the +/// This type is generated by the `gen_program!` macro as a variant of the /// program's output type set (`ThisProgram`). pub struct Entry { /// The arguments provided by the user @@ -42,7 +41,7 @@ pub enum ThisProgram { /// A struct representing a "renderer not found" error. /// -/// This type is created by the `pack!` macro as a variant of the +/// This type is generated by the `gen_program!` macro as a variant of the /// program's output type set (`ThisProgram`). pub struct ErrorRendererNotFound { /// The name of the renderer that was not found @@ -51,7 +50,7 @@ pub struct ErrorRendererNotFound { /// A struct representing a "dispatcher not found" error. /// -/// This type is created by the `pack!` macro as a variant of the +/// This type is generated by the `gen_program!` macro as a variant of the /// program's output type set (`ThisProgram`). pub struct EntryFallback { /// The arguments provided by the user @@ -62,8 +61,6 @@ pub struct EntryFallback { pub struct ResultEmpty; /// A dispatcher representing the subcommand `__comp` itself. -/// -/// **You can register it using `with_dispatcher`.** #[cfg(feature = "comp")] pub struct CMDCompletion; @@ -72,7 +69,7 @@ pub struct CMDCompletion; /// This struct holds the raw command-line arguments that were passed to the `__comp` /// subcommand, which will be used to compute completion suggestions. /// -/// This type is created by the `pack!` macro as a variant of the +/// This type is generated by the `gen_program!` macro as a variant of the /// program's output type set (`ThisProgram`). #[cfg(feature = "comp")] pub struct CompletionContext { @@ -86,7 +83,7 @@ pub struct CompletionContext { /// which together provide the information needed to render completion candidates /// to the user's shell. /// -/// This type is created by the `pack!` macro as a variant of the +/// This type is generated by the `gen_program!` macro as a variant of the /// program's output type set (`ThisProgram`). #[cfg(feature = "comp")] pub struct CompletionSuggest { @@ -96,18 +93,10 @@ pub struct CompletionSuggest { #[cfg(feature = "comp")] impl Dispatcher<ThisProgram> for CMDCompletion { - fn node(&self) -> mingling_core::Node { - Node::default().join(mingling_core::COMPLETION_SUBCOMMAND) - } - fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> { use mingling_core::AnyOutput; AnyOutput::new(CompletionContext { inner: args }).route_chain() } - - fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> { - todo!() - } } // SAFETY: These implementations are provided for demonstration purposes only. @@ -193,6 +182,19 @@ impl ProgramCollect for ThisProgram { type ResultEmpty = ResultEmpty; + fn dispatch_args( + _raw: &[String], + ) -> Result< + mingling_core::AnyOutput<Self::Enum>, + mingling_core::error::ProgramInternalExecuteError, + > { + todo!() + } + + fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher<Self::Enum> + Send + Sync))> { + todo!() + } + fn build_renderer_not_found(_member_id: Self::Enum) -> mingling_core::AnyOutput<Self::Enum> { todo!() } diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index 9d38a2a..45c8ec3 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -36,10 +36,6 @@ pub use mingling::*; #[cfg(feature = "core")] pub use mingling_core as mingling; -/// `Mingling` argument parser (Built-in) -#[cfg(feature = "parser")] -pub mod parser; - /// `Mingling` argument parser (Picker2) #[cfg(feature = "picker")] pub mod picker; @@ -55,7 +51,7 @@ pub mod consts { /// /// This module re-exports all macros provided by the `mingling_macros` crate, /// including `dispatcher!`, `chain!`, `renderer!`, -/// `gen_program!`, `pack!`, and many others. These macros form the core +/// `gen_program!`, and many others. These macros form the core /// building blocks of the Mingling framework. /// /// For detailed documentation, usage examples, and the full list of available @@ -68,6 +64,10 @@ pub mod macros { #[cfg(feature = "picker")] pub use arg_picker::macros::*; pub use mingling_macros::buffer; + #[cfg(feature = "comp")] + pub use mingling_macros::build_comp; + #[cfg(feature = "pathf")] + pub use mingling_macros::build_pathf; pub use mingling_macros::chain; #[cfg(feature = "extras")] pub use mingling_macros::command; @@ -88,14 +88,6 @@ pub mod macros { pub use mingling_macros::help; pub use mingling_macros::metadata; pub use mingling_macros::mlint; - pub use mingling_macros::node; - pub use mingling_macros::pack; - #[cfg(feature = "extras")] - pub use mingling_macros::pack_err; - #[cfg(all(feature = "structural_renderer", feature = "extras"))] - pub use mingling_macros::pack_err_structural; - #[cfg(feature = "structural_renderer")] - pub use mingling_macros::pack_structural; #[cfg(feature = "comp")] #[doc(hidden)] pub use mingling_macros::program_comp_gen; @@ -138,6 +130,9 @@ pub mod macros { } #[cfg(feature = "macros")] +pub use mingling_macros::Wrap; + +#[cfg(feature = "macros")] pub use mingling_macros::EnumTag; #[cfg(feature = "macros")] @@ -202,6 +197,8 @@ pub mod prelude { #[cfg(feature = "core")] pub use crate::Routable; #[cfg(feature = "macros")] + pub use crate::Wrap; + #[cfg(feature = "macros")] pub use crate::macros::chain; #[cfg(all(feature = "extras", feature = "macros"))] pub use crate::macros::command; @@ -212,19 +209,7 @@ pub mod prelude { #[cfg(feature = "macros")] pub use crate::macros::gen_program; #[cfg(feature = "macros")] - pub use crate::macros::pack; - #[cfg(all(feature = "extras", feature = "macros"))] - pub use crate::macros::pack_err; - #[cfg(feature = "macros")] pub use crate::macros::renderer; - #[cfg(all( - feature = "macros", - feature = "structural_renderer", - feature = "extras" - ))] - pub use mingling_macros::pack_err_structural; - #[cfg(all(feature = "macros", feature = "structural_renderer"))] - pub use mingling_macros::pack_structural; pub use mingling_macros::r_append; pub use mingling_macros::r_eprint; pub use mingling_macros::r_eprintln; @@ -232,11 +217,9 @@ pub mod prelude { pub use mingling_macros::r_println; #[cfg(all(feature = "macros", feature = "comp"))] + #[cfg(feature = "comp")] pub use crate::macros::completion; - #[cfg(feature = "parser")] - pub use crate::parser::AsPicker; - #[cfg(feature = "picker")] pub use arg_picker::prelude::arg; diff --git a/mingling/src/parser.rs b/mingling/src/parser.rs deleted file mode 100644 index 97124ca..0000000 --- a/mingling/src/parser.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Doc Not Optimize -mod args; -pub use crate::parser::args::*; - -mod picker; -pub use crate::parser::picker::*; - -pub use crate::parser::picker::bools::*; -pub use crate::parser::picker::path::*; - -#[cfg(test)] -mod test; diff --git a/mingling/src/parser/args.rs b/mingling/src/parser/args.rs deleted file mode 100644 index c7139c4..0000000 --- a/mingling/src/parser/args.rs +++ /dev/null @@ -1,177 +0,0 @@ -// Doc Not Optimize -use std::mem::replace; - -use mingling_core::{Flag, special_argument, special_arguments, special_flag}; - -/// User input arguments -#[derive(Debug, Default, Clone)] -pub struct Argument { - vec: Vec<String>, -} - -impl From<Vec<&str>> for Argument { - fn from(vec: Vec<&str>) -> Self { - Self { - vec: vec - .into_iter() - .map(std::string::ToString::to_string) - .collect(), - } - } -} - -impl From<&'static str> for Argument { - fn from(s: &'static str) -> Self { - Self { - vec: vec![s.to_string()], - } - } -} - -impl From<&'static [&'static str]> for Argument { - fn from(slice: &'static [&'static str]) -> Self { - Self { - vec: slice.iter().map(|&s| s.to_string()).collect(), - } - } -} - -impl<const N: usize> From<[&'static str; N]> for Argument { - fn from(slice: [&'static str; N]) -> Self { - Self { - vec: slice.iter().map(|&s| s.to_string()).collect(), - } - } -} - -impl<const N: usize> From<&'static [&'static str; N]> for Argument { - fn from(slice: &'static [&'static str; N]) -> Self { - Self { - vec: slice.iter().map(|&s| s.to_string()).collect(), - } - } -} - -impl From<Vec<String>> for Argument { - fn from(vec: Vec<String>) -> Self { - Self { vec } - } -} - -impl AsRef<[String]> for Argument { - fn as_ref(&self) -> &[String] { - &self.vec - } -} - -impl std::ops::Deref for Argument { - type Target = Vec<String>; - - fn deref(&self) -> &Self::Target { - &self.vec - } -} - -impl std::ops::DerefMut for Argument { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.vec - } -} - -impl Argument { - /// Picks a single argument with the given flag - pub fn pick_argument<F>(&mut self, flag: F) -> Option<String> - where - F: Into<Flag>, - { - if self.is_empty() { - return None; - } - - let flag: Flag = flag.into(); - if flag.is_empty() { - // No flag - return Some(self.vec.remove(0)); - } - // Has any flag - for argument in flag.iter() { - let value = special_argument!(self.vec, argument); - if value.is_some() { - return value; - } - } - None - } - - /// Picks arguments with the given flag - pub fn pick_arguments<F>(&mut self, flag: F) -> Vec<String> - where - F: Into<Flag>, - { - let mut str_result = Vec::new(); - - if self.is_empty() { - return str_result; - } - - let flag: Flag = flag.into(); - if flag.is_empty() { - let value = special_arguments!(self.vec, ""); - str_result.extend(value); - } else { - for argument in flag.iter() { - let value = special_arguments!(self.vec, argument); - str_result.extend(value); - } - } - - str_result - } - - /// Picks a flag with the given flag - pub fn pick_flag<F>(&mut self, flag: F) -> bool - where - F: Into<Flag>, - { - if self.is_empty() { - return false; - } - - let flag: Flag = flag.into(); - if flag.is_empty() { - let first = self.vec.remove(0); - let first_lower = first.to_lowercase(); - let trimmed = first_lower.trim(); - let result = match trimmed { - "y" | "yes" | "true" | "1" => return true, - "n" | "no" | "false" | "0" => return false, - _ => false, - }; - return result; - } - // Has any flag - for argument in flag.iter() { - let enabled = special_flag!(self.vec, argument); - if enabled { - return enabled; - } - } - false - } - - /// Dump all remaining arguments - pub const fn dump_remains(&mut self) -> Vec<String> { - let new = Vec::new(); - replace(&mut self.vec, new) - } - - /// Removes all arguments that start with a dash ('-') - /// - /// This method filters out all command-line style flags from the arguments, - /// returning a new `Argument` instance containing only non-flag arguments. - #[must_use] - pub fn strip_all_flags(mut self) -> Self { - self.vec.retain(|f| !f.starts_with('-')); - self - } -} diff --git a/mingling/src/parser/picker.rs b/mingling/src/parser/picker.rs deleted file mode 100644 index 2f43e8c..0000000 --- a/mingling/src/parser/picker.rs +++ /dev/null @@ -1,815 +0,0 @@ -// Doc Not Optimize -use crate::parser::Argument; -use mingling_core::{EnumTag, Flag}; - -#[doc(hidden)] -pub mod builtin; - -#[doc(hidden)] -pub mod bools; - -#[doc(hidden)] -pub mod path; - -/// A builder for extracting values from command-line arguments. -/// -/// The `Picker` struct holds parsed arguments and provides a fluent interface -/// to extract values associated with specific flags. -#[derive(Default)] -pub struct Picker { - /// The parsed command-line arguments. - pub args: Argument, -} - -impl Picker { - /// Creates a new `Picker` from a value that can be converted into `Argument`. - pub fn new(args: impl Into<Argument>) -> Self { - Self { args: args.into() } - } - - /// Extracts a value for the given flag and returns a `Pick1` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable` and `Default`. - /// If the flag is not present, the default value for `TNext` is used. - pub fn pick<TNext>(mut self, val: impl Into<Flag>) -> Pick1<TNext> - where - TNext: Pickable<Output = TNext> + Default, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_default(); - Pick1 { - args: self.args, - val_1: v, - } - } - - /// Extracts a value for the given flag, returning the provided default value if not present, - /// and returns a `Pick1` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable`. - /// If the flag is not present, the provided `or` value is used. - pub fn pick_or<TNext>(mut self, val: impl Into<Flag>, or: impl Into<TNext>) -> Pick1<TNext> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_else(|| or.into()); - Pick1 { - args: self.args, - val_1: v, - } - } - - /// Extracts a value for the given flag, storing the provided route if the flag is not present, - /// and returns a `PickWithRoute1` builder (with route). - /// - /// The extracted type `TNext` must implement `Pickable` and `Default`. - /// If the flag is not present, the default value for `TNext` is used and the provided `route` - /// is stored in the returned builder for later error handling. - pub fn pick_or_route<TNext, R>( - mut self, - val: impl Into<Flag>, - route: R, - ) -> PickWithRoute1<TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let Some(v) = TNext::pick(&mut self.args, val.into()) else { - return PickWithRoute1 { - args: self.args, - val_1: TNext::default(), - route: Some(route), - }; - }; - PickWithRoute1 { - args: self.args, - val_1: v, - route: None, - } - } - - /// Extracts a value for the given flag, returning `None` if the flag is not present, - /// and returns an `Option<Pick1<TNext>>` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable`. - /// If the flag is not present, `None` is returned. - pub fn require<TNext>(mut self, val: impl Into<Flag>) -> Option<Pick1<TNext>> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()); - match v { - Some(s) => Some(Pick1 { - args: self.args, - val_1: s, - }), - None => None, - } - } - - /// Applies an operation to the parsed arguments and returns the modified `Picker`. - /// - /// Takes a closure that receives the current `Argument` and returns a new `Argument`. - /// The returned `Argument` replaces the original arguments in the builder. - /// This method can be used to modify or transform the parsed arguments before extracting values. - #[must_use] - pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self { - self.args = operation(self.args); - self - } -} - -impl<T: Into<Argument>> From<T> for Picker { - fn from(value: T) -> Self { - Self::new(value) - } -} - -/// Extracts values from command-line arguments -/// -/// The `Pickable` trait defines how to extract the value of a specific flag from parsed arguments -pub trait Pickable { - /// The output type produced by the extraction operation, must implement the `Default` trait - type Output: Default; - - /// Extracts the value associated with the given flag from the provided arguments - /// - /// If the flag exists and the value can be successfully extracted, returns `Some(Output)`; - /// otherwise returns `None` - fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output>; -} - -// Non-routed Pick structs (no R parameter, no route field) - -/// Internal macro: generates the struct definition and common methods -/// (after, `after_or_route`, `operate_args`) for non-routed Pick structs. -macro_rules! define_pick_struct { - ($n:ident $final:ident $final_val:ident $route_self:ident $($T:ident $val:ident),+ $(,)?) => { - #[doc(hidden)] - pub struct $n<$($T,)+> - where - $($T: Pickable,)+ - { - #[allow(dead_code)] - args: Argument, - $(pub $val: $T,)+ - } - - impl<$($T,)+> $n<$($T,)+> - where - $($T: Pickable,)+ - { - /// Applies a transformation to the last extracted value. - /// - /// Takes a closure that receives the last extracted value and returns a new value of the same type. - /// The transformed value replaces the original value in the builder. - /// This method can be used to modify or validate the extracted value before final unpacking. - #[must_use] - pub fn after<F>(mut self, mut edit: F) -> Self - where - F: FnMut($final) -> $final, - { - self.$final_val = edit(self.$final_val); - self - } - - /// Applies a transformation to the last extracted value, storing a route if the transformation fails. - /// - /// Takes a closure that receives a reference to the last extracted value and returns a `Result`. - /// If the closure returns `Ok(new_value)`, the new value replaces the original value in the builder. - /// If the closure returns `Err(route)`, the provided `route` is stored in the builder for later error handling. - /// If a route was already stored from a previous `pick_or_route` call, the existing route is preserved. - #[must_use] - pub fn after_or_route<F, R>(mut self, mut edit: F) -> $route_self<$($T,)+ R> - where - F: FnMut(&$final) -> Result<$final, R>, - { - match edit(&self.$final_val) { - Ok(new_value) => { - self.$final_val = new_value; - $route_self { - args: self.args, - $($val: self.$val,)+ - route: None, - } - } - Err(err_route) => { - $route_self { - args: self.args, - $($val: self.$val,)+ - route: Some(err_route), - } - } - } - } - - /// Applies an operation to the parsed arguments and returns the modified builder. - /// - /// Takes a closure that receives the current `Argument` and returns a new `Argument`. - /// The returned `Argument` replaces the original arguments in the builder. - /// This method can be used to modify or transform the parsed arguments before extracting values. - #[must_use] - pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self { - self.args = operation(self.args); - self - } - } - }; -} - -// Pick1 special case (single value) - -define_pick_struct! { Pick1 T1 val_1 PickWithRoute1 T1 val_1 } - -impl<T1> From<Pick1<T1>> for (T1,) -where - T1: Pickable, -{ - fn from(pick: Pick1<T1>) -> Self { - (pick.val_1,) - } -} - -impl<T1> Pick1<T1> -where - T1: Pickable, -{ - /// Unpacks the builder into the extracted value. - /// - /// Always returns the value directly since there is no route. - pub fn unpack(self) -> T1 { - self.val_1 - } -} - -// Pick2 .. Pick12 - -macro_rules! impl_pick_from_tuple { - ($n:ident $($T:ident $val:ident),+) => { - impl<$($T,)+> From<$n<$($T,)+>> for ($($T,)+) - where - $($T: Pickable,)+ - { - fn from(pick: $n<$($T,)+>) -> Self { - ($(pick.$val,)+) - } - } - }; -} - -macro_rules! impl_pick_unpack_tuple { - ($n:ident $($T:ident $val:ident),+) => { - impl<$($T,)+> $n<$($T,)+> - where - $($T: Pickable,)+ - { - /// Unpacks the builder into a tuple of extracted values. - /// - /// Always returns the tuple directly since there is no route. - pub fn unpack(self) -> ($($T,)+) { - ($(self.$val,)+) - } - } - }; -} - -define_pick_struct! { Pick2 T2 val_2 PickWithRoute2 T1 val_1, T2 val_2 } -impl_pick_from_tuple! { Pick2 T1 val_1, T2 val_2 } -impl_pick_unpack_tuple! { Pick2 T1 val_1, T2 val_2 } - -define_pick_struct! { Pick3 T3 val_3 PickWithRoute3 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_from_tuple! { Pick3 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_unpack_tuple! { Pick3 T1 val_1, T2 val_2, T3 val_3 } - -define_pick_struct! { Pick4 T4 val_4 PickWithRoute4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_from_tuple! { Pick4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_unpack_tuple! { Pick4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } - -define_pick_struct! { Pick5 T5 val_5 PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_from_tuple! { Pick5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_unpack_tuple! { Pick5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } - -define_pick_struct! { Pick6 T6 val_6 PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_from_tuple! { Pick6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_unpack_tuple! { Pick6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } - -define_pick_struct! { Pick7 T7 val_7 PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_from_tuple! { Pick7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_unpack_tuple! { Pick7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } - -define_pick_struct! { Pick8 T8 val_8 PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_from_tuple! { Pick8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_unpack_tuple! { Pick8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } - -define_pick_struct! { Pick9 T9 val_9 PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_from_tuple! { Pick9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_unpack_tuple! { Pick9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } - -define_pick_struct! { Pick10 T10 val_10 PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_from_tuple! { Pick10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_unpack_tuple! { Pick10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } - -define_pick_struct! { Pick11 T11 val_11 PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } -impl_pick_from_tuple! { Pick11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } -impl_pick_unpack_tuple! { Pick11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } - -define_pick_struct! { Pick12 T12 val_12 PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } -impl_pick_from_tuple! { Pick12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } -impl_pick_unpack_tuple! { Pick12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } - -// Non-routed Pick chaining methods (pick, pick_or, pick_or_route, require) - -#[doc(hidden)] -macro_rules! impl_pick_next { - ($n:ident $next:ident $next_val:ident $route_next:ident $($T:ident $val:ident),+) => { - impl<$($T,)+> $n<$($T,)+> - where - $($T: Pickable,)+ - { - /// Extracts a value for the given flag and returns a `PickN` builder (no route). - pub fn pick<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> $next<$($T,)+ TNext> - where - TNext: Pickable<Output = TNext> + Default, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_default(); - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - } - } - - /// Extracts a value for the given flag, returning the provided default value if not present, - /// and returns a `PickN` builder (no route). - pub fn pick_or<TNext>(mut self, val: impl Into<mingling_core::Flag>, or: impl Into<TNext>) -> $next<$($T,)+ TNext> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_else(|| or.into()); - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - } - } - - /// Extracts a value for the given flag, storing the provided route if the flag is not present, - /// and returns a `PickWithRouteN` builder (with route). - pub fn pick_or_route<TNext, R>( - mut self, - val: impl Into<mingling_core::Flag>, - route: R, - ) -> $route_next<$($T,)+ TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let Some(v) = TNext::pick(&mut self.args, val.into()) else { - return $route_next { - args: self.args, - $($val: self.$val,)+ - $next_val: TNext::default(), - route: Some(route), - }; - }; - $route_next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - route: None, - } - } - - /// Extracts a value for the given flag, returning `None` if the flag is not present, - /// and returns an `Option<PickN<TNext>>` builder (no route). - pub fn require<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> Option<$next<$($T,)+ TNext>> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()); - match v { - Some(s) => Some($next { - args: self.args, - $($val: self.$val,)+ - $next_val: s, - }), - None => None, - } - } - } - }; -} - -impl_pick_next! { Pick1 Pick2 val_2 PickWithRoute2 T1 val_1 } -impl_pick_next! { Pick2 Pick3 val_3 PickWithRoute3 T1 val_1, T2 val_2 } -impl_pick_next! { Pick3 Pick4 val_4 PickWithRoute4 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_next! { Pick4 Pick5 val_5 PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_next! { Pick5 Pick6 val_6 PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_next! { Pick6 Pick7 val_7 PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_next! { Pick7 Pick8 val_8 PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_next! { Pick8 Pick9 val_9 PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_next! { Pick9 Pick10 val_10 PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_next! { Pick10 Pick11 val_11 PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_next! { Pick11 Pick12 val_12 PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } - -// Routed PickWithRoute structs (with R parameter, route field) - -/// Internal macro: generates the routed struct definition and common methods -/// (after, `after_or_route`, `operate_args`) for `PickWithRoute` structs. -macro_rules! define_pick_with_route_struct { - ($n:ident $final:ident $final_val:ident $($T:ident $val:ident),+) => { - #[doc(hidden)] - pub struct $n<$($T,)+ R> - where - $($T: Pickable,)+ - { - #[allow(dead_code)] - args: Argument, - $(pub $val: $T,)+ - route: Option<R>, - } - - impl<$($T,)+ R> $n<$($T,)+ R> - where - $($T: Pickable,)+ - { - /// Applies a transformation to the last extracted value. - /// - /// Takes a closure that receives the last extracted value and returns a new value of the same type. - /// The transformed value replaces the original value in the builder. - /// This method can be used to modify or validate the extracted value before final unpacking. - #[must_use] - pub fn after<F>(mut self, mut edit: F) -> Self - where - F: FnMut($final) -> $final, - { - self.$final_val = edit(self.$final_val); - self - } - - /// Applies a transformation to the last extracted value, storing a route if the transformation fails. - /// - /// Takes a closure that receives a reference to the last extracted value and returns a `Result`. - /// If the closure returns `Ok(new_value)`, the new value replaces the original value in the builder. - /// If the closure returns `Err(route)`, the provided `route` is stored in the builder for later error handling. - /// If a route was already stored from a previous `pick_or_route` call, the existing route is preserved. - #[must_use] - pub fn after_or_route<F>(mut self, mut edit: F) -> Self - where - F: FnMut(&$final) -> Result<$final, R>, - { - let value = &self.$final_val; - match edit(value) { - Ok(new_value) => { - self.$final_val = new_value; - } - Err(err_route) => { - let new_route = match self.route { - Some(existing_route) => Some(existing_route), - None => Some(err_route), - }; - self.route = new_route; - } - } - self - } - - /// Applies an operation to the parsed arguments and returns the modified builder. - /// - /// Takes a closure that receives the current `Argument` and returns a new `Argument`. - /// The returned `Argument` replaces the original arguments in the builder. - /// This method can be used to modify or transform the parsed arguments before extracting values. - #[must_use] - pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self { - self.args = operation(self.args); - self - } - } - }; -} - -/// Internal macro: generates `From` impl for routed `PickWithRouteN` into a tuple. -macro_rules! impl_pick_with_route_from_tuple { - ($n:ident $($T:ident $val:ident),+) => { - impl<$($T,)+ R> From<$n<$($T,)+ R>> for ($($T,)+) - where - $($T: Pickable,)+ - { - fn from(pick: $n<$($T,)+ R>) -> Self { - ($(pick.$val,)+) - } - } - }; -} - -/// Internal macro: generates `unpack` and `unpack_directly` for routed `PickWithRouteN` (N >= 2). -macro_rules! impl_pick_with_route_unpack_tuple { - ($n:ident $($T:ident $val:ident),+) => { - impl<$($T,)+ R> $n<$($T,)+ R> - where - $($T: Pickable,)+ - { - /// Unpacks the builder into a tuple of extracted values. - /// - /// Returns `Ok((T1, T2, ...))` if no route was stored. - /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. - /// - /// # Errors - /// - /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. - pub fn unpack(self) -> Result<($($T,)+), R> { - match self.route { - Some(route) => Err(route), - None => Ok(($(self.$val,)+)), - } - } - - /// Unpacks the builder into a tuple of extracted values. - /// - /// Returns the tuple of extracted values regardless of route state. - #[must_use] - pub fn unpack_directly(self) -> ($($T,)+) { - ($(self.$val,)+) - } - } - }; -} - -// PickWithRoute1 special case (single value) - -define_pick_with_route_struct! { PickWithRoute1 T1 val_1 T1 val_1 } - -impl<T1, R> From<PickWithRoute1<T1, R>> for (T1,) -where - T1: Pickable, -{ - fn from(pick: PickWithRoute1<T1, R>) -> Self { - (pick.val_1,) - } -} - -impl<T1, R> PickWithRoute1<T1, R> -where - T1: Pickable, -{ - /// Unpacks the builder into the extracted value. - /// - /// Returns `Ok(T1)` if no route was stored. - /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. - /// - /// # Errors - /// - /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. - pub fn unpack(self) -> Result<T1, R> { - match self.route { - Some(route) => Err(route), - None => Ok(self.val_1), - } - } - - /// Unpacks the builder into the extracted value. - /// - /// Returns the extracted value regardless of route state. - #[must_use] - pub fn unpack_directly(self) -> T1 { - self.val_1 - } -} - -// PickWithRoute2 .. PickWithRoute12 - -define_pick_with_route_struct! { PickWithRoute2 T2 val_2 T1 val_1, T2 val_2 } -impl_pick_with_route_from_tuple! { PickWithRoute2 T1 val_1, T2 val_2 } -impl_pick_with_route_unpack_tuple! { PickWithRoute2 T1 val_1, T2 val_2 } - -define_pick_with_route_struct! { PickWithRoute3 T3 val_3 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_with_route_from_tuple! { PickWithRoute3 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_with_route_unpack_tuple! { PickWithRoute3 T1 val_1, T2 val_2, T3 val_3 } - -define_pick_with_route_struct! { PickWithRoute4 T4 val_4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_with_route_from_tuple! { PickWithRoute4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_with_route_unpack_tuple! { PickWithRoute4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } - -define_pick_with_route_struct! { PickWithRoute5 T5 val_5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_with_route_from_tuple! { PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_with_route_unpack_tuple! { PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } - -define_pick_with_route_struct! { PickWithRoute6 T6 val_6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_with_route_from_tuple! { PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_with_route_unpack_tuple! { PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } - -define_pick_with_route_struct! { PickWithRoute7 T7 val_7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_with_route_from_tuple! { PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_with_route_unpack_tuple! { PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } - -define_pick_with_route_struct! { PickWithRoute8 T8 val_8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_with_route_from_tuple! { PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_with_route_unpack_tuple! { PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } - -define_pick_with_route_struct! { PickWithRoute9 T9 val_9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_with_route_from_tuple! { PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_with_route_unpack_tuple! { PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } - -define_pick_with_route_struct! { PickWithRoute10 T10 val_10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_with_route_from_tuple! { PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_with_route_unpack_tuple! { PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } - -define_pick_with_route_struct! { PickWithRoute11 T11 val_11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } -impl_pick_with_route_from_tuple! { PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } -impl_pick_with_route_unpack_tuple! { PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } - -define_pick_with_route_struct! { PickWithRoute12 T12 val_12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } -impl_pick_with_route_from_tuple! { PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } -impl_pick_with_route_unpack_tuple! { PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } - -// Routed PickWithRoute chaining methods (pick, pick_or, pick_or_route, require) - -#[doc(hidden)] -macro_rules! impl_pick_with_route_next { - ($n:ident $next:ident $next_val:ident $($T:ident $val:ident),+) => { - impl<$($T,)+ R> $n<$($T,)+ R> - where - $($T: Pickable,)+ - { - /// Extracts a value for the given flag and returns a `PickWithRouteN` builder. - pub fn pick<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> $next<$($T,)+ TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_default(); - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - route: self.route, - } - } - - /// Extracts a value for the given flag, returning the provided default value if not present, - /// and returns a `PickWithRouteN` builder. - pub fn pick_or<TNext>(mut self, val: impl Into<mingling_core::Flag>, or: impl Into<TNext>) -> $next<$($T,)+ TNext, R> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_else(|| or.into()); - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - route: self.route, - } - } - - /// Extracts a value for the given flag, storing the provided route if the flag is not present, - /// and returns a `PickWithRouteN` builder. - /// - /// If a route was already stored from a previous `pick_or_route` or `after_or_route` call, - /// the existing route is preserved and the new `route` parameter is ignored. - #[allow(clippy::manual_let_else)] - pub fn pick_or_route<TNext>(mut self, val: impl Into<mingling_core::Flag>, route: R) -> $next<$($T,)+ TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let v = match TNext::pick(&mut self.args, val.into()) { - Some(value) => value, - None => { - let new_route = match self.route { - Some(existing_route) => Some(existing_route), - None => Some(route), - }; - return $next { - args: self.args, - $($val: self.$val,)+ - $next_val: TNext::default(), - route: new_route, - }; - } - }; - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - route: self.route, - } - } - - /// Extracts a value for the given flag, returning `None` if the flag is not present, - /// and returns an `Option<PickWithRouteN>` builder. - pub fn require<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> Option<$next<$($T,)+ TNext, R>> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()); - match v { - Some(s) => Some($next { - args: self.args, - $($val: self.$val,)+ - $next_val: s, - route: self.route, - }), - None => None, - } - } - } - }; -} - -impl_pick_with_route_next! { PickWithRoute1 PickWithRoute2 val_2 T1 val_1 } -impl_pick_with_route_next! { PickWithRoute2 PickWithRoute3 val_3 T1 val_1, T2 val_2 } -impl_pick_with_route_next! { PickWithRoute3 PickWithRoute4 val_4 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_with_route_next! { PickWithRoute4 PickWithRoute5 val_5 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_with_route_next! { PickWithRoute5 PickWithRoute6 val_6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_with_route_next! { PickWithRoute6 PickWithRoute7 val_7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_with_route_next! { PickWithRoute7 PickWithRoute8 val_8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_with_route_next! { PickWithRoute8 PickWithRoute9 val_9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_with_route_next! { PickWithRoute9 PickWithRoute10 val_10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_with_route_next! { PickWithRoute10 PickWithRoute11 val_11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_with_route_next! { PickWithRoute11 PickWithRoute12 val_12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } - -/// Trait for types that can be used with `Pickable` to extract enum values from command-line arguments. -/// -/// This trait combines `EnumTag` (for building an enum variant from a string name) and `Default` -/// (for providing a fallback value when the flag is not present). -/// -/// Types implementing this trait can be used with `Picker::pick`, `Picker::pick_or_route`, and -/// the chaining `.pick()` methods to extract and parse enum values from command-line arguments. -pub trait PickableEnum: EnumTag + Default {} - -impl<T> Pickable for T -where - T: PickableEnum, -{ - type Output = T; - - fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output> { - let name = args.pick_argument(flag)?; - T::build_enum(name) - } -} - -/// Trait for types that can be converted into a `Picker` to extract values from command-line arguments. -/// -/// This trait provides a convenient way to convert a value (such as `Vec<String>`, `&[String]`, etc.) -/// into a `Picker` and immediately start extracting values associated with specific flags. -pub trait AsPicker -where - Self: Into<Vec<String>>, -{ - /// Converts the value into a `Picker` by first converting it into a `Vec<String>`. - fn to_picker(self) -> Picker - where - Self: Sized, - Vec<String>: From<Self>, - { - let vec: Vec<String> = self.into(); - Picker { args: vec.into() } - } - - /// Extracts a value for the given flag and returns a `Pick1` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable` and `Default`. - /// If the flag is not present, the default value for `TNext` is used. - fn pick<TNext>(self, val: impl Into<Flag>) -> Pick1<TNext> - where - Self: Sized, - TNext: Pickable<Output = TNext> + Default, - { - let vec: Vec<String> = self.into(); - let picker: Picker = vec.into(); - picker.pick(val) - } - - /// Extracts a value for the given flag, returning the provided default value if not present, - /// and returns a `Pick1` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable`. - /// If the flag is not present, the provided `or` value is used. - fn pick_or<TNext>(self, val: impl Into<Flag>, or: impl Into<TNext>) -> Pick1<TNext> - where - TNext: Pickable<Output = TNext>, - { - let vec: Vec<String> = self.into(); - let picker: Picker = vec.into(); - picker.pick_or(val, or) - } - - /// Extracts a value for the given flag, storing the provided route if the flag is not present, - /// and returns a `PickWithRoute1` builder (with route). - /// - /// The extracted type `TNext` must implement `Pickable` and `Default`. - /// If the flag is not present, the default value for `TNext` is used and the provided `route` - /// is stored in the returned builder for later error handling. - fn pick_or_route<TNext, R>(self, val: impl Into<Flag>, route: R) -> PickWithRoute1<TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let vec: Vec<String> = self.into(); - let picker: Picker = vec.into(); - picker.pick_or_route(val, route) - } -} - -// Implement AsPicker for any type that can be converted into a Vec<String> -impl<T> AsPicker for T -where - T: Sized, - Vec<String>: From<T>, -{ -} diff --git a/mingling/src/parser/picker/bools.rs b/mingling/src/parser/picker/bools.rs deleted file mode 100644 index bc9fd90..0000000 --- a/mingling/src/parser/picker/bools.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Doc Not Optimize -use crate::parser::Pickable; - -/// Represents a boolean-like value with `Yes` and `No` variants. -/// -/// `Yes` can be parsed from command-line arguments using positive keywords such as `"y"` or `"yes"`, -/// and defaults to `No`. -#[derive(Debug, Default)] -#[repr(u8)] -pub enum Yes { - /// The affirmative/positive variant. - Yes, - /// The negative/default variant. - #[default] - No, -} - -impl From<bool> for Yes { - fn from(b: bool) -> Self { - if b { Self::Yes } else { Self::No } - } -} - -impl From<Yes> for bool { - fn from(val: Yes) -> Self { - match val { - Yes::Yes => true, - Yes::No => false, - } - } -} - -impl std::ops::Deref for Yes { - type Target = bool; - - fn deref(&self) -> &Self::Target { - static TRUE: bool = true; - static FALSE: bool = false; - match self { - Self::Yes => &TRUE, - Self::No => &FALSE, - } - } -} - -impl Yes { - #[must_use] - pub const fn is_yes(&self) -> bool { - matches!(self, Self::Yes) - } - - #[must_use] - pub const fn is_no(&self) -> bool { - matches!(self, Self::No) - } -} - -impl Pickable for Yes { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let value = pick_bool(args, flag, &["y", "yes"]); - Some(value.into()) - } -} - -/// Represents a boolean-like value with `True` and `False` variants. -/// -/// `True` can be parsed from command-line arguments using positive keywords such as `"t"` or `"true"`, -/// and defaults to `False`. -#[derive(Debug, Default)] -#[repr(u8)] -pub enum True { - /// The affirmative/positive variant. - True, - /// The negative/default variant. - #[default] - False, -} - -impl From<bool> for True { - fn from(b: bool) -> Self { - if b { Self::True } else { Self::False } - } -} - -impl From<True> for bool { - fn from(val: True) -> Self { - match val { - True::True => true, - True::False => false, - } - } -} - -impl std::ops::Deref for True { - type Target = bool; - - fn deref(&self) -> &Self::Target { - static TRUE: bool = true; - static FALSE: bool = false; - match self { - Self::True => &TRUE, - Self::False => &FALSE, - } - } -} - -impl True { - #[must_use] - pub const fn is_true(&self) -> bool { - matches!(self, Self::True) - } - - #[must_use] - pub const fn is_false(&self) -> bool { - matches!(self, Self::False) - } -} - -impl Pickable for True { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let value = pick_bool(args, flag, &["true", "t"]); - Some(value.into()) - } -} - -fn pick_bool( - args: &mut crate::parser::Argument, - flag: mingling_core::Flag, - positive: &[&str], -) -> bool { - let content = args.pick_argument(flag); - content.map_or_else( - || false, - |content| { - let s = content.as_str(); - positive.contains(&s) - }, - ) -} diff --git a/mingling/src/parser/picker/builtin.rs b/mingling/src/parser/picker/builtin.rs deleted file mode 100644 index 6f67c78..0000000 --- a/mingling/src/parser/picker/builtin.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Doc Not Optimize -use size::Size; - -use crate::parser::{Argument, Pickable}; - -impl Pickable for String { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - args.pick_argument(flag) - } -} - -impl Pickable for Vec<String> { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - Some(args.pick_arguments(flag)) - } -} - -macro_rules! impl_pickable_for_number { - ($($t:ty),*) => { - $( - impl Pickable for $t { - type Output = $t; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let picked = args.pick_argument(flag)?; - picked.parse().ok() - } - } - - impl Pickable for Vec<$t> { - type Output = Vec<$t>; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let picked_vec = args.pick_arguments(flag); - let mut result = Vec::new(); - for picked in picked_vec { - if let Ok(parsed) = picked.parse() { - result.push(parsed); - } else { - return None; - } - } - Some(result) - } - } - )* - }; -} - -impl_pickable_for_number!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64); - -impl Pickable for bool { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - Some(args.pick_flag(flag)) - } -} - -/// Special: parses a size string (e.g. "10MB") into a `usize` representing the number of bytes. -impl Pickable for usize { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let picked = args.pick_argument(flag)?; - let size_parse = Size::from_str(picked.as_str()); - size_parse.map_or(None, |size| Self::try_from(size.bytes()).ok()) - } -} - -/// Special: parses a comma-separated list of size strings (e.g. "10MB,20KB") into a `Vec<usize>`. -impl Pickable for Vec<usize> { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let picked_vec = args.pick_arguments(flag); - let mut result = Self::new(); - for picked in picked_vec { - let size_parse = Size::from_str(picked.as_str()); - match size_parse { - Ok(size) => result.push(usize::try_from(size.bytes()).unwrap_or(usize::MAX)), - Err(_) => return None, - } - } - Some(result) - } -} - -/// Special: dumps the remaining arguments into an `Argument` struct. -impl Pickable for Argument { - type Output = Self; - - fn pick( - args: &mut crate::parser::Argument, - _flag: mingling_core::Flag, - ) -> Option<Self::Output> { - Some(args.dump_remains().into()) - } -} - -/// Special: parses a single value of type `T` using the `Pickable` implementation for `T`, and wraps it in an `Option`. -impl<T: Pickable<Output = T> + Default> Pickable for Option<T> { - type Output = Self; - - fn pick(args: &mut Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let r = T::pick(args, flag); - Some(r) - } -} diff --git a/mingling/src/parser/picker/path.rs b/mingling/src/parser/picker/path.rs deleted file mode 100644 index 1caecfa..0000000 --- a/mingling/src/parser/picker/path.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Doc Not Optimize -use std::path::{Path, PathBuf}; - -use crate::parser::Pickable; - -mod rule; -pub use rule::*; - -impl Pickable for Vec<PathBuf> { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let raw: Vec<String> = args.pick_arguments(flag); - let paths = raw.into_iter().map(PathBuf::from).collect(); - Some(paths) - } -} - -impl Pickable for PathBuf { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let raw: String = args.pick_argument(flag)?; - Some(Self::from(raw)) - } -} - -/// Provides path checking methods for [`Vec<PathBuf>`] -/// -/// This trait automatically provides implementations for `Into<Vec<PathBuf>>` -pub trait PathsChecker { - /// Check if all paths in the list satisfy the rule - fn is_all_passed(&self, rule: &PathCheckRule) -> bool - where - Self: Into<Vec<PathBuf>> + Clone, - { - check_paths(self.clone(), rule).is_ok() - } - - /// Classify paths into (Passed, Stripped) - /// - /// Passed means paths that satisfy the rule, Stripped means paths that do not. - fn classify(self, rule: &PathCheckRule) -> (Vec<PathBuf>, Vec<PathBuf>) - where - Self: Into<Vec<PathBuf>>, - { - let paths = self.into(); - let mut passed = Vec::new(); - let mut stripped = Vec::new(); - for path in paths { - if check_path(&path, rule).is_ok() { - passed.push(path); - } else { - stripped.push(path); - } - } - (passed, stripped) - } - - /// Return paths that satisfy the rule - fn passed(self, rule: &PathCheckRule) -> Vec<PathBuf> - where - Self: Into<Vec<PathBuf>>, - { - self.classify(rule).0 - } - - /// Return paths that do not satisfy the rule - fn stripped(self, rule: &PathCheckRule) -> Vec<PathBuf> - where - Self: Into<Vec<PathBuf>>, - { - self.classify(rule).1 - } -} - -/// Provides path checking methods for [`PathBuf`] -/// -/// This trait automatically provides implementations for `Into<PathBuf>` -pub trait PathChecker { - fn is_passed(&self, rule: &PathCheckRule) -> bool - where - Self: Into<PathBuf> + Clone, - { - check_path(self.clone(), rule).is_ok() - } -} - -impl<T: Into<Vec<PathBuf>>> PathsChecker for T {} -impl<T: Into<PathBuf>> PathChecker for T {} - -fn check_paths(path: impl Into<Vec<PathBuf>>, rule: &PathCheckRule) -> Result<(), ()> { - let paths = path.into(); - for p in &paths { - check_exist(p, rule)?; - check_type(p, rule)?; - } - - Ok(()) -} - -fn check_path(path: impl Into<PathBuf>, rule: &PathCheckRule) -> Result<(), ()> { - let p = path.into(); - check_exist(&p, rule)?; - check_type(&p, rule)?; - - Ok(()) -} - -fn check_exist(path: &Path, rule: &PathCheckRule) -> Result<(), ()> { - let Some(exist_check) = &rule.exist_check else { - return Ok(()); - }; - - match exist_check { - PathExistCheck::Exists => bool_to_result(path.exists()), - PathExistCheck::NotExists => bool_to_result(!path.exists()), - } -} - -fn check_type(path: &Path, rule: &PathCheckRule) -> Result<(), ()> { - let Some(type_check) = &rule.type_check else { - return Ok(()); - }; - - let is_dir = path.is_dir(); - let is_file = path.is_file(); - let is_symlink = path.is_symlink(); - - if type_check.allow_dir && is_dir { - return Ok(()); - } - if type_check.allow_file && is_file { - return Ok(()); - } - if type_check.allow_symlink && is_symlink { - return Ok(()); - } - - Err(()) -} - -const fn bool_to_result(b: bool) -> Result<(), ()> { - if b { Ok(()) } else { Err(()) } -} diff --git a/mingling/src/parser/picker/path/rule.rs b/mingling/src/parser/picker/path/rule.rs deleted file mode 100644 index 5256f35..0000000 --- a/mingling/src/parser/picker/path/rule.rs +++ /dev/null @@ -1,231 +0,0 @@ -// Doc Not Optimize -/// Path check rule -#[derive(Default)] -pub struct PathCheckRule { - pub exist_check: Option<PathExistCheck>, - pub type_check: Option<PathTypeCheck>, -} - -/// Path existence check -pub enum PathExistCheck { - Exists, - NotExists, -} - -/// Path type check -pub struct PathTypeCheck { - /// Whether the path is allowed to be a file - pub allow_file: bool, - - /// Whether the path is allowed to be a directory - pub allow_dir: bool, - - /// Whether the path is allowed to be a symlink - pub allow_symlink: bool, -} - -impl PathCheckRule { - /// Creates a new `PathCheckRule` with default values - #[must_use] - pub const fn new() -> Self { - Self { - exist_check: None, - type_check: None, - } - } - - /// Allows the path to be a file - #[must_use] - pub const fn allow_file(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: type_check.allow_dir, - allow_symlink: type_check.allow_symlink, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: false, - allow_symlink: false, - }), - ..self - }, - } - } - - /// Allows the path to be a directory - #[must_use] - pub const fn allow_dir(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: type_check.allow_file, - allow_dir: true, - allow_symlink: type_check.allow_symlink, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: true, - allow_symlink: false, - }), - ..self - }, - } - } - - /// Allows the path to be a symlink - #[must_use] - pub const fn allow_symlink(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: type_check.allow_file, - allow_dir: type_check.allow_dir, - allow_symlink: true, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: false, - allow_symlink: true, - }), - ..self - }, - } - } - - /// Denies the path from being a file - #[must_use] - pub const fn deny_file(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: type_check.allow_dir, - allow_symlink: type_check.allow_symlink, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: true, - allow_symlink: true, - }), - ..self - }, - } - } - - /// Denies the path from being a directory - #[must_use] - pub const fn deny_dir(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: type_check.allow_file, - allow_dir: false, - allow_symlink: type_check.allow_symlink, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: false, - allow_symlink: true, - }), - ..self - }, - } - } - - /// Denies the path from being a symlink - #[must_use] - pub const fn deny_symlink(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: type_check.allow_file, - allow_dir: type_check.allow_dir, - allow_symlink: false, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: true, - allow_symlink: false, - }), - ..self - }, - } - } - - /// Requires the path to be a file (overrides type checks) - #[must_use] - pub const fn must_file(self) -> Self { - Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: false, - allow_symlink: false, - }), - ..self - } - } - - /// Requires the path to be a directory (overrides type checks) - #[must_use] - pub const fn must_dir(self) -> Self { - Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: true, - allow_symlink: false, - }), - ..self - } - } - - /// Requires the path to be a symlink (overrides type checks) - #[must_use] - pub const fn must_symlink(self) -> Self { - Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: false, - allow_symlink: true, - }), - ..self - } - } - - /// Requires the path to exist - #[must_use] - pub const fn must_exist(self) -> Self { - Self { - exist_check: Some(PathExistCheck::Exists), - ..self - } - } - - /// Requires the path to not exist - #[must_use] - pub const fn must_not_exist(self) -> Self { - Self { - exist_check: Some(PathExistCheck::NotExists), - ..self - } - } -} diff --git a/mingling/src/parser/test.rs b/mingling/src/parser/test.rs deleted file mode 100644 index 172d1da..0000000 --- a/mingling/src/parser/test.rs +++ /dev/null @@ -1,731 +0,0 @@ -// Doc Not Optimize -use crate::parser::picker::bools::{True, Yes}; -use crate::parser::{Argument, Pick1, Picker}; - -#[test] -fn test_argument_from_static_str() { - let arg: Argument = "hello".into(); - assert_eq!(arg.len(), 1); - assert_eq!(arg[0], "hello"); -} - -#[test] -fn test_argument_from_slice() { - let arg: Argument = (&["--name", "value"][..]).into(); - assert_eq!(arg.len(), 2); - assert_eq!(arg[0], "--name"); - assert_eq!(arg[1], "value"); -} - -#[test] -fn test_argument_from_array() { - let arg: Argument = ["--file", "test.txt"].into(); - assert_eq!(arg.len(), 2); -} - -#[test] -fn test_argument_from_vec() { - let arg: Argument = vec!["a".to_string(), "b".to_string()].into(); - assert_eq!(arg.len(), 2); -} - -#[test] -fn test_argument_default_is_empty() { - let arg = Argument::default(); - assert!(arg.is_empty()); -} - -#[test] -fn test_pick_argument_with_flag() { - let mut arg: Argument = vec!["--name", "Alice", "--verbose"].into(); - let value = arg.pick_argument("--name"); - assert_eq!(value, Some("Alice".to_string())); - // After picking, the flag and its value are removed - assert_eq!(arg.as_ref(), &["--verbose"]); -} - -#[test] -fn test_pick_argument_flag_not_found() { - let mut arg: Argument = vec!["--name", "Alice"].into(); - let value = arg.pick_argument("--missing"); - assert_eq!(value, None); - // Original args unchanged - assert_eq!(arg.as_ref(), &["--name", "Alice"]); -} - -#[test] -fn test_pick_argument_empty() { - let mut arg: Argument = Argument::default(); - let value = arg.pick_argument("--flag"); - assert_eq!(value, None); -} - -#[test] -fn test_pick_argument_flag_at_end_no_value() { - let mut arg: Argument = vec!["--name"].into(); - let value = arg.pick_argument("--name"); - assert_eq!(value, None); - assert!(arg.is_empty()); -} - -#[test] -fn test_pick_argument_no_flag_positional() { - let mut arg: Argument = vec!["first", "second", "--flag", "val"].into(); - let value = arg.pick_argument(()); - assert_eq!(value, Some("first".to_string())); - assert_eq!(arg.as_ref(), &["second", "--flag", "val"]); -} - -#[test] -fn test_pick_argument_positional_all() { - let mut arg: Argument = vec!["one", "two", "three"].into(); - let v1 = arg.pick_argument(()); - let v2 = arg.pick_argument(()); - let v3 = arg.pick_argument(()); - let v4 = arg.pick_argument(()); - assert_eq!(v1, Some("one".to_string())); - assert_eq!(v2, Some("two".to_string())); - assert_eq!(v3, Some("three".to_string())); - assert_eq!(v4, None); -} - -#[test] -fn test_pick_argument_empty_args_no_flag() { - let mut arg: Argument = Argument::default(); - let value = arg.pick_argument(()); - assert_eq!(value, None); -} - -#[test] -fn test_pick_argument_with_flag_from_iter() { - let mut arg: Argument = vec!["-f", "data.txt", "--other"].into(); - let value = arg.pick_argument(&["-f", "--file"][..]); - assert_eq!(value, Some("data.txt".to_string())); - assert_eq!(arg.as_ref(), &["--other"]); -} - -#[test] -fn test_pick_arguments_multiple_values() { - let mut arg: Argument = vec!["--files", "a.txt", "b.txt", "c.txt", "--other"].into(); - let values = arg.pick_arguments("--files"); - assert_eq!(values, vec!["a.txt", "b.txt", "c.txt"]); - assert_eq!(arg.as_ref(), &["--other"]); -} - -#[test] -fn test_pick_arguments_single_value() { - let mut arg: Argument = vec!["--name", "Alice", "--verbose"].into(); - let values = arg.pick_arguments("--name"); - assert_eq!(values, vec!["Alice"]); - assert_eq!(arg.as_ref(), &["--verbose"]); -} - -#[test] -fn test_pick_arguments_no_values() { - let mut arg: Argument = vec!["--flag", "--other", "val"].into(); - let values = arg.pick_arguments("--flag"); - assert!(values.is_empty()); - assert_eq!(arg.as_ref(), &["--other", "val"]); -} - -#[test] -fn test_pick_arguments_flag_not_found() { - let mut arg: Argument = vec!["--name", "Alice"].into(); - let values = arg.pick_arguments("--missing"); - assert!(values.is_empty()); - assert_eq!(arg.as_ref(), &["--name", "Alice"]); -} - -#[test] -fn test_pick_arguments_stops_at_next_flag() { - let mut arg: Argument = vec!["--list", "a", "b", "-c", "d", "e"].into(); - let values = arg.pick_arguments("--list"); - assert_eq!(values, vec!["a", "b"]); - assert_eq!(arg.as_ref(), &["-c", "d", "e"]); -} - -#[test] -fn test_pick_arguments_empty_flag_positional() { - let mut arg: Argument = vec!["pos1", "pos2", "--flag", "val"].into(); - let values = arg.pick_arguments(()); - assert_eq!(values, vec!["pos1", "pos2"]); - assert_eq!(arg.as_ref(), &["--flag", "val"]); -} - -#[test] -fn test_pick_arguments_empty_args() { - let mut arg: Argument = Argument::default(); - let values = arg.pick_arguments("--flag"); - assert!(values.is_empty()); -} - -#[test] -fn test_pick_flag_found() { - let mut arg: Argument = vec!["--verbose", "--name", "Alice"].into(); - let result = arg.pick_flag("--verbose"); - assert!(result); - assert_eq!(arg.as_ref(), &["--name", "Alice"]); -} - -#[test] -fn test_pick_flag_not_found() { - let mut arg: Argument = vec!["--name", "Alice"].into(); - let result = arg.pick_flag("--verbose"); - assert!(!result); - assert_eq!(arg.as_ref(), &["--name", "Alice"]); -} - -#[test] -fn test_pick_flag_empty_args() { - let mut arg: Argument = Argument::default(); - let result = arg.pick_flag("--flag"); - assert!(!result); -} - -#[test] -fn test_pick_flag_with_flag_iter() { - let mut arg: Argument = vec!["-h", "--name", "Alice"].into(); - let result = arg.pick_flag(&["-h", "--help"][..]); - assert!(result); -} - -#[test] -fn test_pick_flag_second_not_first() { - let mut arg: Argument = vec!["--name", "Alice"].into(); - let result = arg.pick_flag(&["-h", "--help"][..]); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_yes() { - let mut arg: Argument = vec!["yes"].into(); - let result = arg.pick_flag(()); - assert!(result); - assert!(arg.is_empty()); -} - -#[test] -fn test_pick_flag_positional_no() { - let mut arg: Argument = vec!["no"].into(); - let result = arg.pick_flag(()); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_true() { - let mut arg: Argument = vec!["true"].into(); - let result = arg.pick_flag(()); - assert!(result); -} - -#[test] -fn test_pick_flag_positional_false() { - let mut arg: Argument = vec!["false"].into(); - let result = arg.pick_flag(()); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_1() { - let mut arg: Argument = vec!["1"].into(); - let result = arg.pick_flag(()); - assert!(result); -} - -#[test] -fn test_pick_flag_positional_0() { - let mut arg: Argument = vec!["0"].into(); - let result = arg.pick_flag(()); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_unknown() { - let mut arg: Argument = vec!["unknown_value"].into(); - let result = arg.pick_flag(()); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_case_insensitive_yes() { - let mut arg: Argument = vec!["YeS"].into(); - let result = arg.pick_flag(()); - assert!(result); -} - -#[test] -fn test_dump_remains() { - let mut arg: Argument = vec!["a", "b", "c"].into(); - let remains = arg.dump_remains(); - assert_eq!(remains, vec!["a", "b", "c"]); - assert!(arg.is_empty()); -} - -#[test] -fn test_dump_remains_empty() { - let mut arg: Argument = Argument::default(); - let remains = arg.dump_remains(); - assert!(remains.is_empty()); -} - -#[test] -fn test_dump_remains_after_pick() { - let mut arg: Argument = vec!["--flag", "value", "extra"].into(); - let _ = arg.pick_argument("--flag"); - let remains = arg.dump_remains(); - assert_eq!(remains, vec!["extra"]); -} - -#[test] -fn test_strip_all_flags() { - let arg: Argument = vec!["--verbose", "file.txt", "--format", "json"].into(); - let result = arg.strip_all_flags(); - assert_eq!(result.as_ref(), &["file.txt", "json"]); -} - -#[test] -fn test_strip_all_flags_no_flags() { - let arg: Argument = vec!["just", "positional", "args"].into(); - let result = arg.strip_all_flags(); - assert_eq!(result.as_ref(), &["just", "positional", "args"]); -} - -#[test] -fn test_strip_all_flags_all_flags() { - let arg: Argument = vec!["--a", "-b", "--c"].into(); - let result = arg.strip_all_flags(); - assert!(result.is_empty()); -} - -#[test] -fn test_strip_all_flags_empty() { - let arg: Argument = Argument::default(); - let result = arg.strip_all_flags(); - assert!(result.is_empty()); -} - -#[test] -fn test_picker_new() { - let picker = Picker::new(vec!["--name", "Alice"]); - assert_eq!(picker.args.len(), 2); -} - -#[test] -fn test_picker_from_trait() { - let picker: Picker = vec!["--name", "Alice"].into(); - assert_eq!(picker.args.len(), 2); -} - -#[test] -fn test_picker_pick_string() { - let result: String = Picker::new(vec!["--name", "Alice"]).pick("--name").unpack(); - assert_eq!(result, "Alice"); -} - -#[test] -fn test_picker_pick_string_default_when_missing() { - let result: String = Picker::new(vec!["--other", "val"]) - .pick::<String>("--name") - .unpack(); - assert_eq!(result, ""); -} - -#[test] -fn test_picker_pick_string_default_when_missing_with_or() { - let result: String = Picker::new(vec!["--other", "val"]) - .pick_or("--name", "default_name") - .unpack(); - assert_eq!(result, "default_name"); -} - -#[test] -fn test_picker_pick_bool_flag_present() { - let result: bool = Picker::new(vec!["--verbose", "--name", "Alice"]) - .pick::<bool>("--verbose") - .unpack(); - assert!(result); -} - -#[test] -fn test_picker_pick_bool_flag_absent() { - let result: bool = Picker::new(vec!["--name", "Alice"]) - .pick::<bool>("--verbose") - .unpack(); - assert!(!result); -} - -#[test] -fn test_picker_pick_i32() { - let result: i32 = Picker::new(vec!["--count", "42"]).pick("--count").unpack(); - assert_eq!(result, 42); -} - -#[test] -fn test_picker_pick_i32_default_zero() { - let result: i32 = Picker::new(vec!["--other"]).pick::<i32>("--count").unpack(); - assert_eq!(result, 0); -} - -#[test] -fn test_picker_pick_f64() { - let result: f64 = Picker::new(vec!["--ratio", "5.16"]) - .pick("--ratio") - .unpack(); - let expected: f64 = 5.16; - assert!((result - expected).abs() < 1e-10); -} - -#[test] -fn test_picker_pick_u64() { - let result: u64 = Picker::new(vec!["--size", "100"]).pick("--size").unpack(); - assert_eq!(result, 100); -} - -#[test] -fn test_picker_pick_i32_parse_failure_returns_default() { - let result: i32 = Picker::new(vec!["--count", "not-a-number"]) - .pick::<i32>("--count") - .unpack(); - assert_eq!(result, 0); -} - -#[test] -fn test_picker_pick_usize_bytes() { - let result: usize = Picker::new(vec!["--limit", "1024"]) - .pick("--limit") - .unpack(); - assert_eq!(result, 1024); -} - -#[test] -fn test_picker_pick_usize_kib() { - let result: usize = Picker::new(vec!["--limit", "1KiB"]) - .pick("--limit") - .unpack(); - assert_eq!(result, 1024); -} - -#[test] -fn test_picker_pick_usize_mib() { - let result: usize = Picker::new(vec!["--limit", "2MiB"]) - .pick("--limit") - .unpack(); - assert_eq!(result, 2 * 1024 * 1024); -} - -#[test] -fn test_picker_pick_usize_parse_failure_returns_default() { - let result: usize = Picker::new(vec!["--limit", "invalid"]) - .pick::<usize>("--limit") - .unpack(); - assert_eq!(result, 0); -} - -#[test] -fn test_picker_pick_vec_string() { - let result: Vec<String> = Picker::new(vec!["--files", "a.txt", "b.txt", "c.txt"]) - .pick("--files") - .unpack(); - assert_eq!(result, vec!["a.txt", "b.txt", "c.txt"]); -} - -#[test] -fn test_picker_pick_vec_string_missing() { - let result: Vec<String> = Picker::new(vec!["--other", "val"]) - .pick::<Vec<String>>("--files") - .unpack(); - assert!(result.is_empty()); -} - -#[test] -fn test_picker_pick_vec_usize() { - let result: Vec<usize> = Picker::new(vec!["--sizes", "100", "1KiB", "2MiB"]) - .pick("--sizes") - .unpack(); - assert_eq!(result, vec![100, 1024, 2 * 1024 * 1024]); -} - -#[test] -fn test_picker_pick_vec_i32() { - let result: Vec<i32> = Picker::new(vec!["--nums", "10", "20", "30"]) - .pick("--nums") - .unpack(); - assert_eq!(result, vec![10, 20, 30]); -} - -#[test] -fn test_picker_pick_yes_yes() { - let result: Yes = Picker::new(vec!["--flag", "y"]).pick("--flag").unpack(); - assert!(result.is_yes()); - assert!(*result); -} - -#[test] -fn test_picker_pick_yes_no() { - let result: Yes = Picker::new(vec!["--flag", "no"]).pick("--flag").unpack(); - assert!(result.is_no()); - assert!(!*result); -} - -#[test] -fn test_picker_pick_yes_default_no() { - let result: Yes = Picker::new(vec!["--other"]).pick::<Yes>("--flag").unpack(); - assert!(result.is_no()); -} - -#[test] -fn test_picker_pick_true_true() { - let result: True = Picker::new(vec!["--flag", "true"]).pick("--flag").unpack(); - assert!(result.is_true()); - assert!(*result); -} - -#[test] -fn test_picker_pick_true_false() { - let result: True = Picker::new(vec!["--flag", "anything"]) - .pick("--flag") - .unpack(); - assert!(result.is_false()); - assert!(!*result); -} - -#[test] -fn test_picker_pick_true_default_false() { - let result: True = Picker::new(vec!["--other"]).pick::<True>("--flag").unpack(); - assert!(result.is_false()); -} - -#[test] -fn test_picker_pick_or_fallback() { - let result: String = Picker::new(vec!["--other", "val"]) - .pick_or("--name", "fallback") - .unpack(); - assert_eq!(result, "fallback"); -} - -#[test] -fn test_picker_pick_or_existing() { - let result: String = Picker::new(vec!["--name", "Alice"]) - .pick_or("--name", "fallback") - .unpack(); - assert_eq!(result, "Alice"); -} - -#[test] -fn test_picker_pick_or_numeric_fallback() { - let result: i32 = Picker::new(vec!["--other"]).pick_or("--count", 99).unpack(); - assert_eq!(result, 99); -} - -#[test] -fn test_picker_pick_or_route_present() { - let result = Picker::new(vec!["--name", "Alice"]) - .pick_or_route::<String, _>("--name", "missing_name") - .unpack(); - assert_eq!(result, Ok("Alice".to_string())); -} - -#[test] -fn test_picker_pick_or_route_missing() { - let result = Picker::new(vec!["--other"]) - .pick_or_route::<String, _>("--name", "missing_name") - .unpack(); - assert_eq!(result, Err("missing_name")); -} - -#[test] -fn test_picker_require_present() { - let result: Option<String> = Picker::new(vec!["--name", "Alice"]) - .require::<String>("--name") - .map(super::picker::Pick1::unpack); - assert_eq!(result, Some("Alice".to_string())); -} - -#[test] -fn test_picker_require_missing() { - let result: Option<Pick1<String>> = Picker::new(vec!["--other"]).require::<String>("--name"); - assert!(result.is_none()); -} - -#[test] -fn test_picker_chaining_two_values() { - let (name, count): (String, i32) = Picker::new(vec!["--name", "Alice", "--count", "42"]) - .pick::<String>("--name") - .pick::<i32>("--count") - .unpack(); - assert_eq!(name, "Alice"); - assert_eq!(count, 42); -} - -#[test] -fn test_picker_chaining_three_values() { - let (_name, _verbose, count): (String, bool, i32) = - Picker::new(vec!["--name", "Alice", "--count", "42", "--verbose"]) - .pick::<String>("--name") - .pick::<bool>("--verbose") - .pick::<i32>("--count") - .unpack(); - assert_eq!(count, 42); -} - -#[test] -fn test_picker_chaining_with_pick_or() { - let (name, count): (String, i32) = Picker::new(vec!["--name", "Alice"]) - .pick::<String>("--name") - .pick_or("--count", 10) - .unpack(); - assert_eq!(name, "Alice"); - assert_eq!(count, 10); -} - -#[test] -fn test_picker_chaining_with_mixed_flag_styles() { - let (name, verbose): (String, bool) = Picker::new(vec!["-n", "Bob", "--verbose"]) - .pick::<String>("-n") - .pick::<bool>("--verbose") - .unpack(); - assert_eq!(name, "Bob"); - assert!(verbose); -} - -#[test] -fn test_pick_after_modification() { - let result: String = Picker::new(vec!["--name", " Alice "]) - .pick::<String>("--name") - .after(|s| s.trim().to_string()) - .unpack(); - assert_eq!(result, "Alice"); -} - -#[test] -fn test_pick_after_chained() { - let (name, count): (String, i32) = Picker::new(vec!["--name", "alice", "--count", "7"]) - .pick::<String>("--name") - .after(|s| s.to_uppercase()) - .pick::<i32>("--count") - .after(|n| n * 2) - .unpack(); - assert_eq!(name, "ALICE"); - assert_eq!(count, 14); -} - -#[test] -fn test_pick_after_or_route_ok() { - let result = Picker::new(vec!["--name", "Alice"]) - .pick::<String>("--name") - .after_or_route(|s| { - if s.len() > 3 { - Ok(s.clone()) - } else { - Err("too_short") - } - }) - .unpack(); - assert_eq!(result, Ok("Alice".to_string())); -} - -#[test] -fn test_pick_after_or_route_err() { - let result = Picker::new(vec!["--name", "Ab"]) - .pick::<String>("--name") - .after_or_route(|s| { - if s.len() > 3 { - Ok(s.clone()) - } else { - Err("too_short") - } - }) - .unpack(); - assert_eq!(result, Err("too_short")); -} - -#[test] -fn test_pick_with_route_unpack_ok() { - let result = Picker::new(vec!["--name", "Alice"]) - .pick_or_route::<String, _>("--name", "error") - .unpack(); - assert_eq!(result, Ok("Alice".to_string())); -} - -#[test] -fn test_pick_with_route_unpack_err() { - let result: Result<String, &str> = Picker::new(vec!["--other"]) - .pick_or_route::<String, _>("--name", "missing") - .unpack(); - assert_eq!(result, Err("missing")); -} - -#[test] -fn test_pick_with_route_unpack_directly() { - let result: String = Picker::new(vec!["--other"]) - .pick_or_route::<String, _>("--name", "fallback_in_route") - .unpack_directly(); - // When route is set, unpack_directly returns the default value (empty string for String) - assert_eq!(result, ""); -} - -#[test] -fn test_pick_with_route_chaining_present() { - let result = Picker::new(vec!["--name", "Alice", "--count", "42"]) - .pick_or_route::<String, _>("--name", "err_name") - .pick::<i32>("--count") - .unpack(); - assert_eq!(result, Ok(("Alice".to_string(), 42))); -} - -#[test] -fn test_pick_with_route_chaining_missing_first_route_propagates() { - let result = Picker::new(vec!["--count", "42"]) - .pick_or_route::<String, _>("--name", "err_name") - .pick::<i32>("--count") - .unpack(); - assert_eq!(result, Err("err_name")); -} - -#[test] -fn test_pick_with_route_chaining_pick_or_route_second_missing() { - let result = Picker::new(vec!["--name", "Alice"]) - .pick_or_route::<String, _>("--name", "err_name") - .pick_or_route::<i32>("--count", "err_count") - .unpack(); - assert_eq!(result, Err("err_count")); -} - -#[test] -fn test_pick_with_route_after_or_route_preserves_existing_route() { - let result = Picker::new(vec!["--other"]) - .pick_or_route::<String, _>("--name", "missing_name") - .after_or_route(|_s: &String| { - // This won't be called because route is already set, but let's see behavior - Ok("should_not_matter".to_string()) - }) - .unpack(); - assert_eq!(result, Err("missing_name")); -} - -#[test] -fn test_picker_operate_args_filter() { - let result: String = Picker::new(vec!["--name", "Alice", "--verbose"]) - .operate_args(Argument::strip_all_flags) - .pick_or("--name", "fallback_name") - .unpack(); - // After stripping flags, "--name" and "--verbose" are gone, "Alice" is a positional arg. - // But --name with a value won't be present as a flag, so it falls back to positional. - // Actually, strip_all_flags removes anything starting with '-'. - // So "--name" is removed, and "Alice" remains as a positional argument. - // When we try to pick "--name", it won't find it, so we get the fallback. - assert_eq!(result, "fallback_name"); -} - -#[test] -fn test_picker_operate_args_transform() { - let result: Vec<String> = Picker::new(vec!["--files", "a.txt", "b.txt", "c.txt"]) - .operate_args(|mut args| { - // Add an extra file - args.push("d.txt".to_string()); - args - }) - .pick::<Vec<String>>("--files") - .unpack(); - assert_eq!(result, vec!["a.txt", "b.txt", "c.txt", "d.txt"]); -} diff --git a/mingling/src/setups/dirs.rs b/mingling/src/setups/dirs.rs index ea7f282..65196c2 100644 --- a/mingling/src/setups/dirs.rs +++ b/mingling/src/setups/dirs.rs @@ -1,37 +1,53 @@ -// Doc Not Optimize -use std::marker::PhantomData; - -use mingling_core::{ProgramCollect, setup::ProgramSetup}; +use mingling_core::{Program, ProgramCollect, setup::ProgramSetup}; use crate::res::{ResCurrentDir, ResCurrentExe, ResHomeDir, ResTempDir}; -/// Provides the ability to set up commonly used directory resources for the program. -/// -/// This setup item registers the following directory resources in the program: -/// - `ResCurrentDir`: Current working directory -/// - `ResCurrentExe`: Directory containing the executable -/// - `ResHomeDir`: User's home directory -/// - `ResTempDir`: Temporary directory -pub struct DirectoryEnvironmentSetup<C> { - _collect: PhantomData<C>, -} - -impl<C> Default for DirectoryEnvironmentSetup<C> -where - C: ProgramCollect<Enum = C> + 'static, -{ - fn default() -> Self { - Self { - _collect: PhantomData, - } - } -} +/// `Directory Environment` Setup for managing common directory resources +/// +/// This Setup registers commonly used directory resources into the program's +/// resource store. It provides the current working directory, the executable's +/// directory, the user's home directory, and the system's temporary directory, +/// so that these paths can be retrieved from the resource store without +/// recomputing them each time. +/// +/// # Usage +/// +/// This Setup can be registered using the +/// [`Program`](https://docs.rs/mingling/latest/mingling/struct.Program.html) +/// `with_setup` method, for example: +/// +/// ```rust +/// # use mingling::MockProgramCollect as ThisProgram; +/// use mingling::Program; +/// use mingling::setup::DirectoryEnvironmentSetup; +/// +/// let mut program = Program::<ThisProgram>::new(); +/// program.with_setup(DirectoryEnvironmentSetup); +/// ``` +/// +/// # Behavior +/// +/// - Registers an [`ResCurrentDir`] resource containing the current working +/// directory. +/// - Registers an [`ResCurrentExe`] resource containing the directory of the +/// currently running executable. +/// - Registers an [`ResHomeDir`] resource containing the user's home directory. +/// - Registers an [`ResTempDir`] resource containing the system's temporary +/// directory. +/// +/// # Notes +/// +/// - All directory values are resolved at setup time and stored in the +/// resource store. +/// - These resources can be retrieved later using the program's `resource` +/// accessor with the corresponding resource type. +pub struct DirectoryEnvironmentSetup; -impl<C> ProgramSetup<C> for DirectoryEnvironmentSetup<C> +impl<C> ProgramSetup<C> for DirectoryEnvironmentSetup where C: ProgramCollect<Enum = C> + 'static, { - fn setup(self, program: &mut crate::Program<C>) { + fn setup(self, program: &mut Program<C>) { program.with_resource(ResCurrentDir::default()); program.with_resource(ResCurrentExe::default()); program.with_resource(ResHomeDir::default()); diff --git a/mingling/src/setups/exit_code.rs b/mingling/src/setups/exit_code.rs index e31e511..49d5f9f 100644 --- a/mingling/src/setups/exit_code.rs +++ b/mingling/src/setups/exit_code.rs @@ -1,8 +1,5 @@ -// Doc Not Optimize -use std::marker::PhantomData; - use mingling_core::{ - ProgramCollect, + Program, ProgramCollect, hook::{ProgramControlUnit, ProgramControls, ProgramHook}, setup::ProgramSetup, this, @@ -10,30 +7,43 @@ use mingling_core::{ use crate::res::ResExitCode; -/// Provides the ability to control the program's exit code, which is returned when the program ends. +/// `ExitCodeSetup` — Setup for controlling the program's exit code /// -/// - Use `mingling::update_exit_code` to update the exit code. -/// - Use `mingling::current_exit_code` to query the current exit code. -pub struct ExitCodeSetup<C> { - _collect: PhantomData<C>, -} - -impl<C> Default for ExitCodeSetup<C> -where - C: ProgramCollect<Enum = C> + 'static, -{ - fn default() -> Self { - Self { - _collect: PhantomData, - } - } -} +/// This Setup registers an [`ResExitCode`] resource that tracks the desired exit +/// code for the program. When the program finishes, a hook reads this resource +/// and overrides the program's exit code if it has been modified from its +/// default value of `0`. +/// +/// # Usage +/// +/// This Setup can be registered using the +/// [`Program`](https://docs.rs/mingling/latest/mingling/struct.Program.html) +/// `with_setup` method, for example: +/// +/// ```rust +/// # use mingling::MockProgramCollect as ThisProgram; +/// use mingling::Program; +/// use mingling::setup::ExitCodeSetup; +/// +/// let mut program = Program::<ThisProgram>::new(); +/// program.with_setup(ExitCodeSetup); +/// ``` +/// +/// # Behavior +/// +/// - Registers an [`ResExitCode`] resource initialised to `0`. +/// - Installs a program-finish hook that: +/// - Reads the current [`ResExitCode`] value. +/// - Overrides the program's exit code with that value if it is non-zero. +/// - Leaves the exit code untouched if the resource still holds its default +/// value of `0`. +pub struct ExitCodeSetup; -impl<C> ProgramSetup<C> for ExitCodeSetup<C> +impl<C> ProgramSetup<C> for ExitCodeSetup where C: ProgramCollect<Enum = C> + 'static, { - fn setup(self, program: &mut crate::Program<C>) { + fn setup(self, program: &mut Program<C>) { // Insert resource program.with_resource(ResExitCode { exit_code: 0 }); |
