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}