cli_command/
lib.rs

1//! # cli-command
2//!
3//! A lightweight and ergonomic command-line argument parser for Rust applications.
4//!
5//! ## Features
6//!
7//! - ๐Ÿš€ **Minimal dependencies** - Only 3 lightweight dependencies for macro support
8//! - ๐ŸŽฏ **Dual API design** - Both method-based and macro-based interfaces
9//! - ๐Ÿ”ง **Flexible parsing** - Supports both `-` and `--` argument prefixes
10//! - ๐Ÿ“ **Type conversion** - Built-in support for common types
11//! - โšก **Error handling** - Comprehensive error types with helpful messages
12//! - ๐Ÿงช **Well tested** - Extensive test coverage
13//! - ๐ŸŽจ **Macro ergonomics** - `cli_args!` macro for boilerplate-free argument extraction
14//! - ๐ŸŽญ **Command matching** - `cli_match!` macro for clean command routing
15//!
16//! ## Quick Start
17//!
18//! ```rust
19//! use cli_command::{parse_command_line, Command};
20//!
21//! fn main() -> Result<(), Box<dyn std::error::Error>> {
22//!     // Parse command line arguments
23//!     let cmd = parse_command_line().unwrap();
24//!     
25//!     // Get a simple argument
26//!     if let Some(port) = cmd.get_argument("port") {
27//!         println!("Port: {}", port);
28//!     }
29//!     
30//!     // Get a required argument with type conversion
31//!     let threads: usize = cmd.get_argument_or_default("threads", 4).unwrap();
32//!     println!("Threads: {}", threads);
33//!     
34//!     // Get argument with default value
35//!     let timeout: u64 = cmd.get_argument_or_default("timeout", 30).unwrap();
36//!     println!("Timeout: {}", timeout);
37//!     
38//!     Ok(())
39//! }
40//! ```
41//!
42//! ## Examples
43//!
44//! See the `examples/` directory for complete working examples:
45//! - `simple_server.rs` - A web server with configuration options
46//! - `file_processor.rs` - A file processing tool with multiple subcommands
47//!
48//! ## Error Handling
49//!
50//! The crate provides comprehensive error handling with helpful error messages:
51//!
52//! ```rust
53//! use cli_command::{CliError, CliErrorKind};
54//!
55//! use cli_command::parse_command_string;
56//! let cmd = parse_command_string("--required_arg value").unwrap();
57//! match cmd.get_argument_mandatory("required_arg") {
58//!     Ok(value) => println!("Got: {}", value),
59//!     Err(CliError { kind: CliErrorKind::MissingArgument(arg), .. }) => {
60//!         eprintln!("Missing required argument: {}", arg);
61//!     }
62//!     Err(e) => eprintln!("Error: {}", e),
63//! }
64//! ```
65
66pub mod cli_error;
67pub mod command;
68pub mod parse;
69
70pub use cli_error::{from_error, CliError, CliErrorKind};
71pub use command::Command;
72pub use parse::{parse_command_line, parse_command_string};
73
74
75/// Argument extraction macro
76///
77/// This macro provides a convenient way to extract command-line arguments
78/// with default values in a single expression. It automatically parses the
79/// command line, so you don't need to call `parse_command_line()` yourself.
80///
81/// # Syntax
82///
83/// ```rust
84/// use cli_command::cli_args;
85/// 
86/// fn main() -> Result<(), Box<dyn std::error::Error>> {
87///     let (port, host, verbose) = cli_args!(
88///         port: u16 = 8080,
89///         host: String = "localhost".to_string(),
90///         verbose: bool = false
91///     );
92///     Ok(())
93/// }
94/// ```
95///
96/// # Examples
97///
98/// ```rust
99/// use cli_command::cli_args;
100///
101/// fn main() -> Result<(), Box<dyn std::error::Error>> {
102///     let (port, host, workers, verbose) = cli_args!(
103///         port: u16 = 8080,
104///         host: String = "localhost".to_string(),
105///         workers: usize = 4,
106///         verbose: bool = false
107///     );
108///     
109///     println!("Server: {}:{} (workers: {}, verbose: {})", host, port, workers, verbose);
110///     Ok(())
111/// }
112/// ```
113#[macro_export]
114macro_rules! cli_args {
115    ( $( $name:ident : $ty:ty = $default:expr ),* $(,)? ) => {
116        {
117            let cmd = $crate::parse_command_line()?;
118
119            $(
120                let $name: $ty = cmd.get_argument_or_default(stringify!($name), $default)?;
121            )*
122
123            ($($name),*)
124        }
125    };
126}
127
128/// Command matching macro
129///
130/// This macro provides a convenient way to match command names and automatically
131/// parse the command line. It eliminates the need to manually call `parse_command_line()`.
132///
133/// # Syntax
134///
135/// ```rust
136/// use cli_command::cli_match;
137/// 
138/// fn main() -> Result<(), Box<dyn std::error::Error>> {
139///     cli_match! {
140///         "command1" => { /* handle command1 */ Ok(()) },
141///         "command2" => { /* handle command2 */ Ok(()) },
142///         _ => { /* handle unknown commands */ Ok(()) }
143///     }
144/// }
145/// ```
146///
147/// # Examples
148///
149/// ```rust
150/// use cli_command::{cli_match, cli_args};
151///
152/// fn start_server(port: u16, host: String) -> Result<(), Box<dyn std::error::Error>> {
153///     println!("Starting server on {}:{}", host, port);
154///     Ok(())
155/// }
156///
157/// fn build_project(output: String, release: bool) -> Result<(), Box<dyn std::error::Error>> {
158///     println!("Building project to {} (release: {})", output, release);
159///     Ok(())
160/// }
161///
162/// fn print_help() -> Result<(), Box<dyn std::error::Error>> {
163///     println!("Available commands: serve, build, help");
164///     Ok(())
165/// }
166///
167/// fn main() -> Result<(), Box<dyn std::error::Error>> {
168///     cli_match! {
169///         "serve" => {
170///             let (port, host) = cli_args!(
171///                 port: u16 = 8080,
172///                 host: String = "localhost".to_string()
173///             );
174///             start_server(port, host)
175///         },
176///         "build" => {
177///             let (output, release) = cli_args!(
178///                 output: String = "dist".to_string(),
179///                 release: bool = false
180///             );
181///             build_project(output, release)
182///         },
183///         "help" => print_help(),
184///         _ => {
185///             eprintln!("Unknown command");
186///             print_help();
187///             Ok(())
188///         }
189///     }
190/// }
191/// ```
192#[macro_export]
193macro_rules! cli_match {
194    ( $( $pattern:pat => $expr:expr ),* $(,)? ) => {
195        {
196            let cmd = $crate::parse_command_line()?;
197
198            match cmd.name.as_str() {
199                $( $pattern => $expr ),*
200            }
201        }
202    };
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_basic_usage() -> Result<(), CliError> {
211        let cmd = parse_command_string("serve --port 8080 --host localhost")?;
212
213        assert_eq!(cmd.name, "serve");
214        assert_eq!(cmd.get_argument("port"), Some("8080"));
215        assert_eq!(cmd.get_argument("host"), Some("localhost"));
216
217        Ok(())
218    }
219
220    #[test]
221    fn test_type_conversion() -> Result<(), CliError> {
222        let cmd = parse_command_string("--port 8080 --ratio 0.5 --enabled true")?;
223
224        assert_eq!(cmd.get_argument_usize("port"), Some(8080));
225        assert_eq!(cmd.get_argument_f64("ratio"), Some(0.5));
226        assert_eq!(cmd.get_argument_bool("enabled"), Some(true));
227
228        Ok(())
229    }
230
231    #[test]
232    fn test_default_values() -> Result<(), CliError> {
233        let cmd = parse_command_string("--port 8080")?;
234
235        let port: u16 = cmd.get_argument_or_default("port", 3000)?;
236        assert_eq!(port, 8080);
237
238        let timeout: u64 = cmd.get_argument_or_default("timeout", 30)?;
239        assert_eq!(timeout, 30);
240
241        Ok(())
242    }
243}