cli-command 0.1.0

A lightweight and ergonomic command-line argument parser for Rust
Documentation
//! # cli-command
//!
//! A lightweight and ergonomic command-line argument parser for Rust applications.
//!
//! ## Features
//!
//! - ๐Ÿš€ **Minimal dependencies** - Only 3 lightweight dependencies for macro support
//! - ๐ŸŽฏ **Dual API design** - Both method-based and macro-based interfaces
//! - ๐Ÿ”ง **Flexible parsing** - Supports both `-` and `--` argument prefixes
//! - ๐Ÿ“ **Type conversion** - Built-in support for common types
//! - โšก **Error handling** - Comprehensive error types with helpful messages
//! - ๐Ÿงช **Well tested** - Extensive test coverage
//! - ๐ŸŽจ **Macro ergonomics** - `cli_args!` macro for boilerplate-free argument extraction
//! - ๐ŸŽญ **Command matching** - `cli_match!` macro for clean command routing
//!
//! ## Quick Start
//!
//! ```rust
//! use cli_command::{parse_command_line, Command};
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     // Parse command line arguments
//!     let cmd = parse_command_line().unwrap();
//!     
//!     // Get a simple argument
//!     if let Some(port) = cmd.get_argument("port") {
//!         println!("Port: {}", port);
//!     }
//!     
//!     // Get a required argument with type conversion
//!     let threads: usize = cmd.get_argument_or_default("threads", 4).unwrap();
//!     println!("Threads: {}", threads);
//!     
//!     // Get argument with default value
//!     let timeout: u64 = cmd.get_argument_or_default("timeout", 30).unwrap();
//!     println!("Timeout: {}", timeout);
//!     
//!     Ok(())
//! }
//! ```
//!
//! ## Examples
//!
//! See the `examples/` directory for complete working examples:
//! - `simple_server.rs` - A web server with configuration options
//! - `file_processor.rs` - A file processing tool with multiple subcommands
//!
//! ## Error Handling
//!
//! The crate provides comprehensive error handling with helpful error messages:
//!
//! ```rust
//! use cli_command::{CliError, CliErrorKind};
//!
//! use cli_command::parse_command_string;
//! let cmd = parse_command_string("--required_arg value").unwrap();
//! match cmd.get_argument_mandatory("required_arg") {
//!     Ok(value) => println!("Got: {}", value),
//!     Err(CliError { kind: CliErrorKind::MissingArgument(arg), .. }) => {
//!         eprintln!("Missing required argument: {}", arg);
//!     }
//!     Err(e) => eprintln!("Error: {}", e),
//! }
//! ```

pub mod cli_error;
pub mod command;
pub mod parse;

pub use cli_error::{from_error, CliError, CliErrorKind};
pub use command::Command;
pub use parse::{parse_command_line, parse_command_string};


/// Argument extraction macro
///
/// This macro provides a convenient way to extract command-line arguments
/// with default values in a single expression. It automatically parses the
/// command line, so you don't need to call `parse_command_line()` yourself.
///
/// # Syntax
///
/// ```rust
/// use cli_command::cli_args;
/// 
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let (port, host, verbose) = cli_args!(
///         port: u16 = 8080,
///         host: String = "localhost".to_string(),
///         verbose: bool = false
///     );
///     Ok(())
/// }
/// ```
///
/// # Examples
///
/// ```rust
/// use cli_command::cli_args;
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let (port, host, workers, verbose) = cli_args!(
///         port: u16 = 8080,
///         host: String = "localhost".to_string(),
///         workers: usize = 4,
///         verbose: bool = false
///     );
///     
///     println!("Server: {}:{} (workers: {}, verbose: {})", host, port, workers, verbose);
///     Ok(())
/// }
/// ```
#[macro_export]
macro_rules! cli_args {
    ( $( $name:ident : $ty:ty = $default:expr ),* $(,)? ) => {
        {
            let cmd = $crate::parse_command_line()?;

            $(
                let $name: $ty = cmd.get_argument_or_default(stringify!($name), $default)?;
            )*

            ($($name),*)
        }
    };
}

/// Command matching macro
///
/// This macro provides a convenient way to match command names and automatically
/// parse the command line. It eliminates the need to manually call `parse_command_line()`.
///
/// # Syntax
///
/// ```rust
/// use cli_command::cli_match;
/// 
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
///     cli_match! {
///         "command1" => { /* handle command1 */ Ok(()) },
///         "command2" => { /* handle command2 */ Ok(()) },
///         _ => { /* handle unknown commands */ Ok(()) }
///     }
/// }
/// ```
///
/// # Examples
///
/// ```rust
/// use cli_command::{cli_match, cli_args};
///
/// fn start_server(port: u16, host: String) -> Result<(), Box<dyn std::error::Error>> {
///     println!("Starting server on {}:{}", host, port);
///     Ok(())
/// }
///
/// fn build_project(output: String, release: bool) -> Result<(), Box<dyn std::error::Error>> {
///     println!("Building project to {} (release: {})", output, release);
///     Ok(())
/// }
///
/// fn print_help() -> Result<(), Box<dyn std::error::Error>> {
///     println!("Available commands: serve, build, help");
///     Ok(())
/// }
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
///     cli_match! {
///         "serve" => {
///             let (port, host) = cli_args!(
///                 port: u16 = 8080,
///                 host: String = "localhost".to_string()
///             );
///             start_server(port, host)
///         },
///         "build" => {
///             let (output, release) = cli_args!(
///                 output: String = "dist".to_string(),
///                 release: bool = false
///             );
///             build_project(output, release)
///         },
///         "help" => print_help(),
///         _ => {
///             eprintln!("Unknown command");
///             print_help();
///             Ok(())
///         }
///     }
/// }
/// ```
#[macro_export]
macro_rules! cli_match {
    ( $( $pattern:pat => $expr:expr ),* $(,)? ) => {
        {
            let cmd = $crate::parse_command_line()?;

            match cmd.name.as_str() {
                $( $pattern => $expr ),*
            }
        }
    };
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic_usage() -> Result<(), CliError> {
        let cmd = parse_command_string("serve --port 8080 --host localhost")?;

        assert_eq!(cmd.name, "serve");
        assert_eq!(cmd.get_argument("port"), Some("8080"));
        assert_eq!(cmd.get_argument("host"), Some("localhost"));

        Ok(())
    }

    #[test]
    fn test_type_conversion() -> Result<(), CliError> {
        let cmd = parse_command_string("--port 8080 --ratio 0.5 --enabled true")?;

        assert_eq!(cmd.get_argument_usize("port"), Some(8080));
        assert_eq!(cmd.get_argument_f64("ratio"), Some(0.5));
        assert_eq!(cmd.get_argument_bool("enabled"), Some(true));

        Ok(())
    }

    #[test]
    fn test_default_values() -> Result<(), CliError> {
        let cmd = parse_command_string("--port 8080")?;

        let port: u16 = cmd.get_argument_or_default("port", 3000)?;
        assert_eq!(port, 8080);

        let timeout: u64 = cmd.get_argument_or_default("timeout", 30)?;
        assert_eq!(timeout, 30);

        Ok(())
    }
}