aboutsummaryrefslogtreecommitdiff
path: root/mingling/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling/src')
-rw-r--r--mingling/src/docs/lib.md10
-rw-r--r--mingling/src/example_docs.rs1174
-rw-r--r--mingling/src/features.rs66
-rw-r--r--mingling/src/gen_program.rs34
-rw-r--r--mingling/src/lib.rs39
-rw-r--r--mingling/src/parser.rs12
-rw-r--r--mingling/src/parser/args.rs177
-rw-r--r--mingling/src/parser/picker.rs815
-rw-r--r--mingling/src/parser/picker/bools.rs143
-rw-r--r--mingling/src/parser/picker/builtin.rs113
-rw-r--r--mingling/src/parser/picker/path.rs145
-rw-r--r--mingling/src/parser/picker/path/rule.rs231
-rw-r--r--mingling/src/parser/test.rs731
-rw-r--r--mingling/src/setups/dirs.rs70
-rw-r--r--mingling/src/setups/exit_code.rs56
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 });