diff options
Diffstat (limited to 'mingling')
25 files changed, 1347 insertions, 240 deletions
diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml index be223fe..adf900f 100644 --- a/mingling/Cargo.toml +++ b/mingling/Cargo.toml @@ -22,28 +22,35 @@ mingling = { path = ".", features = [ [package.metadata.docs.rs] features = [ + "core", + "macros", "builds", "structural_renderer", "repl", "comp", "parser", + "picker", "clap", "extra_macros", ] [features] +core = ["dep:mingling_core", "mingling_core/default"] +macros = ["dep:mingling_macros", "mingling_macros/default"] + nightly = ["mingling_core/nightly", "mingling_macros/nightly"] debug = ["mingling_core/debug"] async = ["mingling_core/async", "mingling_macros/async"] builds = ["mingling_core/builds"] -default = ["mingling_core/default", "mingling_macros/default"] +default = ["core", "macros"] clap = ["mingling_core/clap", "mingling_macros/clap"] dispatch_tree = ["mingling_core/dispatch_tree", "mingling_macros/dispatch_tree"] repl = ["mingling_core/repl", "mingling_macros/repl"] comp = ["mingling_core/comp", "mingling_macros/comp"] parser = ["dep:size"] +picker = ["dep:arg-picker", "arg-picker/mingling_support"] pathf = ["mingling_core/pathf", "mingling_macros/pathf"] structural_renderer = [ @@ -81,7 +88,8 @@ ron_serde_fmt = ["mingling_core/ron_serde_fmt"] extra_macros = ["mingling_macros/extra_macros"] [dependencies] -mingling_core.workspace = true -mingling_macros.workspace = true +mingling_core = { workspace = true, optional = true } +mingling_macros = { workspace = true, optional = true } +arg-picker = { workspace = true, optional = true } serde = { workspace = true, optional = true } size = { version = "0.5", optional = true } diff --git a/mingling/src/constants.rs b/mingling/src/constants.rs new file mode 100644 index 0000000..65d02e8 --- /dev/null +++ b/mingling/src/constants.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "picker")] +mod picker; + +#[cfg(feature = "picker")] +pub use picker::*; + +mod exit_codes; +pub use exit_codes::*; diff --git a/mingling/src/constants/exit_codes.rs b/mingling/src/constants/exit_codes.rs new file mode 100644 index 0000000..2359f42 --- /dev/null +++ b/mingling/src/constants/exit_codes.rs @@ -0,0 +1,14 @@ +/// Exit code indicating successful command execution. +pub const EXIT_SUCCESS: i32 = 0; + +/// Exit code for general errors. +pub const EXIT_GENERAL_ERR: i32 = 1; + +/// Exit code for incorrect command usage (or invalid arguments). +pub const EXIT_USAGE_ERR: i32 = 2; + +/// Exit code indicating permission denied (or the command is not executable). +pub const EXIT_PERM_DENIED: i32 = 126; + +/// Exit code for command not found (or PATH error). +pub const EXIT_CMD_NOT_FOUND: i32 = 127; diff --git a/mingling/src/constants/picker.rs b/mingling/src/constants/picker.rs new file mode 100644 index 0000000..f2a448b --- /dev/null +++ b/mingling/src/constants/picker.rs @@ -0,0 +1,129 @@ +use std::marker::PhantomData; + +use arg_picker::{PickerArg, PickerArgs, value::Flag}; + +/// Remaining positional arguments (anything not consumed as an option). +/// - `full`: `[]` (empty — not triggered by any `--` prefix). +/// - `short`: (none) +/// - `positional`: `false` (this is a meta‑argument that collects everything left). +/// This constant is used internally to access any leftover arguments after +/// all defined flags/options have been processed. +pub const REMAINS: PickerArg<PickerArgs> = PickerArg::<PickerArgs> { + full: &[], + short: None, + positional: false, + internal_type: PhantomData, +}; + +/// Help flag: display usage information. +/// - `full`: `["help"]` +/// - `short`: `'h'` +pub const HELP_FLAG: PickerArg<Flag> = PickerArg::<Flag> { + full: &["help"], + short: Some('h'), + positional: false, + internal_type: PhantomData, +}; + +/// Quiet flag: suppress output. +/// - `full`: `["quiet"]` +/// - `short`: `'q'` +pub const QUIET_FLAG: PickerArg<Flag> = PickerArg::<Flag> { + full: &["quiet"], + short: Some('q'), + positional: false, + internal_type: PhantomData, +}; + +/// Confirm flag: require user confirmation before proceeding. +/// - `full`: `["confirm"]` +/// - `short`: `'C'` +pub const CONFIRM_FLAG: PickerArg<Flag> = PickerArg::<Flag> { + full: &["confirm"], + short: Some('C'), + positional: false, + internal_type: PhantomData, +}; + +/// Renderer flag: explicitly specify the Structural Renderer argument. +/// - `full`: `["renderer"]` +/// - `short`: (none) +#[cfg(feature = "structural_renderer")] +pub const RENDERER_ARG: PickerArg<String> = PickerArg::<String> { + full: &["renderer"], + short: None, + positional: false, + internal_type: PhantomData, +}; + +/// JSON flag: enable JSON output format. +/// - `full`: `["json"]` +/// - `short`: (none) +/// Available only when the `json_serde_fmt` feature is enabled. +#[cfg(feature = "json_serde_fmt")] +pub const JSON_FLAG: PickerArg<Flag> = PickerArg::<Flag> { + full: &["json"], + short: None, + positional: false, + internal_type: PhantomData, +}; + +/// JSON pretty flag: enable pretty-printed JSON output format. +/// - `full`: `["json_pretty"]` +/// - `short`: (none) +/// Available only when the `json_serde_fmt` feature is enabled. +#[cfg(feature = "json_serde_fmt")] +pub const JSON_PRETTY_FLAG: PickerArg<Flag> = PickerArg::<Flag> { + full: &["json_pretty"], + short: None, + positional: false, + internal_type: PhantomData, +}; + +/// YAML flag: enable YAML output format. +/// - `full`: `["yaml"]` +/// - `short`: (none) +/// Available only when the `yaml_serde_fmt` feature is enabled. +#[cfg(feature = "yaml_serde_fmt")] +pub const YAML_FLAG: PickerArg<Flag> = PickerArg::<Flag> { + full: &["yaml"], + short: None, + positional: false, + internal_type: PhantomData, +}; + +/// TOML flag: enable TOML output format. +/// - `full`: `["toml"]` +/// - `short`: (none) +/// Available only when the `toml_serde_fmt` feature is enabled. +#[cfg(feature = "toml_serde_fmt")] +pub const TOML_FLAG: PickerArg<Flag> = PickerArg::<Flag> { + full: &["toml"], + short: None, + positional: false, + internal_type: PhantomData, +}; + +/// RON flag: enable RON output format. +/// - `full`: `["ron"]` +/// - `short`: (none) +/// Available only when the `ron_serde_fmt` feature is enabled. +#[cfg(feature = "ron_serde_fmt")] +pub const RON_FLAG: PickerArg<Flag> = PickerArg::<Flag> { + full: &["ron"], + short: None, + positional: false, + internal_type: PhantomData, +}; + +/// RON pretty flag: enable pretty-printed RON output format. +/// - `full`: `["ron_pretty"]` +/// - `short`: (none) +/// Available only when the `ron_serde_fmt` feature is enabled. +#[cfg(feature = "ron_serde_fmt")] +pub const RON_PRETTY_FLAG: PickerArg<Flag> = PickerArg::<Flag> { + full: &["ron_pretty"], + short: None, + positional: false, + internal_type: PhantomData, +}; diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index 92b13a0..bdefb5a 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -39,6 +39,7 @@ /// Source code (./src/main.rs) /// ```ignore /// use mingling::{macros::route, prelude::*}; +/// use std::io::Write; /// /// dispatcher!("transfer", CMDTransfer => EntryTransfer); /// dispatcher!("strict-transfer", CMDStrictTransfer => EntryStrictTransfer); @@ -65,7 +66,7 @@ /// // Convert into ResultFile /// .into(); /// // --------- IMPORTANT --------- -/// result +/// result.into() /// } /// /// pack!(ErrorNoNameProvided = ()); @@ -91,20 +92,26 @@ /// /// /// Renders the parsed transfer result (file/dir, size, name). /// #[renderer] -/// fn render_result_file(result: ResultFile) { +/// fn render_result_file(result: ResultFile) -> RenderResult { /// let (is_dir, size, name) = result.into(); -/// r_println!( +/// 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) { -/// r_println!("Error: name is not provided") +/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { +/// let mut result = RenderResult::new(); +/// writeln!(result, "Error: name is not provided").ok(); +/// result /// } /// /// gen_program!(); @@ -117,6 +124,251 @@ /// } /// ``` 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. +/// +/// Run: +/// ```bash +/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 + 1 +/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 7 * 7 +/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc +/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 +/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 + +/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3 +/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3 --round +/// ``` +/// +/// Output: +/// ```plaintext +/// Result: 2 +/// Result: 49 +/// Error: First number (number_a) was not provided. +/// Error: Operator was not provided. +/// Error: Second number (number_b) was not provided. +/// Result: 1.3333334 +/// Result: 1 +/// ``` +/// +/// Source code (./Cargo.toml) +/// ```toml +/// [package] +/// name = "example-argument-picker" +/// version = "0.1.0" +/// edition = "2024" +/// +/// [dependencies.mingling] +/// path = "../../mingling" +/// +/// # Enable `picker` features +/// features = ["picker", "extra_macros"] +/// +/// [workspace] +/// ``` +/// +/// Source code (./src/main.rs) +/// ```ignore +/// use mingling::{ +/// consts::REMAINS, +/// macros::route, +/// picker::{ +/// IntoPicker, PickerArgResult, SinglePickable, +/// parselib::{ParserStyle, UNIX_STYLE}, +/// value::Flag, +/// }, +/// prelude::*, +/// }; +/// +/// // --------- IMPORTANT --------- +/// // Use picker::BasicProgramSetup instead of the original BasicProgramSetup +/// // It uses arg-picker to rewrite the logic of the original BasicProgramSetup +/// use mingling::setup::picker::BasicProgramSetup; +/// +/// // --------- IMPORTANT --------- +/// +/// dispatcher!("calc", CMDCalculate => EntryCalculate); +/// +/// pack_err!(ErrorNumberANotProvided); +/// pack_err!(ErrorNumberBNotProvided); +/// pack_err!(ErrorNumberOperatorNotProvided); +/// pack_err!(ErrorDivisionByZero); +/// +/// pack!(StateAdd = (f32, f32)); +/// pack!(StateSubtract = (f32, f32)); +/// pack!(StateMultiply = (f32, f32)); +/// pack!(StateDivide = (f32, f32)); +/// +/// pack!(ResultNumber = f32); +/// +/// #[derive(Grouped)] +/// struct StateCalculate { +/// number_a: f32, +/// operator: Operator, +/// number_b: f32, +/// } +/// +/// #[derive(Debug, PartialEq, Eq)] +/// enum Operator { +/// Plus, +/// Dash, +/// Slash, +/// Star, +/// } +/// +/// // --------- IMPORTANT --------- +/// // Define SinglePickable for type Operator +/// // This allows the type to be picked as an argument +/// impl SinglePickable for Operator { +/// fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { +/// let Some(str) = str else { +/// return PickerArgResult::NotFound; +/// }; +/// let op = match str.chars().next() { +/// Some('+') => Operator::Plus, +/// Some('-') => Operator::Dash, +/// Some('*') => Operator::Star, +/// Some('/') => Operator::Slash, +/// _ => return PickerArgResult::NotFound, +/// }; +/// PickerArgResult::Parsed(op) +/// } +/// } +/// // --------- IMPORTANT --------- +/// +/// #[derive(Default, Clone)] +/// struct ResNumberDisplaySetting { +/// round: bool, +/// } +/// +/// fn main() { +/// let mut program = ThisProgram::new(); +/// +/// // Use ParserStyle to manage the arg-picker theme +/// ParserStyle::set_global_style(&UNIX_STYLE); +/// +/// // Enable picker::BasicProgramSetup +/// program.with_setup(BasicProgramSetup); +/// +/// // --------- IMPORTANT --------- +/// // Pre-process global arguments before executing commands +/// let (round, args) = program +/// .take_args() +/// // Use arg![round: Flag] to indicate the `--round` | `-R` flag +/// // | +/// // vvvvvvvvvvvvvvvv +/// .pick(&arg![round: Flag, 'R']) +/// // Use REMAINS to extract remaining arguments +/// // | +/// // vvvvvvvv +/// .pick(&REMAINS) +/// // Since Flag and REMAINS will not fail to parse, +/// // we can safely unwrap here +/// .unwrap(); +/// program.replace_args(args.into()); +/// +/// program.with_resource(ResNumberDisplaySetting { round: *round }); +/// // --------- IMPORTANT --------- +/// +/// program.with_dispatcher(CMDCalculate); +/// program.exec_and_exit(); +/// } +/// +/// #[chain] +/// fn handle_calc(args: EntryCalculate) -> Next { +/// // --------- IMPORTANT --------- +/// let (number_a, operator, number_b) = route!( +/// // Use the arg! macro to define a positional argument of type f32 +/// // | +/// // vvvvvvvvvv +/// args.pick_or_route(&arg![f32], || ErrorNumberANotProvided::default().to_chain()) +/// .pick_or_route(&arg![Operator], || { +/// ErrorNumberOperatorNotProvided::default().to_chain() +/// }) // Returns a routable type when not found or fails to parse +/// // | +/// // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +/// .pick_or_route(&arg![f32], || ErrorNumberBNotProvided::default().to_chain()) +/// // Use `to_result` to parse arguments +/// // and convert to Result<(Tuple, ...), Route> type +/// .to_result() +/// ); +/// // --------- IMPORTANT --------- +/// +/// if operator == Operator::Slash && number_b == 0. { +/// return ErrorDivisionByZero::default().to_chain(); +/// } +/// +/// StateCalculate { +/// number_a, +/// operator, +/// number_b, +/// } +/// .to_chain() +/// } +/// +/// #[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(), +/// } +/// } +/// +/// #[chain] +/// fn handle_state_add(state_add: StateAdd) -> ResultNumber { +/// let (a, b) = state_add.inner; +/// ResultNumber::new(a + b) +/// } +/// +/// #[chain] +/// fn handle_state_subtract(state_subtract: StateSubtract) -> ResultNumber { +/// let (a, b) = state_subtract.inner; +/// ResultNumber::new(a - b) +/// } +/// +/// #[chain] +/// fn handle_state_multiply(state_multiply: StateMultiply) -> ResultNumber { +/// let (a, b) = state_multiply.inner; +/// ResultNumber::new(a * b) +/// } +/// +/// #[chain] +/// fn handle_state_divide(state_divide: StateDivide) -> ResultNumber { +/// let (a, b) = state_divide.inner; +/// ResultNumber::new(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 }; +/// format!("Result: {}", result) +/// } +/// +/// #[renderer] +/// fn render_error_division_by_zero(_: ErrorDivisionByZero) -> String { +/// "Error: Division by zero is not allowed!".to_string() +/// } +/// +/// #[renderer] +/// fn render_error_number_a_not_provided(_: ErrorNumberANotProvided) -> String { +/// "Error: First number (number_a) was not provided.".to_string() +/// } +/// +/// #[renderer] +/// fn render_error_number_b_not_provided(_: ErrorNumberBNotProvided) -> String { +/// "Error: Second number (number_b) was not provided.".to_string() +/// } +/// +/// #[renderer] +/// fn render_error_number_operator_not_provided(_: ErrorNumberOperatorNotProvided) -> String { +/// "Error: Operator was not provided.".to_string() +/// } +/// +/// gen_program!(); +/// ``` +pub mod example_argument_picker {} /// Example Async Runtime Support /// /// > This example shows how to drive an async runtime using the `async` feature @@ -166,6 +418,7 @@ pub mod example_argument_parse {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{hook::ProgramHook, prelude::*}; +/// use std::io::Write; /// /// #[tokio::main] /// async fn main() { @@ -191,14 +444,16 @@ pub mod example_argument_parse {} /// // vvvvv_ `async` keyword can be used directly here /// pub async fn handle_download(args: EntryDownload) -> Next { /// let file_name = args.pick(()).unpack(); -/// fake_download(file_name).await +/// fake_download(file_name).await.into() /// } /// /// /// Renders the downloaded file name. /// #[renderer] /// // But renderers cannot use the `async` keyword -/// pub fn render_downloaded(result: ResultDownloaded) { -/// r_println!("\"{}\" downloaded.", *result); +/// pub fn render_downloaded(result: ResultDownloaded) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "\"{}\" downloaded.", *result).ok(); +/// render_result /// } /// // --------- IMPORTANT --------- /// @@ -241,6 +496,7 @@ pub mod example_async_support {} /// ```ignore /// // Import commonly used Mingling modules /// use mingling::prelude::*; +/// use std::io::Write; /// /// // Define the `greet` subcommand /// // _____________________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm") @@ -281,14 +537,16 @@ pub mod example_async_support {} /// .cloned() /// .unwrap_or_else(|| "World".to_string()) /// .into(); -/// name +/// name.into() /// } /// /// // Define renderer `render_name`, used to render `ResultName` /// /// Renders the greeting message with the provided name. /// #[renderer] -/// fn render_name(name: ResultName) { -/// r_println!("Hello, {}!", *name); +/// fn render_name(name: ResultName) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Hello, {}!", *name).ok(); +/// render_result /// } /// /// // Note: This macro generates the program entry point. @@ -365,7 +623,8 @@ pub mod example_basic {} /// /// Source code (./src/main.rs) /// ```ignore -/// use mingling::{macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup, Groupped}; +/// use mingling::{Grouped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup}; +/// use std::io::Write; /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -393,10 +652,10 @@ pub mod example_basic {} /// // Implement Clap Parser, and bind to Dispatcher /// // _______________________________ Default trait, provides fallback on parse failure /// // / ______________________ clap::Parser, parsing logic implemented by Clap -/// // | / ________ Implement mingling::Groupped +/// // | / ________ Implement mingling::Grouped /// // | | / to ensure Mingling can recognize the type -/// // vvvvvvv vvvvvvvvvvvv vvvvvvvv -/// #[derive(Default, clap::Parser, Groupped)] +/// // vvvvvvv vvvvvvvvvvvv vvvvvvv +/// #[derive(Default, clap::Parser, Grouped)] /// #[dispatcher_clap( /// "greet", CMDGreet, // Bind EntryGreet to "greet" command /// help = true, // Generate clap help for EntryGreet @@ -416,24 +675,29 @@ pub mod example_basic {} /// /// /// Renders the greet output with optional repetition. /// #[renderer] -/// fn render_greet(greet: EntryGreet) { +/// fn render_greet(greet: EntryGreet) -> RenderResult { /// let name = greet.name; /// let count = greet.repeat.max(0) as usize; /// -/// r_print!("Hello, "); +/// let mut render_result = RenderResult::default(); +/// write!(render_result, "Hello, ").ok(); /// for i in 0..count { -/// r_print!("{name}"); +/// write!(render_result, "{name}").ok(); /// if i < count - 1 { -/// r_print!(", "); +/// write!(render_result, ", ").ok(); /// } /// } -/// r_println!("!"); +/// writeln!(render_result, "!").ok(); +/// render_result /// } /// /// /// Renders the error message when greet argument parsing fails. /// #[renderer] -/// fn render_greet_parse_failed(err: ErrorGreetParsed) { -/// r_println!("{}", *err); +/// // renderers can return a RenderResult instead of using r_println! +/// pub fn render_greet_parse_failed(err: ErrorGreetParsed) -> RenderResult { +/// let mut render_result = RenderResult::default(); +/// writeln!(render_result, "{}", *err).ok(); +/// render_result /// } /// /// gen_program!(); @@ -584,6 +848,7 @@ pub mod example_combine_pathf_dispatch_tree {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{macros::suggest, prelude::*, ShellContext, Suggest}; +/// use std::io::Write; /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -652,18 +917,20 @@ pub mod example_combine_pathf_dispatch_tree {} /// .pick_or((), "World") /// .unpack() /// .into(); -/// result +/// result.into() /// } /// /// /// Renders the greeting with the result name and repeat count. /// #[renderer] -/// fn render_name(result: ResultName) { +/// fn render_name(result: ResultName) -> RenderResult { /// let (repeat, name) = result.inner; +/// let mut render_result = RenderResult::new(); /// let mut parts = Vec::with_capacity(repeat as usize); /// for _ in 0..repeat { /// parts.push(name.clone()); /// } -/// r_println!("Hello, {}!", parts.join(", ")); +/// writeln!(render_result, "Hello, {}!", parts.join(", ")).ok(); +/// render_result /// } /// /// gen_program!(); @@ -702,14 +969,15 @@ pub mod example_completion {} /// /// Source code (./src/main.rs) /// ```ignore -/// use mingling::{macros::route, parser::Pickable, prelude::*, Groupped}; +/// 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 Groupped derive macro registers an ID for this type +/// // / ________ The Grouped derive macro registers an ID for this type /// // | / Mingling uses this ID to identify the type -/// // vvvvvvv vvvvvvvv -/// #[derive(Debug, Default, Clone, Groupped)] +/// // vvvvvvv vvvvvvv +/// #[derive(Debug, Default, Clone, Grouped)] /// pub struct Address { /// pub ip: [u8; 4], /// pub port: u16, @@ -740,14 +1008,18 @@ pub mod example_completion {} /// /// /// Renders the connected address. /// #[renderer] -/// fn render_address(addr: Address) { -/// r_println!("Connected to \"{}\"", addr.to_string()); +/// 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] -/// fn render_error_parse_address_failed(_: ErrorParseAddressFailed) { -/// r_println!("Failed to parse address"); +/// 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!(); @@ -856,6 +1128,7 @@ pub mod example_custom_pickable {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::prelude::*; +/// use std::io::Write; /// /// // --------- IMPORTANT --------- /// // You have a large number of subcommands @@ -888,8 +1161,10 @@ pub mod example_custom_pickable {} /// /// /// Renders the confirmation message for the `cmd5` command. /// #[renderer] -/// fn render_cmd5(_: Entry5) { -/// r_println!("It's works!"); +/// fn render_cmd5(_: Entry5) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "It's works!").ok(); +/// render_result /// } /// /// gen_program!(); @@ -933,15 +1208,16 @@ pub mod example_dispatch_tree {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{ -/// macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Groupped, ShellContext, +/// macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Grouped, ShellContext, /// Suggest, /// }; +/// use std::io::Write; /// /// // Define the enum and derive the EnumTag trait /// // ________ adds metadata to the enum, enabling it to: /// // / 1. Be used by the `suggest_enum!(Enum)` macro under the `comp` feature for autocompletion /// // vvvvvvv 2. Implement the `PickableEnum` trait -/// #[derive(Debug, Default, EnumTag, Groupped)] +/// #[derive(Debug, Default, EnumTag, Grouped)] /// pub enum ProgrammingLanguages { /// #[enum_desc("An efficient and flexible compiled language widely used for system programming")] /// C, @@ -995,15 +1271,16 @@ pub mod example_dispatch_tree {} /// fn handle_language_selection(args: EntryLanguageSelection) -> Next { /// // You can use Picker to directly parse ProgrammingLanguages /// let lang: ProgrammingLanguages = args.pick(()).unpack(); -/// lang +/// lang.into() /// } /// /// /// Renders the selected programming language with its name and description. /// #[renderer] -/// fn render_programming_language(lang: ProgrammingLanguages) { -/// // You can use `enum_info()` to get the name and description of the current enum +/// pub fn render_programming_language(lang: ProgrammingLanguages) -> RenderResult { +/// let mut render_result = RenderResult::new(); /// let (name, desc) = lang.enum_info(); -/// r_println!("Selected: {} ({})", name, desc) +/// writeln!(render_result, "Selected: {} ({})", name, desc).ok(); +/// render_result /// } /// /// #[completion(EntryLanguageSelection)] @@ -1060,6 +1337,7 @@ pub mod example_enum_tag {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::prelude::*; +/// use std::io::Write; /// /// // In Mingling, instead of using ? to propagate errors upward, /// // errors are treated as branches that continue execution. @@ -1100,36 +1378,47 @@ pub mod example_enum_tag {} /// /// /// Renders a successful greeting with the given name. /// #[renderer] -/// fn render_result_name(name: ResultName) { -/// r_println!("Hello, {}", *name); +/// fn render_result_name(name: ResultName) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Hello, {}", *name).ok(); +/// render_result /// } /// /// /// Renders the error when no name is provided. /// #[renderer] -/// fn render_error_no_name_provided(_: ErrorNoNameProvided) { -/// // Prompt when no name is provided -/// r_println!("No name provided"); +/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "No name provided").ok(); +/// render_result /// } /// /// /// Renders the error when the name is already taken. /// #[renderer] -/// fn render_error_name_not_available(_: ErrorNameNotAvailable) { -/// // Prompt when name is already taken -/// r_println!("Name not available"); +/// fn render_error_name_not_available(_: ErrorNameNotAvailable) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Name not available").ok(); +/// render_result /// } /// /// /// Renders the error when the name exceeds the maximum length. /// #[renderer] -/// fn render_error_name_too_long(len: ErrorNameTooLong) { -/// // Prompt when name is too long, showing actual length -/// r_println!("Name too long: {} > 10", *len); +/// fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Name too long: {} > 10", *len).ok(); +/// render_result /// } /// /// /// Renders the error when the dispatcher (subcommand) is not found. /// #[renderer] -/// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { -/// // Prompt when command is not found, showing the input command -/// r_println!("Command not found: \"{}\"", err.inner.join(" ")); +/// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!( +/// render_result, +/// "Command not found: \"{}\"", +/// err.inner.join(" ") +/// ) +/// .ok(); +/// render_result /// } /// /// gen_program!(); @@ -1178,6 +1467,7 @@ pub mod example_error_handling {} /// res::ResExitCode, /// setup::{BasicProgramSetup, ExitCodeSetup}, /// }; +/// use std::io::Write; /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -1210,25 +1500,32 @@ pub mod example_error_handling {} /// /// /// Renders a successful greeting with the given name. /// #[renderer] -/// fn render_result_name(name: ResultName) { -/// r_println!("Hello, {}", *name); +/// fn render_result_name(name: ResultName) -> RenderResult { +/// let mut result = RenderResult::new(); +/// writeln!(result, "Hello, {}", *name).ok(); +/// result /// } /// /// #[help] -/// fn help_hello(_p: EntryHello, ec: &mut ResExitCode) { -/// r_println!("Usage: hello <NAME>"); +/// fn help_hello(_p: EntryHello, ec: &mut ResExitCode) -> RenderResult { +/// let mut result = RenderResult::new(); +/// writeln!(result, "Usage: hello <NAME>").ok(); /// ec.exit_code = 2; +/// result /// } /// /// // Define renderer, render error message _______________ Inject exit code resource /// // / /// /// Renders the error when no name is provided | /// #[renderer] // vvvvvvvvvvvvvvvv -/// fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ResExitCode) { +/// fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ResExitCode) -> RenderResult { /// ec.exit_code = 1; /// +/// let mut result = RenderResult::new(); +/// /// // Prompt when no name is provided -/// r_println!("No name provided (with exit code 1)"); +/// writeln!(result, "No name provided (with exit code 1)").ok(); +/// result /// } /// /// gen_program!(); @@ -1265,14 +1562,17 @@ pub mod example_exitcode {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{macros::help, prelude::*, setup::BasicProgramSetup}; +/// use std::io::Write; /// /// dispatcher!("greet", CMDGreet => EntryGreet); /// /// // Define help _________ When `program.user_context.help` is `true` /// // / the command will not enter `#[chain]` / `#[renderer]` /// #[help] // vvvvvvvvvv but instead enter this `#[help]` function -/// fn help_greet(_prev: EntryGreet) { -/// r_println!("Usage: greet <NAME>"); +/// fn help_greet(_prev: EntryGreet) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Usage: greet <NAME>").ok(); +/// render_result /// } /// /// fn main() { @@ -1333,6 +1633,7 @@ pub mod example_help {} /// hook::{ProgramControlUnit, ProgramHook}, /// prelude::*, /// }; +/// use std::io::Write; /// /// dispatcher!("greet", CMDGreet => EntryGreet); /// @@ -1372,13 +1673,16 @@ pub mod example_help {} /// .cloned() /// .unwrap_or_else(|| "World".to_string()) /// .into(); -/// name +/// name.into() /// } /// /// /// Renders the greeting message with the provided name. +/// /// Renders the greeting message with the provided name. /// #[renderer] -/// fn render_name(name: ResultName) { -/// r_println!("Hello, {}!", *name); +/// fn render_name(name: ResultName) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Hello, {}!", *name).ok(); +/// render_result /// } /// /// gen_program!(); @@ -1465,6 +1769,7 @@ pub mod example_implicit_dispatcher {} /// Source code (./src/main.rs) /// ```ignore /// use std::collections::BTreeMap; +/// use std::io::Write; /// /// use mingling::{LazyInit, LazyRes, prelude::*}; /// @@ -1516,20 +1821,25 @@ pub mod example_implicit_dispatcher {} /// // | _____________________ Use LazyRes<ResLargeData> /// // | / instead of ResLargeData /// #[renderer] // vvvv vvvvvvvvvvvvvvvvvvvvv -/// fn render_entry_show(_args: EntryShow, res: &mut LazyRes<ResLargeData>) { +/// fn render_entry_show(_args: EntryShow, res: &mut LazyRes<ResLargeData>) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// /// // _______ Initialization happens here /// // / /// // vvvvvvv /// let res = res.get_ref(); /// for (key, value) in &res.data { -/// r_println!("{}: {}", key, value); +/// writeln!(render_result, "{}: {}", key, value).ok(); /// } +/// render_result /// } /// /// // When not using LazyRes<ResLargeData>, it will not be initialized /// #[renderer] -/// fn render_entry_none(_args: EntryNone) { -/// r_println!("None"); +/// fn render_entry_none(_args: EntryNone) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "None").ok(); +/// render_result /// } /// /// gen_program!(); @@ -1574,6 +1884,7 @@ pub mod example_lazy_resources {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{macros::group, prelude::*}; +/// use std::io::Write; /// use std::{io::ErrorKind::Other, num::ParseIntError}; /// /// dispatcher!("parse"); @@ -1616,25 +1927,31 @@ pub mod example_lazy_resources {} /// // _____________ Using std::num::ParseIntError as a chain input /// // / /// #[renderer] // vvvvvvvvvvvv -/// fn render_number(num: ParsedNumber) { -/// r_println!("Parsed number: {}", *num); +/// fn render_number(num: ParsedNumber) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!(render_result, "Parsed number: {}", *num).ok(); +/// render_result /// } /// /// /// Renderer for parse errors — using the outside `ParseIntError` type. /// /// /// /// The `ParseIntError` type is registered via `group!` above, so it implements -/// /// `Groupped<ThisProgram>` and can be used directly in a `#[renderer]` function. +/// /// `Grouped<ThisProgram>` and can be used directly in a `#[renderer]` function. /// #[renderer] -/// fn render_parse_error(err: ParseIntError) { -/// r_println!("Parse error: {}", err); +/// fn render_parse_error(err: ParseIntError) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!(render_result, "Parse error: {}", err).ok(); +/// render_result /// } /// /// /// Renderer for IO errors — using `std::io::Error` registered as `ErrorIo`. /// // ________ Must use alias `ErrorIo` here, not bare `std::io::Error` /// // / /// #[renderer] // vvvvvvv -/// fn render_error_io(err: ErrorIo) { -/// r_println!("IO_ERROR: {}", err.to_string()); +/// fn render_error_io(err: ErrorIo) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!(render_result, "IO_ERROR: {}", err).ok(); +/// render_result /// } /// /// fn main() { @@ -1699,6 +2016,7 @@ pub mod example_outside_type {} /// ```ignore /// use mingling::prelude::*; /// use mingling::setup::StructuralRendererSetup; +/// use std::io::Write; /// use std::path::PathBuf; /// /// dispatcher!("find", CMDFind => EntryFind); @@ -1771,32 +2089,42 @@ pub mod example_outside_type {} /// /// /// Renders the successful result with the found directory path. /// #[renderer] -/// fn render_result_path(path: ResultPath) { -/// r_println!("Found directory: {}", path.display()); +/// 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) { -/// r_println!("Search path not provided"); +/// 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) { -/// r_println!("Not a directory: {}", err.info.display()); +/// 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) { -/// r_println!("Search path not provided"); +/// 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) { -/// r_println!("Not a directory: {}", err.info.display()); +/// 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!(); @@ -1853,6 +2181,7 @@ pub mod example_pack_err {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{hook::ProgramHook, prelude::*}; +/// use std::io::Write; /// /// dispatcher!("panic", CMDPanic => EntryPanic); /// pack!(NotPanic = ()); @@ -1883,14 +2212,17 @@ pub mod example_pack_err {} /// // Panic happens here, will be caught /// panic!("{}", s) /// } -/// None => NotPanic::default(), +/// None => NotPanic::default().into(), /// } /// } /// /// /// Renders the message when no panic occurs. +/// /// Renders the message when no panic occurs. /// #[renderer] -/// fn render(_: NotPanic) { -/// r_println!("Program not panic"); +/// pub fn render(_: NotPanic) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Program not panic").ok(); +/// render_result /// } /// /// gen_program!(); @@ -1994,6 +2326,7 @@ pub mod example_pathfinder {} /// setup::{BasicREPLOutputSetup, BasicREPLPromptSetup, BasicREPLReadlineSetup}, /// this, /// }; +/// use std::io::Write; /// use std::{env::current_dir, path::PathBuf}; /// /// // Resource to store the current directory @@ -2073,7 +2406,7 @@ pub mod example_pathfinder {} /// #[chain] /// fn parse_cd_args(prev: EntryCd) -> Next { /// let join = prev.pick(()).unpack(); -/// StateChangeDirectory::new(join) +/// StateChangeDirectory::new(join).into() /// } /// /// // Execute directory change @@ -2116,10 +2449,12 @@ pub mod example_pathfinder {} /// /// /// Render ResultList data /// #[renderer] -/// fn render_list(list: ResultList) { +/// fn render_list(list: ResultList) -> RenderResult { +/// let mut render_result = RenderResult::new(); /// for item in list.inner { -/// r_println!("{}", item); +/// writeln!(render_result, "{}", item).ok(); /// } +/// render_result /// } /// /// // Handle exit command event @@ -2141,15 +2476,24 @@ pub mod example_pathfinder {} /// /// /// Handle path not found event /// #[renderer] -/// fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) { -/// r_println!("Directory not found: {}", err.inner.display()) +/// 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(); +/// render_result /// } /// /// /// Handle dispatcher not found event /// /// Renders the error when a command is not found. /// #[renderer] -/// fn dispatcher_not_found(prev: ErrorDispatcherNotFound) { -/// r_println!("Command not found: \"{}\"", prev.join(", ")) +/// fn dispatcher_not_found(prev: ErrorDispatcherNotFound) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Command not found: \"{}\"", prev.join(", ")).ok(); +/// render_result /// } /// /// gen_program!(); @@ -2187,9 +2531,9 @@ pub mod example_repl_basic {} /// /// Source code (./src/main.rs) /// ```ignore -/// use std::path::PathBuf; -/// /// use mingling::prelude::*; +/// use std::io::Write; +/// use std::path::PathBuf; /// /// // Create resource /// // ______________ Resource needs to @@ -2224,15 +2568,22 @@ pub mod example_repl_basic {} /// current_dir.current_dir = current_dir /// .current_dir /// .join(args.pick::<String>(()).unpack()); -/// EntryCurrent::default() +/// EntryCurrent::default().into() /// } /// /// // Define renderer for output current path _____________ Injected resource /// // / /// /// Renders the current directory path. | /// #[renderer] // vvvvvvvvvvvvvv -/// fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) { -/// r_println!("Current directory: {}", current_dir.current_dir.display()); +/// fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!( +/// render_result, +/// "Current directory: {}", +/// current_dir.current_dir.display() +/// ) +/// .ok(); +/// render_result /// } /// /// gen_program!(); @@ -2330,8 +2681,9 @@ pub mod example_setup {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::prelude::*; -/// use mingling::{parser::Picker, setup::StructuralRendererSetup, StructuralData, Groupped}; +/// use mingling::{parser::Picker, setup::StructuralRendererSetup, Grouped, StructuralData}; /// use serde::Serialize; +/// use std::io::Write; /// /// dispatcher!("render", CMDRender => EntryRender); /// @@ -2346,12 +2698,12 @@ pub mod example_setup {} /// // --------- IMPORTANT --------- /// // For beautiful output structure, do not use `pack!` to wrap the types that need to be output. /// // Instead, manually implement -/// // __________________________________ Mark as structured data so it can be rendered -/// // / ____________________ Implement serde::Serialize -/// // | / _________ Implement mingling::Groupped -/// // | | / to ensure Mingling can recognize the type -/// // vvvvvvvvvvvv vvvvvvvvv vvvvvvvv -/// #[derive(StructuralData, Serialize, Groupped)] +/// // ____________________________________ Mark as structured data so it can be rendered +/// // / ____________________ Implement serde::Serialize +/// // | / _________ Implement mingling::Grouped +/// // | | / to ensure Mingling can recognize the type +/// // vvvvvvvvvvvv vvvvvvvvv vvvvvvv +/// #[derive(StructuralData, Serialize, Grouped)] /// struct Info { /// #[serde(rename = "member_name")] /// name: String, @@ -2376,8 +2728,10 @@ pub mod example_setup {} /// /// /// Implement default renderer for when structural_renderer is not specified /// #[renderer] -/// fn render_info(prev: Info) { -/// r_println!("{} is {} years old", prev.name, prev.age); +/// fn render_info(prev: Info) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "{} is {} years old", prev.name, prev.age).ok(); +/// render_result /// } /// /// gen_program!(); @@ -2407,6 +2761,7 @@ pub mod example_structural_renderer {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::prelude::*; +/// use std::io::Write; /// /// #[cfg(test)] /// mod tests { @@ -2441,25 +2796,25 @@ pub mod example_structural_renderer {} /// #[test] /// fn test_render_result_name() { /// let r = render_result_name(ResultName::new("Peter".into())); -/// assert_eq!(r, "Hello, Peter!\n") +/// assert_eq!(r.to_string().as_str(), "Hello, Peter!\n") /// } /// /// #[test] /// fn test_render_error_no_name_provided() { /// let r = render_error_no_name_provided(ErrorNoNameProvided::default()); -/// assert_eq!(r, "No name provided\n") +/// assert_eq!(r.to_string().as_str(), "No name provided\n") /// } /// /// #[test] /// fn test_render_error_name_not_available() { /// let r = render_error_name_not_available(ErrorNameNotAvailable::default()); -/// assert_eq!(r, "Name not available\n") +/// assert_eq!(r.to_string().as_str(), "Name not available\n") /// } /// /// #[test] /// fn test_render_error_name_too_long() { /// let r = render_error_name_too_long(ErrorNameTooLong::new(17)); -/// assert_eq!(r, "Name too long: 17 > 10\n") +/// assert_eq!(r.to_string().as_str(), "Name too long: 17 > 10\n") /// } /// // --------- IMPORTANT --------- /// } @@ -2493,32 +2848,47 @@ pub mod example_structural_renderer {} /// /// /// Renders a successful greeting with the given name. /// #[renderer] -/// fn render_result_name(name: ResultName) -> String { -/// r_println!("Hello, {}!", *name); +/// fn render_result_name(name: ResultName) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Hello, {}!", *name).ok(); +/// render_result /// } /// /// /// Renders the error when no name is provided. /// #[renderer] -/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> String { -/// r_println!("No name provided"); +/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "No name provided").ok(); +/// render_result /// } /// /// /// Renders the error when the name is already taken. /// #[renderer] -/// fn render_error_name_not_available(_: ErrorNameNotAvailable) -> String { -/// r_println!("Name not available"); +/// fn render_error_name_not_available(_: ErrorNameNotAvailable) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Name not available").ok(); +/// render_result /// } /// /// /// Renders the error when the name exceeds the maximum length. /// #[renderer] -/// fn render_error_name_too_long(len: ErrorNameTooLong) -> String { -/// r_println!("Name too long: {} > 10", *len); +/// fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Name too long: {} > 10", *len).ok(); +/// render_result /// } /// /// /// Renders the error when the dispatcher (subcommand) is not found. /// #[renderer] -/// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { -/// r_println!("Command not found: \"{}\"", err.inner.join(" ")); +/// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!( +/// render_result, +/// "Command not found: \"{}\"", +/// err.inner.join(" ") +/// ) +/// .ok(); +/// render_result /// } /// /// gen_program!(); diff --git a/mingling/src/features.rs b/mingling/src/features.rs index 4d0c50b..cb474ae 100644 --- a/mingling/src/features.rs +++ b/mingling/src/features.rs @@ -53,6 +53,17 @@ pub const MINGLING_COMP: bool = false; #[cfg(feature = "comp")] #[allow(unused)] pub const MINGLING_COMP: bool = true; +/// Whether the `core` feature is enabled +/// Current: `disabled` +#[cfg(not(feature = "core"))] +#[allow(unused)] +pub const MINGLING_CORE: bool = false; + +/// Whether the `core` feature is enabled +/// Current: `enabled` +#[cfg(feature = "core")] +#[allow(unused)] +pub const MINGLING_CORE: bool = true; /// Whether the `debug` feature is enabled /// Current: `disabled` #[cfg(not(feature = "debug"))] @@ -108,6 +119,17 @@ pub const MINGLING_JSON_SERDE_FMT: bool = false; #[cfg(feature = "json_serde_fmt")] #[allow(unused)] pub const MINGLING_JSON_SERDE_FMT: bool = true; +/// Whether the `macros` feature is enabled +/// Current: `disabled` +#[cfg(not(feature = "macros"))] +#[allow(unused)] +pub const MINGLING_MACROS: bool = false; + +/// Whether the `macros` feature is enabled +/// Current: `enabled` +#[cfg(feature = "macros")] +#[allow(unused)] +pub const MINGLING_MACROS: bool = true; /// Whether the `nightly` feature is enabled /// Current: `disabled` #[cfg(not(feature = "nightly"))] @@ -141,6 +163,17 @@ pub const MINGLING_PATHF: bool = false; #[cfg(feature = "pathf")] #[allow(unused)] pub const MINGLING_PATHF: bool = true; +/// Whether the `picker` feature is enabled +/// Current: `disabled` +#[cfg(not(feature = "picker"))] +#[allow(unused)] +pub const MINGLING_PICKER: bool = false; + +/// Whether the `picker` feature is enabled +/// Current: `enabled` +#[cfg(feature = "picker")] +#[allow(unused)] +pub const MINGLING_PICKER: bool = true; /// Whether the `repl` feature is enabled /// Current: `disabled` #[cfg(not(feature = "repl"))] diff --git a/mingling/src/lib.md b/mingling/src/lib.md new file mode 100644 index 0000000..03fa61d --- /dev/null +++ b/mingling/src/lib.md @@ -0,0 +1,96 @@ +<h1 align="center">Mìng Lìng - 命令</h1> + +<p align="center"> + <b>/mɪŋ lɪŋ/</b> +</p> + +<p align="center"> + Macro magician in your CLI. +</p> + +## Intro + +[`Mingling`](https://github.com/mingling-rs/mingling) is a **proc-macro and type system-based** Rust CLI framework, suitable for developing complex command-line programs with numerous subcommands. + +## Use + +Here is a basic project written using **Mingling**: + +- When the user types `greet`, the program outputs `Hello, World!` +- When the user types `greet Alice`, the program outputs `Hello, Alice!` + +```rust +use mingling::prelude::*; + +dispatcher!("greet", CMDGreet => EntryGreet); + +fn main() { + let mut program = ThisProgram::new(); + program.with_dispatcher(CMDGreet); + program.exec_and_exit(); +} + +pack!(ResultName = String); + +#[chain] +fn handle_greet(args: EntryGreet) -> Next { + let name: ResultName = args + .inner + .first() + .cloned() + .unwrap_or_else(|| "World".to_string()) + .into(); + name.into() +} + +#[renderer] +fn render_name(name: ResultName) -> RenderResult { + let mut result = RenderResult::default(); + result.println(&format!("Hello, {}!", *name)); + result +} + +#[renderer] +fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { + let mut result = RenderResult::default(); + if err.len() > 0 { + result.println(&format!("Command not found: [{}]", err.join(" "))); + } + result +} + +gen_program!(); +``` + +Output: + +```text +> mycmd greet +Hello, World! +> mycmd greet Alice +Hello, Alice! +> mycmd great +Command not found: [great] +``` + +## Examples + +`Mingling` provides detailed usage examples for your reference. +See [Examples](_mingling_examples/index.html) or [Helpdoc](https://mingling-rs.github.io/mingling/docs/examples.html) + +## About Features + +All features of `Mingling` are opt-in. To learn what each feature provides, see [Features](feature/index.html) or [Helpdoc](https://mingling-rs.github.io/mingling/docs/doc.html#/pages/other/features) + +## Use unreleased version + +If you want to use the latest development version, you can pull from the [Unreleased](https://github.com/mingling-rs/mingling/tree/unreleased) branch on [GitHub](https://github.com/mingling-rs/mingling) by adding the following to your `Cargo.toml`: + +```toml +[dependencies.mingling] +git = "https://github.com/mingling-rs/mingling.git" +tag = "unreleased" +features = [] +``` + +**Please note** that the documentation for the latest version may differ from this article. It is recommended to refer to the [latest documentation](https://mingling-rs.github.io/mingling/docs/api-docs/mingling/). diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index b4372dc..f4bc9dc 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -1,85 +1,35 @@ -//! Mingling -//! -//! # Intro -//! [`Mingling`](https://github.com/mingling-rs/mingling) is a **proc-macro and type system-based** Rust CLI framework, suitable for developing complex command-line programs with numerous subcommands. -//! -//! # Use -//! Here is a basic project written using **Mingling**: -//! - When the user types `greet`, the program outputs `Hello, World!` -//! - When the user types `greet Alice`, the program outputs `Hello, Alice!` -//! -//! ```rust -//! use mingling::prelude::*; -//! -//! dispatcher!("greet", CMDGreet => EntryGreet); -//! -//! fn main() { -//! let mut program = ThisProgram::new(); -//! program.with_dispatcher(CMDGreet); -//! program.exec_and_exit(); -//! } -//! -//! pack!(ResultName = String); -//! -//! #[chain] -//! fn handle_greet(args: EntryGreet) -> Next { -//! let name: ResultName = args -//! .inner -//! .first() -//! .cloned() -//! .unwrap_or_else(|| "World".to_string()) -//! .into(); -//! name -//! } -//! -//! #[renderer] -//! fn render_name(name: ResultName) { -//! r_println!("Hello, {}!", *name); -//! } -//! -//! #[renderer] -//! fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { -//! if err.len() > 0 { -//! r_println!("Command not found: [{}]", err.join(" ")); -//! } -//! } -//! -//! gen_program!(); -//! ``` -//! -//! Output: -//! -//! ```text -//! > mycmd greet -//! Hello, World! -//! > mycmd greet Alice -//! Hello, Alice! -//! > mycmd great -//! Command not found: [great] -//! ``` -//! -//! # Examples -//! `Mingling` provides detailed usage examples for your reference. -//! See [Examples](_mingling_examples/index.html) or [Helpdoc](https://mingling-rs.github.io/mingling/docs/examples.html) -//! -//! # About Features -//! All features of `Mingling` are opt-in. To learn what each feature provides, see [Features](feature/index.html) or [Helpdoc](https://mingling-rs.github.io/mingling/docs/doc.html#/pages/other/features) +#![doc = include_str!("lib.md")] +#[cfg(feature = "core")] mod example_docs; // Re-export Core lib +#[cfg(feature = "core")] pub use mingling::*; + +#[cfg(feature = "core")] pub use mingling_core as mingling; -/// `Mingling` argument parser +/// `Mingling` argument parser (Built-in) #[cfg(feature = "parser")] pub mod parser; +/// `Mingling` argument parser (Picker2) +#[cfg(feature = "picker")] +pub mod picker; + +mod constants; + +/// Constants used throughout the Mingling framework. +pub mod consts { + pub use crate::constants::*; +} + /// Re-export of all macros from `mingling_macros`. /// /// This module re-exports all macros provided by the `mingling_macros` crate, -/// including `dispatcher!`, `chain!`, `renderer!`, `gen_program!`, `pack!`, -/// `r_print!`, `r_println!`, and many others. These macros form the core +/// including `dispatcher!`, `chain!`, `renderer!`, +/// `gen_program!`, `pack!`, 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 @@ -87,7 +37,11 @@ pub mod parser; /// /// <https://docs.rs/mingling_macros/latest/mingling_macros/> #[allow(unused_imports)] +#[cfg(feature = "macros")] pub mod macros { + /// New Parser provided by the `picker` feature + #[cfg(feature = "picker")] + pub use arg_picker::macros::*; /// `#[chain]` - Used to generate a struct implementing the `Chain` trait via a method pub use mingling_macros::chain; /// `#[completion(EntryType)]` - Used to generate completion entry @@ -118,15 +72,15 @@ pub mod macros { pub use mingling_macros::node; /// `pack!(StateGreet = String)` - Used to create a wrapper type for use with `Chain` and `Renderer` pub use mingling_macros::pack; - /// `pack_structural!(StateGreet = String)` - Like `pack!` but also marks the type for structured output - #[cfg(feature = "structural_renderer")] - pub use mingling_macros::pack_structural; /// `pack_err!(ErrorUnknown)` - Used to create an error struct with automatic `name` field #[cfg(feature = "extra_macros")] pub use mingling_macros::pack_err; /// `pack_err_structural!(ErrorUnknown)` - Like `pack_err!` but also marks the type for structured output #[cfg(all(feature = "structural_renderer", feature = "extra_macros"))] pub use mingling_macros::pack_err_structural; + /// `pack_structural!(StateGreet = String)` - Like `pack!` but also marks the type for structured output + #[cfg(feature = "structural_renderer")] + pub use mingling_macros::pack_structural; #[cfg(feature = "comp")] #[doc(hidden)] pub use mingling_macros::program_comp_gen; @@ -137,10 +91,6 @@ pub mod macros { /// `#[program_setup]` - Used to generate program setup #[cfg(feature = "extra_macros")] pub use mingling_macros::program_setup; - /// `r_print!("{someting}")` - Used to print content within a `Renderer` context - pub use mingling_macros::r_print; - /// `r_println!("{someting}")` - Used to print content with a newline within a `Renderer` context - pub use mingling_macros::r_println; #[doc(hidden)] pub use mingling_macros::register_chain; #[doc(hidden)] @@ -165,20 +115,24 @@ pub mod macros { } /// derive macro `EnumTag` +#[cfg(feature = "macros")] pub use mingling_macros::EnumTag; -/// derive macro Groupped -pub use mingling_macros::Groupped; +/// derive macro Grouped +#[cfg(feature = "macros")] +pub use mingling_macros::Grouped; /// derive macro `StructuralData` — marks a type as supporting structured output #[cfg(feature = "structural_renderer")] pub use mingling_macros::StructuralData; /// Example projects for `Mingling`, for learning how to use `Mingling` +#[cfg(feature = "core")] pub mod _mingling_examples { pub use crate::example_docs::*; } +#[cfg(feature = "core")] mod features; /// Module for checking which features are enabled at compile time. @@ -186,19 +140,23 @@ mod features; /// Each constant re-exported from this module corresponds to a Cargo feature flag. /// They can be used for conditional compilation or runtime branching based on /// feature availability. +#[cfg(feature = "core")] pub mod feature { include!("./features.rs"); } +#[cfg(feature = "core")] mod setups; /// Setups provided by Mingling, which can extend command-line programs. +#[cfg(feature = "core")] pub mod setup { pub use crate::setups::*; pub use mingling_core::setup::*; } /// Mutable global resources provided within Mingling +#[cfg(feature = "core")] pub mod res; /// The prelude module provides convenient re-exports of commonly used macros and traits. @@ -213,41 +171,59 @@ pub mod res; /// use mingling::prelude::*; /// ``` pub mod prelude { - /// Re-export of the `Groupped` derive macro for grouping types. - pub use crate::Groupped; + /// Re-export of the `Grouped` derive macro for grouping types. + #[cfg(feature = "core")] + pub use crate::Grouped; + /// Re-export of the `RenderResult` struct for outputting rendering result + #[cfg(feature = "core")] + pub use crate::RenderResult; /// Re-export of the `chain` macro for defining a chain of commands. + #[cfg(feature = "macros")] pub use crate::macros::chain; /// Re-export of the `dispatcher` macro for routing commands. + #[cfg(feature = "macros")] pub use crate::macros::dispatcher; /// Re-export of the `empty_result` macro for creating an empty result value for early return. - #[cfg(feature = "extra_macros")] + #[cfg(all(feature = "extra_macros", feature = "macros"))] pub use crate::macros::empty_result; /// Re-export of the `gen_program` macro for generating the program entry point. + #[cfg(feature = "macros")] pub use crate::macros::gen_program; /// Re-export of the `pack` macro for creating wrapper types. + #[cfg(feature = "macros")] pub use crate::macros::pack; - /// Like `pack!` but also marks the type for structured output - #[cfg(feature = "structural_renderer")] - pub use mingling_macros::pack_structural; /// Re-export of the `pack_err` macro for creating error types. - #[cfg(feature = "extra_macros")] + #[cfg(all(feature = "extra_macros", feature = "macros"))] pub use crate::macros::pack_err; - /// Like `pack_err!` but also marks the type for structured output - #[cfg(all(feature = "structural_renderer", feature = "extra_macros"))] - pub use mingling_macros::pack_err_structural; - /// Re-export of the `r_print` macro for printing within a renderer context. - pub use crate::macros::r_print; - /// Re-export of the `r_println` macro for printing with a newline within a renderer - /// context. - pub use crate::macros::r_println; /// Re-export of the `renderer` macro for defining renderer functions. + #[cfg(feature = "macros")] pub use crate::macros::renderer; + /// Like `pack_err!` but also marks the type for structured output + #[cfg(all( + feature = "macros", + feature = "structural_renderer", + feature = "extra_macros" + ))] + pub use mingling_macros::pack_err_structural; + /// Like `pack!` but also marks the type for structured output + #[cfg(all(feature = "macros", feature = "structural_renderer"))] + pub use mingling_macros::pack_structural; /// Re-export of the `completion` macro for generating completion entries. - #[cfg(feature = "comp")] + #[cfg(all(feature = "macros", feature = "comp"))] pub use crate::macros::completion; /// Re-export of the `AsPicker` trait for picker functionality. #[cfg(feature = "parser")] pub use crate::parser::AsPicker; + + #[cfg(feature = "picker")] + pub use arg_picker::prelude::arg; + + #[cfg(feature = "picker")] + pub use crate::picker::EntryPicker; + + /// Used to enable the `writeln!` macro for `RenderResult` + #[cfg(feature = "core")] + pub use std::io::Write; } diff --git a/mingling/src/parser/picker.rs b/mingling/src/parser/picker.rs index 21ba9a6..ca4561c 100644 --- a/mingling/src/parser/picker.rs +++ b/mingling/src/parser/picker.rs @@ -722,6 +722,13 @@ impl_pick_with_route_next! { PickWithRoute9 PickWithRoute10 val_10 T1 val_1, T2 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 @@ -736,6 +743,10 @@ where } } +/// 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>>, diff --git a/mingling/src/parser/picker/bools.rs b/mingling/src/parser/picker/bools.rs index ede8812..0525c52 100644 --- a/mingling/src/parser/picker/bools.rs +++ b/mingling/src/parser/picker/bools.rs @@ -1,9 +1,15 @@ 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, } @@ -57,10 +63,16 @@ impl Pickable for Yes { } } +/// 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, } diff --git a/mingling/src/picker.rs b/mingling/src/picker.rs new file mode 100644 index 0000000..f370656 --- /dev/null +++ b/mingling/src/picker.rs @@ -0,0 +1,13 @@ +/// Provides the specific parsing logic for command-line arguments and common utilities, +/// as well as customization of command-line argument styles. +pub mod parselib { + pub use arg_picker::parselib::*; +} + +pub use arg_picker::*; + +mod entry_picker; +pub use entry_picker::*; + +mod global; +pub use global::*; diff --git a/mingling/src/picker/entry_picker.rs b/mingling/src/picker/entry_picker.rs new file mode 100644 index 0000000..7364c50 --- /dev/null +++ b/mingling/src/picker/entry_picker.rs @@ -0,0 +1,126 @@ +use mingling_core::{ChainProcess, Grouped, ProgramCollect}; + +use crate::{picker::Pickable, picker::Picker, picker::PickerArg, picker::PickerPattern1}; + +/// Trait for converting Mingling entry types (types that implement `Grouped<R>` and `Into<Vec<String>>`) +/// into [`Picker`] instances for a given route type `R`. +/// +/// This trait provides a bridge between entry definitions created with the `mingling` framework +/// and the picker argument system used for CLI argument parsing and routing. +/// +/// # Type Parameters +/// +/// * `'a` — The lifetime of the picker and its references to argument definitions. +/// * `This` — The program type used for dispatching runtime routes; must implement [`ProgramCollect`]. +/// * `Route` — The route type used for dispatching; must implement [`Grouped<This>`]. +pub trait EntryPicker<'a, This> { + /// Converts `self` into a [`Picker`] for the given route type `Route`. + fn to_picker(self) -> Picker<'a, ChainProcess<This>>; + + /// Starts building a picker pattern with the first argument. + /// + /// Returns a [`PickerPattern1`] that can be further chained with additional + /// arguments, defaults, routes, and post-processing. + /// + /// # Type Parameters + /// + /// * `Next` — The nominal type of the first argument; must implement [`Pickable`]. + /// + /// # Parameters + /// + /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`]. + fn pick<Next>( + self, + arg: impl Into<&'a PickerArg<'a, Next>>, + ) -> PickerPattern1<'a, Next, ChainProcess<This>> + where + Self: Sized, + Next: Pickable<'a> + Default + Sized, + { + let picker = Self::to_picker(self); + Picker::build_pattern1(picker.into_args(), arg.into(), None) + } + + /// Starts building a picker pattern with the first argument, using a default value provider. + /// + /// This is a shorthand for calling `.pick(arg).or(func)`. + /// + /// # Type Parameters + /// + /// * `Next` — The nominal type of the first argument; must implement [`Pickable`]. + /// + /// # Parameters + /// + /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`]. + /// * `func` — A closure that provides a default value if the arg is not provided by the user. + fn pick_or<Next, F>( + self, + arg: impl Into<&'a PickerArg<'a, Next>>, + func: F, + ) -> PickerPattern1<'a, Next, ChainProcess<This>> + where + Self: Sized, + Next: Pickable<'a> + Default + Sized, + F: FnMut() -> Next + 'static, + { + self.pick(arg).or(func) + } + + /// Starts building a picker pattern with the first argument, using a default value. + /// + /// This is a shorthand for calling `.pick(arg).or_default()`. + /// + /// # Type Parameters + /// + /// * `Next` — The nominal type of the first argument; must implement [`Pickable`] and [`Default`]. + /// + /// # Parameters + /// + /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`]. + fn pick_or_default<Next>( + self, + arg: impl Into<&'a PickerArg<'a, Next>>, + ) -> PickerPattern1<'a, Next, ChainProcess<This>> + where + Self: Sized, + Next: Pickable<'a> + Default + Sized, + { + self.pick(arg).or_default() + } + + /// Starts building a picker pattern with the first argument, using a route if the arg is not provided. + /// + /// This is a shorthand for calling `.pick(arg).or_route(func)`. + /// + /// # Type Parameters + /// + /// * `Next` — The nominal type of the first argument; must implement [`Pickable`]. + /// + /// # Parameters + /// + /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`]. + /// * `func` — A closure that produces a route value if the arg is not provided by the user. + fn pick_or_route<Next, F>( + self, + arg: impl Into<&'a PickerArg<'a, Next>>, + func: F, + ) -> PickerPattern1<'a, Next, ChainProcess<This>> + where + Self: Sized, + Next: Pickable<'a> + Default + Sized, + F: FnMut() -> ChainProcess<This> + 'static, + { + self.pick(arg).or_route(func) + } +} + +impl<'a, This, Bind> EntryPicker<'a, This> for Bind +where + This: ProgramCollect<Enum = This>, + Bind: Grouped<This> + Into<Vec<String>>, +{ + fn to_picker(self) -> Picker<'a, ChainProcess<This>> { + let args = self.into(); + Picker::from(args) + } +} diff --git a/mingling/src/picker/global.rs b/mingling/src/picker/global.rs new file mode 100644 index 0000000..c203524 --- /dev/null +++ b/mingling/src/picker/global.rs @@ -0,0 +1,35 @@ +use arg_picker::{IntoPicker, Pickable, PickerArg, value::Flag}; +use mingling_core::{Program, ProgramCollect}; + +use crate::consts::REMAINS; + +/// Picks a global flag from the program's arguments. +/// +/// This function takes ownership of the program's current arguments, picks the specified `flag` +/// from them, and then returns the remaining arguments back to the program. It returns the +/// boolean value of the flag. +pub fn pick_global_flag<C>(program: &mut Program<C>, flag: &PickerArg<Flag>) -> bool +where + C: ProgramCollect<Enum = C>, +{ + let args = program.take_args(); + let (flag, args) = args.pick(flag).pick(&REMAINS).unwrap(); + program.replace_args(args.into()); + *flag +} + +/// Picks a global argument from the program's arguments. +/// +/// This function takes ownership of the program's current arguments, picks the specified `arg` +/// from them, and then returns the remaining arguments back to the program. It returns the +/// picked argument value, or `None` if the argument was not present. +pub fn pick_global_argument<C, A>(program: &mut Program<C>, arg: &PickerArg<A>) -> Option<A> +where + A: for<'a> Pickable<'a> + Default, + C: ProgramCollect<Enum = C>, +{ + let args = program.take_args(); + let (arg, remains) = args.pick(arg).pick(&REMAINS).unpack(); + program.replace_args(remains.unwrap().into()); + arg +} diff --git a/mingling/src/res/dirs/current_dir.rs b/mingling/src/res/dirs/current_dir.rs index 1de84e0..54d2b05 100644 --- a/mingling/src/res/dirs/current_dir.rs +++ b/mingling/src/res/dirs/current_dir.rs @@ -17,7 +17,7 @@ use std::{ /// a `Result`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ResCurrentDir { - cwd: PathBuf + cwd: PathBuf, } impl ResCurrentDir { @@ -27,13 +27,17 @@ impl ResCurrentDir { /// deleted or permissions are insufficient). Unlike the `Default` implementation, this /// method does not panic on failure. pub fn new() -> Result<Self, std::io::Error> { - Ok(Self { cwd: current_dir()? }) + Ok(Self { + cwd: current_dir()?, + }) } } impl Default for ResCurrentDir { fn default() -> Self { - Self { cwd: current_dir().unwrap() } + Self { + cwd: current_dir().unwrap(), + } } } @@ -45,7 +49,9 @@ impl From<PathBuf> for ResCurrentDir { impl From<&Path> for ResCurrentDir { fn from(path: &Path) -> Self { - Self { cwd: path.to_path_buf() } + Self { + cwd: path.to_path_buf(), + } } } diff --git a/mingling/src/res/dirs/current_exe.rs b/mingling/src/res/dirs/current_exe.rs index 051fcee..051f380 100644 --- a/mingling/src/res/dirs/current_exe.rs +++ b/mingling/src/res/dirs/current_exe.rs @@ -26,13 +26,17 @@ impl ResCurrentExe { /// filesystem is not available on Linux, or the process handle is invalid). /// Unlike the `Default` implementation, this method does not panic on failure. pub fn new() -> Result<Self, std::io::Error> { - Ok(Self { exe: current_exe()? }) + Ok(Self { + exe: current_exe()?, + }) } } impl Default for ResCurrentExe { fn default() -> Self { - Self { exe: current_exe().unwrap() } + Self { + exe: current_exe().unwrap(), + } } } @@ -44,7 +48,9 @@ impl From<PathBuf> for ResCurrentExe { impl From<&Path> for ResCurrentExe { fn from(path: &Path) -> Self { - Self { exe: path.to_path_buf() } + Self { + exe: path.to_path_buf(), + } } } diff --git a/mingling/src/res/dirs/home_dir.rs b/mingling/src/res/dirs/home_dir.rs index de2e5e7..fae3055 100644 --- a/mingling/src/res/dirs/home_dir.rs +++ b/mingling/src/res/dirs/home_dir.rs @@ -24,15 +24,18 @@ impl ResHomeDir { /// Returns `Err` if the home directory cannot be determined (e.g., the `HOME` or /// `USERPROFILE` environment variable is not set). pub fn new() -> Result<Self, std::io::Error> { - let home = home_dir_env() - .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "home directory not found"))?; + let home = home_dir_env().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::NotFound, "home directory not found") + })?; Ok(Self { home }) } } impl Default for ResHomeDir { fn default() -> Self { - Self { home: home_dir_env().expect("home directory not found") } + Self { + home: home_dir_env().expect("home directory not found"), + } } } @@ -44,7 +47,9 @@ impl From<PathBuf> for ResHomeDir { impl From<&Path> for ResHomeDir { fn from(path: &Path) -> Self { - Self { home: path.to_path_buf() } + Self { + home: path.to_path_buf(), + } } } diff --git a/mingling/src/res/dirs/temp_dir.rs b/mingling/src/res/dirs/temp_dir.rs index f4f0dca..343d9c7 100644 --- a/mingling/src/res/dirs/temp_dir.rs +++ b/mingling/src/res/dirs/temp_dir.rs @@ -40,7 +40,9 @@ impl From<PathBuf> for ResTempDir { impl From<&Path> for ResTempDir { fn from(path: &Path) -> Self { - Self { tmp: path.to_path_buf() } + Self { + tmp: path.to_path_buf(), + } } } diff --git a/mingling/src/setups.rs b/mingling/src/setups.rs index e572cf5..6fd728a 100644 --- a/mingling/src/setups.rs +++ b/mingling/src/setups.rs @@ -7,6 +7,13 @@ pub use dirs::*; mod exit_code; pub use exit_code::*; +/// Picker's `ProgramSetup` variant. +/// +/// Internally does not use its own argument parsing, +/// but relies on `arg_picker`'s argument parsing capability. +#[cfg(feature = "picker")] +pub mod picker; + #[cfg(feature = "structural_renderer")] mod structural_renderer; diff --git a/mingling/src/setups/basic.rs b/mingling/src/setups/basic.rs index 6a69733..e081c3e 100644 --- a/mingling/src/setups/basic.rs +++ b/mingling/src/setups/basic.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use mingling_core::{Flag, Program, ProgramCollect, setup::ProgramSetup}; /// Performs basic program initialization: @@ -5,6 +7,12 @@ use mingling_core::{Flag, Program, ProgramCollect, setup::ProgramSetup}; /// - Collects `--quiet` flag to control message rendering /// - Collects `--help` flag to enable help mode /// - Collects `--confirm` flag to skip user confirmation +#[cfg_attr( + feature = "picker", + deprecated( + note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::BasicProgramSetup` instead" + ) +)] pub struct BasicProgramSetup; impl<C> ProgramSetup<C> for BasicProgramSetup @@ -21,6 +29,12 @@ where /// Provides setup for parsing the user help flag /// /// The default value is `--help / -h` +#[cfg_attr( + feature = "picker", + deprecated( + note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::HelpFlagSetup` instead" + ) +)] pub struct HelpFlagSetup { flag: Flag, } @@ -54,6 +68,12 @@ impl Default for HelpFlagSetup { /// Provides setup for parsing the quiet flag /// /// The default value is `--quiet / -q` +#[cfg_attr( + feature = "picker", + deprecated( + note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::QuietFlagSetup` instead" + ) +)] pub struct QuietFlagSetup { flag: Flag, } @@ -88,6 +108,12 @@ impl Default for QuietFlagSetup { /// Provides setup for parsing the confirm flag /// /// The default value is `--confirm / -C` +#[cfg_attr( + feature = "picker", + deprecated( + note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::ConfirmFlagSetup` instead" + ) +)] pub struct ConfirmFlagSetup { flag: Flag, } diff --git a/mingling/src/setups/dirs.rs b/mingling/src/setups/dirs.rs index a7f81fa..1f5e2d2 100644 --- a/mingling/src/setups/dirs.rs +++ b/mingling/src/setups/dirs.rs @@ -1,9 +1,6 @@ use std::marker::PhantomData; -use mingling_core::{ - ProgramCollect, - setup::ProgramSetup, -}; +use mingling_core::{ProgramCollect, setup::ProgramSetup}; use crate::res::{ResCurrentDir, ResCurrentExe, ResHomeDir, ResTempDir}; diff --git a/mingling/src/setups/exit_code.rs b/mingling/src/setups/exit_code.rs index 025ed8a..6b8a1ef 100644 --- a/mingling/src/setups/exit_code.rs +++ b/mingling/src/setups/exit_code.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use mingling_core::{ ProgramCollect, - hook::{ProgramControlUnit, ProgramHook}, + hook::{ProgramControlUnit, ProgramControls, ProgramHook}, setup::ProgramSetup, this, }; @@ -39,7 +39,14 @@ where // Insert hook to override exit code before program ends program.with_hook(ProgramHook::empty().on_finish(|_| { let this = this::<C>().res_or_default::<ResExitCode>(); - ProgramControlUnit::OverrideExitCode(this.exit_code) + let ec = this.exit_code; + + // Only override when ResExitCode has been modified + if ec != 0 { + ProgramControlUnit::OverrideExitCode(this.exit_code).into() + } else { + ProgramControls::Empty + } })); } } diff --git a/mingling/src/setups/picker.rs b/mingling/src/setups/picker.rs new file mode 100644 index 0000000..0b7bd33 --- /dev/null +++ b/mingling/src/setups/picker.rs @@ -0,0 +1,7 @@ +mod basic; +pub use basic::*; + +#[cfg(feature = "structural_renderer")] +mod structural_renderer; +#[cfg(feature = "structural_renderer")] +pub use structural_renderer::*; diff --git a/mingling/src/setups/picker/basic.rs b/mingling/src/setups/picker/basic.rs new file mode 100644 index 0000000..c9f82b3 --- /dev/null +++ b/mingling/src/setups/picker/basic.rs @@ -0,0 +1,123 @@ +use arg_picker::{PickerArg, value::Flag}; +use mingling_core::{Program, ProgramCollect, setup::ProgramSetup}; + +use crate::{ + consts::{CONFIRM_FLAG, HELP_FLAG, QUIET_FLAG}, + picker::pick_global_flag, +}; + +/// Performs basic program initialization: +/// +/// - Collects `--quiet` flag to control message rendering +/// - Collects `--help` flag to enable help mode +/// - Collects `--confirm` flag to skip user confirmation +pub struct BasicProgramSetup; + +impl<C> ProgramSetup<C> for BasicProgramSetup +where + C: ProgramCollect<Enum = C>, +{ + fn setup(self, program: &mut Program<C>) { + program.with_setup(HelpFlagSetup::default()); + program.with_setup(QuietFlagSetup::default()); + program.with_setup(ConfirmFlagSetup::default()); + } +} + +/// Provides setup for parsing the user help flag +/// +/// The default value is `--help / -h` +pub struct HelpFlagSetup<'a> { + flag: &'a PickerArg<'a, Flag>, +} + +impl<'a> HelpFlagSetup<'a> { + /// Creates a new `HelpFlagSetup` with the given flag aliases. + pub fn new(flag: &'a PickerArg<Flag>) -> Self { + Self { flag } + } +} + +impl<'a, C> ProgramSetup<C> for HelpFlagSetup<'a> +where + C: ProgramCollect<Enum = C>, +{ + fn setup(self, program: &mut Program<C>) { + let help = pick_global_flag(program, self.flag); + if help { + program.user_context.help = true; + } + } +} + +impl<'a> Default for HelpFlagSetup<'a> { + fn default() -> Self { + Self { flag: &HELP_FLAG } + } +} + +/// Provides setup for parsing the quiet flag +/// +/// The default value is `--quiet / -q` +pub struct QuietFlagSetup<'a> { + flag: &'a PickerArg<'a, Flag>, +} + +impl<'a> QuietFlagSetup<'a> { + /// Creates a new `QuietFlagSetup` with the given flag aliases. + pub fn new(flag: &'a PickerArg<Flag>) -> Self { + Self { flag } + } +} + +impl<'a, C> ProgramSetup<C> for QuietFlagSetup<'a> +where + C: ProgramCollect<Enum = C>, +{ + fn setup(self, program: &mut Program<C>) { + let help = pick_global_flag(program, self.flag); + if help { + program.stdout_setting.quiet = true; + } + } +} + +impl<'a> Default for QuietFlagSetup<'a> { + fn default() -> Self { + Self { flag: &QUIET_FLAG } + } +} + +/// Provides setup for parsing the confirm flag +/// +/// The default value is `--confirm / -C` +pub struct ConfirmFlagSetup<'a> { + flag: &'a PickerArg<'a, Flag>, +} + +impl<'a> ConfirmFlagSetup<'a> { + /// Creates a new `ConfirmFlagSetup` with the given flag aliases. + pub fn new(flag: &'a PickerArg<Flag>) -> Self { + Self { flag } + } +} + +impl<'a, C> ProgramSetup<C> for ConfirmFlagSetup<'a> +where + C: ProgramCollect<Enum = C>, +{ + fn setup(self, program: &mut Program<C>) { + let help = pick_global_flag(program, self.flag); + if help { + program.user_context.confirm = true; + } + } +} + +impl<'a> Default for ConfirmFlagSetup<'a> { + fn default() -> Self { + Self { + flag: &CONFIRM_FLAG, + } + } +} diff --git a/mingling/src/setups/picker/structural_renderer.rs b/mingling/src/setups/picker/structural_renderer.rs new file mode 100644 index 0000000..1fa48fd --- /dev/null +++ b/mingling/src/setups/picker/structural_renderer.rs @@ -0,0 +1,76 @@ +use mingling_core::{Program, ProgramCollect, setup::ProgramSetup}; + +use crate::{ + consts::RENDERER_ARG, + picker::{pick_global_argument, pick_global_flag}, +}; + +/// Sets up the structural renderer for the program: +/// +/// - Adds a `--renderer` global argument to specify the renderer type +pub struct StructuralRendererSimpleSetup; + +impl<C> ProgramSetup<C> for StructuralRendererSimpleSetup +where + C: ProgramCollect<Enum = C>, +{ + fn setup(self, program: &mut Program<C>) { + if let Some(renderer) = pick_global_argument(program, &RENDERER_ARG) { + program.structural_renderer_name = renderer.into(); + } + } +} + +/// Sets up the structural renderer for the program: +/// +/// - Adds global flags to specify the renderer type: +/// * `--json` for JSON output +/// * `--json-pretty` for pretty-printed JSON output +/// * `--yaml` for YAML output +/// * `--toml` for TOML output +/// * `--ron` for RON output +/// * `--ron-pretty` for pretty-printed RON output +/// +/// # Flag priority +/// +/// If multiple flags are specified, the last matching flag in the following +/// declaration order takes precedence: +/// 1. `--json` +/// 2. `--json-pretty` +/// 3. `--yaml` +/// 4. `--toml` +/// 5. `--ron` +/// 6. `--ron-pretty` +pub struct StructuralRendererSetup; + +impl<C> ProgramSetup<C> for StructuralRendererSetup +where + C: ProgramCollect<Enum = C>, +{ + fn setup(self, program: &mut Program<C>) { + #[cfg(feature = "json_serde_fmt")] + if pick_global_flag(program, &crate::consts::JSON_FLAG) { + program.structural_renderer_name = crate::StructuralRendererSetting::Json; + } + #[cfg(feature = "json_serde_fmt")] + if pick_global_flag(program, &crate::consts::JSON_PRETTY_FLAG) { + program.structural_renderer_name = crate::StructuralRendererSetting::JsonPretty; + } + #[cfg(feature = "yaml_serde_fmt")] + if pick_global_flag(program, &crate::consts::YAML_FLAG) { + program.structural_renderer_name = crate::StructuralRendererSetting::Yaml; + } + #[cfg(feature = "toml_serde_fmt")] + if pick_global_flag(program, &crate::consts::TOML_FLAG) { + program.structural_renderer_name = crate::StructuralRendererSetting::Toml; + } + #[cfg(feature = "ron_serde_fmt")] + if pick_global_flag(program, &crate::consts::RON_FLAG) { + program.structural_renderer_name = crate::StructuralRendererSetting::Ron; + } + #[cfg(feature = "ron_serde_fmt")] + if pick_global_flag(program, &crate::consts::RON_PRETTY_FLAG) { + program.structural_renderer_name = crate::StructuralRendererSetting::RonPretty; + } + } +} diff --git a/mingling/src/setups/structural_renderer.rs b/mingling/src/setups/structural_renderer.rs index af3ed91..0ab2347 100644 --- a/mingling/src/setups/structural_renderer.rs +++ b/mingling/src/setups/structural_renderer.rs @@ -1,8 +1,16 @@ +#![allow(deprecated)] + use mingling_core::{Program, ProgramCollect, setup::ProgramSetup}; /// Sets up the structural renderer for the program: /// /// - Adds a `--renderer` global argument to specify the renderer type +#[cfg_attr( + feature = "picker", + deprecated( + note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::StructuralRendererSimpleSetup` instead" + ) +)] pub struct StructuralRendererSimpleSetup; impl<C> ProgramSetup<C> for StructuralRendererSimpleSetup @@ -25,6 +33,12 @@ where /// * `--toml` for TOML output /// * `--ron` for RON output /// * `--ron-pretty` for pretty-printed RON output +#[cfg_attr( + feature = "picker", + deprecated( + note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::StructuralRendererSetup` instead" + ) +)] pub struct StructuralRendererSetup; impl<C> ProgramSetup<C> for StructuralRendererSetup |
