cliargs/lib.rs
1// Copyright (C) 2024 Takayuki Sato. All Rights Reserved.
2// This program is free software under MIT License.
3// See the file LICENSE in this distribution for more details.
4
5//! This crate is a library to parse command line arguments.
6//!
7//! This crate provides the following functionalities:
8//!
9//! - Supports [POSIX][posix] & [GNU][gnu] like short and long options.
10//! - This crate supports `--` option.
11//! - This library doesn't support numeric short option.
12//! - This library supports not `-ofoo` but `-o=foo` as an alternative to
13//! `-o foo` for short option.
14//! - Supports parsing with option configurations.
15//! - Supports parsing with option configurations made from struct fields and attributes, and
16//! setting the option values to them.
17//! - Supports parsing command line arguments including sub commands.
18//! - Generates help text from option configurations.
19//!
20//! [posix]: https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html#Argument-Syntax
21//! [gnu]: https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html
22//!
23//! ## Install
24//!
25//! In `Cargo.toml`, write this crate as a dependency.
26//!
27//! ```toml
28//! [dependencies]
29//! cliargs = "0.6.0"
30//! ```
31//!
32//! ## Usage
33//!
34//! This crate provides the `Cmd` strcut to parse command line arguments.
35//! The usage of this `Cmd` struct is as follows:
36//!
37//! ### Creates a `Cmd` instance
38//!
39//! The `Cmd::new` function creates a `Cmd` instance with original command line
40//! arguments.
41//! This function uses `std::env::arg_os` and `OsString#into_string` to read
42//! command line arguments in order to avoid `panic!` call that the user cannot
43//! control.
44//!
45//! ```rust
46//! use cliargs::Cmd;
47//! use cliargs::errors::InvalidOsArg;
48//!
49//! let cmd = match Cmd::new() {
50//! Ok(cmd) => cmd,
51//! Err(InvalidOsArg::OsArgsContainInvalidUnicode { index, os_arg }) => {
52//! panic!("Invalid Unicode data: {:?} (index: {})", os_arg, index);
53//! }
54//! };
55//! ```
56//!
57//! ### Creates a `Cmd` instance with the specified `String` array
58//!
59//! The `Cmd::with_strings` function creates a `Cmd` instance with the
60//! specified `String` array.
61//!
62//! ```rust
63//! use cliargs::Cmd;
64//! use std::env;
65//!
66//! let cmd = Cmd::with_strings(env::args());
67//! ```
68//!
69//! ### Creates a `Cmd` instance with the specified `OsString` array.
70//!
71//! ```rust
72//! use cliargs::Cmd;
73//! use cliargs::errors::InvalidOsArg;
74//! use std::env;
75//!
76//! let cmd = match Cmd::with_os_strings(env::args_os()) {
77//! Ok(cmd) => cmd,
78//! Err(InvalidOsArg::OsArgsContainInvalidUnicode { index, os_arg }) => {
79//! panic!("Invalid Unicode data: {:?} (index: {})", os_arg, index);
80//! }
81//! };
82//! ```
83//!
84//! ## Parses without configurations
85//!
86//! The `Cmd` struct has the method which parses command line arguments without
87//! configurations.
88//! This method automatically divides command line arguments to options and
89//! command arguments.
90//!
91//! Command line arguments starts with `-` or `--` are options, and others are
92//! command arguments.
93//! If you want to specify a value to an option, follows `"="` and the value
94//! after the option, like `foo=123`.
95//!
96//! All command line arguments after `--` are command arguments, even they
97//! starts with `-` or `--`.
98//!
99//! ```rust
100//! use cliargs::Cmd;
101//! use cliargs::errors::InvalidOption;
102//!
103//! let mut cmd = Cmd::with_strings([ /* ... */ ]);
104//! match cmd.parse() {
105//! Ok(_) => { /* ... */ },
106//! Err(InvalidOption::OptionContainsInvalidChar { option }) => {
107//! panic!("Option contains invalid character: {option}");
108//! },
109//! Err(err) => panic!("Invalid option: {}", err.option()),
110//! }
111//! ```
112//!
113//! ## Parses with configurations
114//!
115//! The `Cmd` struct has the method `parse_with` which parses command line
116//! arguments with configurations.
117//! This method takes an array of option configurations: `OptCfg`, and divides
118//! command line arguments to options and command arguments according to this
119//! configurations..
120//!
121//! An option configuration has fields: `store_key`, `names`, `has_arg`,
122//! `is_array`, `defaults`, `desc`, `arg_in_help`, and `validator`.
123//!
124//! `store_key` field is specified the key name to store the option value to
125//! the option map in the `Cmd` instance.
126//! If this field is not specified, the first element of `names` field is used
127//! instead.
128//!
129//! `names` field is a string array and specified the option names, that are
130//! both long options and short options.
131//! The order of elements in this field is used in a help text.
132//! If you want to prioritize the output of short option name first in the help
133//! text, like `-f, --foo-bar`, but use the long option name as the key in the
134//! option map, write `store_key` and `names` fields as follows:
135//! `OptCfg::with([store_key("foo-bar"), names(&["f", "foo-bar"])])`.
136//!
137//! `has_arg` field indicates the option requires one or more values.
138//! `is_array` field indicates the option can have multiple values.
139//! `defaults` field is an array of string which is used as default one or more
140//! option arguments if the option is not specified.
141//! `desc` is a description of the option for help text.
142//! `arg_n_help` field is a text which is output after option name and aliases
143//! as an option value in help text.
144//!
145//! `validator` field is to set a function pointer which validates an option
146//! argument.
147//! This crate provides the validator `cliargs::validators::validate_number<T>`
148//! which validates whether an option argument is valid format as a number.
149//!
150//! In addition,the help printing for an array of [OptCfg] is generated with [Help].
151//!
152//! ```rust
153//! use cliargs::{Cmd, OptCfg};
154//! use cliargs::OptCfgParam::{names, has_arg, defaults, validator, desc, arg_in_help};
155//! use cliargs::validators::validate_number;
156//! use cliargs::errors::InvalidOption;
157//! use cliargs::Help;
158//!
159//! let mut cmd = Cmd::with_strings([ /* ... */ ]);
160//! let opt_cfgs = vec![
161//! OptCfg {
162//! store_key: "foo_bar".to_string(),
163//! names: vec!["foo-bar".to_string(), "f".to_string()],
164//! has_arg: true,
165//! is_array: false,
166//! defaults: Some(vec![123.to_string()]),
167//! desc: "This is description of foo-bar.".to_string(),
168//! arg_in_help: "<num>".to_string(),
169//! validator: validate_number::<u64>,
170//! },
171//! OptCfg::with([
172//! names(&["baz", "z"]),
173//! has_arg(true),
174//! defaults(&["1"]),
175//! desc("This is description of baz."),
176//! arg_in_help("<num>"),
177//! validator(validate_number::<u64>),
178//! ]),
179//! ];
180//!
181//! match cmd.parse_with(opt_cfgs) {
182//! Ok(_) => { /* ... */ },
183//! Err(InvalidOption::OptionContainsInvalidChar { option }) => { /* ... */ },
184//! Err(InvalidOption::UnconfiguredOption { option }) => { /* ... */ },
185//! Err(InvalidOption::OptionNeedsArg { option, .. }) => { /* ... */ },
186//! Err(InvalidOption::OptionTakesNoArg { option, .. }) => { /* ... */ },
187//! Err(InvalidOption::OptionIsNotArray { option, .. }) => { /* ... */ },
188//! Err(InvalidOption::OptionArgIsInvalid { option, opt_arg, details, .. }) => { /* ... */ },
189//! Err(err) => panic!("Invalid option: {}", err.option()),
190//! }
191//!
192//! let opt_cfgs = cmd.opt_cfgs();
193//!
194//! let mut help = Help::new();
195//! help.add_text("This is the usage description.".to_string());
196//! help.add_opts_with_margins(opt_cfgs, 2, 0);
197//! help.print();
198//!
199//! // (stdout)
200//! // This is the usage description.
201//! // --foo-bar, -f This is description of foo-bar.
202//! // --bar, -z <num> This is description of baz.
203//! ```
204//!
205//! ## Parse for a OptStore struct
206//!
207//! The [Cmd] struct has the method parse_for which parses command line arguments and set their option values to
208//! the option store which is passed as an argument.
209//!
210//! This method divides command line arguments to command arguments and options, then sets each option value to a
211//! curresponding field of the option store.
212//!
213//! Within this method, a vector of [OptCfg] is made from the fields of the option store. This [OptCfg] vector is
214//! set to the public field cfgs of the [Cmd] instance. If you want to access this option configurations, get them
215//! from this field.
216//! An option configuration corresponding to each field of an option store is determined by its type and opt field
217//! attribute.
218//! If the type is bool, the option takes no argument. If the type is integer, floating point number or string, the
219//! option can takes single option argument, therefore it can appear once in command line arguments.
220//! If the type is a vector, the option can takes multiple option arguments, therefore it can appear multiple times
221//! in command line arguments.
222//!
223//! A `opt` field attribute can have the following pairs of name and value: one is `cfg` to specify `names` and
224//! `defaults` fields of [OptCfg] struct, another is `desc` to specify `desc` field, and yet another is `arg` to
225//! specify `arg_in_help` field.
226//!
227//! The format of `cfg` is like `cfg="f,foo=123"`. The left side of the equal sign is the option name(s), and the
228//! right side is the default value(s).
229//! If there is no equal sign, it is determined that only the option name is specified.
230//! If you want to specify multiple option names, separate them with commas.
231//! If you want to specify multiple default values, separate them with commas and round them with square brackets,
232//! like `[1,2,3]`.
233//! If you want to use your favorite carachter as a separator, you can use it by putting it on the left side of the
234//! open square bracket, like `/[1/2/3]`.
235//!
236//! NOTE: A default value of empty string array option in a field attribute is `[]`, like `#[opt(cfg="=[]")]`, but
237//! it doesn't represent an array which contains only one empty string.
238//! If you want to specify an array which contains only one emtpy string, write nothing after `=` symbol, like
239//! `#[opt(cfg="=")]`.
240//!
241//! ```rust
242//! use cliargs::Cmd;
243//! use cliargs::errors::InvalidOption;
244//! use cliargs::Help;
245//!
246//! #[derive(cliargs::OptStore)]
247//! struct MyOptions {
248//! #[opt(cfg = "f,foo-bar", desc="The description of foo_bar.")]
249//! foo_bar: bool,
250//! #[opt(cfg = "b,baz", desc="The description of baz.", arg="<s>")]
251//! baz: String,
252//! }
253//! let mut my_options = MyOptions::with_defaults();
254//!
255//! let mut cmd = Cmd::with_strings([ /* ... */ ]);
256//! match cmd.parse_for(&mut my_options) {
257//! Ok(_) => { /* ... */ },
258//! Err(InvalidOption::OptionContainsInvalidChar { option }) => { /* ... */ },
259//! Err(InvalidOption::UnconfiguredOption { option }) => { /* ... */ },
260//! Err(InvalidOption::OptionNeedsArg { option, .. }) => { /* ... */ },
261//! Err(InvalidOption::OptionTakesNoArg { option, .. }) => { /* ... */ },
262//! Err(InvalidOption::OptionIsNotArray { option, .. }) => { /* ... */ },
263//! Err(InvalidOption::OptionArgIsInvalid { option, opt_arg, details, .. }) => { /* ... */ },
264//! Err(err) => panic!("Invalid option: {}", err.option()),
265//! }
266//!
267//! let opt_cfgs = cmd.opt_cfgs();
268//!
269//! let mut help = Help::new();
270//! help.add_text("This is the usage description.".to_string());
271//! help.add_opts_with_margins(opt_cfgs, 2, 0);
272//! help.print();
273//!
274//! // (stdout)
275//! // This is the usage description.
276//! // -f, --foo-bar This is description of foo_bar.
277//! // -z, --baz <s> This is description of baz.
278//! ```
279//!
280//! ## Parse command line arguments including sub command
281//!
282//! This crate provides methods [Cmd::parse_until_sub_cmd], [Cmd::parse_until_sub_cmd_with], and
283//! [Cmd::parse_until_sub_cmd_for] for parsing command line arguments including sub commands.
284//!
285//! These methods correspond to [Cmd::parse], [Cmd::parse_with], and [Cmd::parse_for],
286//! respectively, and behave the same except that they stop parsing before the first command
287//! argument (= sub command) and
288//! return a [Cmd] instance containing the arguments starting from the the sub command.
289//!
290//! The folowing is an example code using [Cmd::parse_until_sub_cmd]:
291//!
292//! ```rust
293//! use cliargs::Cmd;
294//! use cliargs::errors::InvalidOption;
295//!
296//! let mut cmd = Cmd::with_strings([ /* ... */ ]);
297//!
298//! match cmd.parse_until_sub_cmd() {
299//! Ok(Some(mut sub_cmd)) => {
300//! let sub_cmd_name = sub_cmd.name();
301//! match sub_cmd.parse() {
302//! Ok(_) => { /* ... */ },
303//! Err(err) => panic!("Invalid option: {}", err.option()),
304//! }
305//! },
306//! Ok(None) => { /* ... */ },
307//! Err(InvalidOption::OptionContainsInvalidChar { option }) => {
308//! panic!("Option contains invalid character: {option}");
309//! },
310//! Err(err) => panic!("Invalid option: {}", err.option()),
311//! }
312//! ```
313
314/// Enums for errors that can occur when parsing command line arguments.
315pub mod errors;
316
317mod opt_cfg;
318pub use opt_cfg::OptCfg;
319pub use opt_cfg::OptCfgParam;
320
321mod help;
322pub use help::Help;
323pub use help::HelpIter;
324
325/// Function pointers for validating an option argument.
326pub use opt_cfg::validators;
327
328mod parse;
329pub use parse::make_opt_cfgs_for;
330pub use parse::OptStore;
331
332extern crate cliargs_derive;
333pub use cliargs_derive::OptStore;
334
335use std::collections::HashMap;
336use std::env;
337use std::ffi::OsString;
338use std::fmt;
339use std::mem;
340use std::path;
341
342/// Parses command line arguments and stores them.
343///
344/// The results of parsing are stored by separating into command name, command arguments, options,
345/// and option arguments.
346///
347/// These values are retrieved as string slices with the same lifetime as this `Cmd` instance.
348/// Therefore, if you want to use those values for a longer period, it is needed to convert them
349/// to [String]s.
350pub struct Cmd<'a> {
351 name: &'a str,
352 args: Vec<&'a str>,
353 opts: HashMap<&'a str, Vec<&'a str>>,
354 cfgs: Vec<OptCfg>,
355 is_after_end_opt: bool,
356
357 _leaked_strs: Vec<&'a str>,
358 _num_of_args: usize,
359}
360
361impl<'a> Drop for Cmd<'a> {
362 fn drop(&mut self) {
363 for str in &self._leaked_strs {
364 let boxed = unsafe { Box::from_raw(*str as *const str as *mut str) };
365 mem::drop(boxed);
366 }
367 }
368}
369
370impl fmt::Debug for Cmd<'_> {
371 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372 f.debug_struct("Cmd")
373 .field("name", &self.name)
374 .field("args", &self.args)
375 .field("opts", &self.opts)
376 .finish()
377 }
378}
379
380impl<'b, 'a> Cmd<'a> {
381 /// Creates a `Cmd` instance with command line arguments obtained from [std::env::args_os].
382 ///
383 /// Since [std::env::args_os] returns a vector of [OsString] and they can contain invalid
384 /// unicode data, the return value of this funciton is [Result] of `Cmd` or
385 /// `errors::InvalidOsArg`.
386 pub fn new() -> Result<Cmd<'a>, errors::InvalidOsArg> {
387 Self::with_os_strings(env::args_os())
388 }
389
390 /// Creates a `Cmd` instance with the specified iterator of [OsString]s.
391 ///
392 /// [OsString]s can contain invalid unicode data, the return value of this function
393 /// is [Result] of `Cmd` or `errors::InvalidOsArg`.
394 pub fn with_os_strings(
395 osargs: impl IntoIterator<Item = OsString>,
396 ) -> Result<Cmd<'a>, errors::InvalidOsArg> {
397 let osarg_iter = osargs.into_iter();
398 let (size, _) = osarg_iter.size_hint();
399 let mut _leaked_strs = Vec::with_capacity(size);
400
401 let cmd_name_start: usize;
402
403 let mut enm = osarg_iter.enumerate();
404 if let Some((idx, osarg)) = enm.next() {
405 // The first element is the command path.
406 let path = path::Path::new(&osarg);
407 let base_len = if let Some(base_os) = path.file_name() {
408 if let Some(base_str) = base_os.to_str() {
409 base_str.len()
410 } else {
411 0
412 }
413 } else {
414 0
415 };
416 match osarg.into_string() {
417 Ok(string) => {
418 let str: &'a str = string.leak();
419 _leaked_strs.push(str);
420 cmd_name_start = str.len() - base_len;
421 }
422 Err(osstring) => {
423 return Err(errors::InvalidOsArg::OsArgsContainInvalidUnicode {
424 index: idx,
425 os_arg: osstring,
426 });
427 }
428 }
429
430 // The elements from the second one onward are the arguments.
431 for (idx, osarg) in enm {
432 match osarg.into_string() {
433 Ok(string) => {
434 let str: &'a str = string.leak();
435 _leaked_strs.push(str);
436 }
437 Err(osstring) => {
438 for str in _leaked_strs {
439 let boxed = unsafe { Box::from_raw(str as *const str as *mut str) };
440 mem::drop(boxed);
441 }
442 return Err(errors::InvalidOsArg::OsArgsContainInvalidUnicode {
443 index: idx,
444 os_arg: osstring,
445 });
446 }
447 }
448 }
449 } else {
450 _leaked_strs.push("");
451 cmd_name_start = 0;
452 }
453
454 let _num_of_args = _leaked_strs.len();
455
456 Ok(Cmd {
457 name: &_leaked_strs[0][cmd_name_start..],
458 args: Vec::new(),
459 opts: HashMap::new(),
460 cfgs: Vec::new(),
461 is_after_end_opt: false,
462 _leaked_strs,
463 _num_of_args,
464 })
465 }
466
467 /// Creates a `Cmd` instance with the specified iterator of [String]s.
468 pub fn with_strings(args: impl IntoIterator<Item = String>) -> Cmd<'a> {
469 let arg_iter = args.into_iter();
470 let (size, _) = arg_iter.size_hint();
471 let mut _leaked_strs = Vec::with_capacity(size);
472
473 for arg in arg_iter {
474 let str: &'a str = arg.leak();
475 _leaked_strs.push(str);
476 }
477
478 let cmd_name_start: usize;
479
480 if _leaked_strs.len() > 0 {
481 let path = path::Path::new(_leaked_strs[0]);
482 let mut base_len = 0;
483 if let Some(base_os) = path.file_name() {
484 if let Some(base_str) = base_os.to_str() {
485 base_len = base_str.len();
486 }
487 }
488 cmd_name_start = _leaked_strs[0].len() - base_len;
489 } else {
490 _leaked_strs.push("");
491 cmd_name_start = 0;
492 };
493
494 let _num_of_args = _leaked_strs.len();
495
496 Cmd {
497 name: &_leaked_strs[0][cmd_name_start..],
498 args: Vec::new(),
499 opts: HashMap::new(),
500 cfgs: Vec::new(),
501 is_after_end_opt: false,
502 _leaked_strs,
503 _num_of_args,
504 }
505 }
506
507 fn sub_cmd(&'a self, from_index: usize, is_after_end_opt: bool) -> Cmd<'b> {
508 let arg_iter = self._leaked_strs[from_index..(self._num_of_args)].into_iter();
509 let (size, _) = arg_iter.size_hint();
510 let mut _leaked_strs = Vec::with_capacity(size);
511
512 for arg in arg_iter {
513 let str: &'b str = arg.to_string().leak();
514 _leaked_strs.push(str);
515 }
516
517 let _num_of_args = _leaked_strs.len();
518
519 Cmd {
520 name: &_leaked_strs[0],
521 args: Vec::new(),
522 opts: HashMap::new(),
523 cfgs: Vec::new(),
524 is_after_end_opt: is_after_end_opt,
525 _leaked_strs,
526 _num_of_args,
527 }
528 }
529
530 /// Returns the command name.
531 ///
532 /// This name is base name extracted from the command path string slice, which is the first
533 /// element of the command line arguments.
534 pub fn name(&'a self) -> &'a str {
535 self.name
536 }
537
538 /// Returns the command arguments.
539 ///
540 /// These arguments are retrieved as string slices in an array.
541 pub fn args(&'a self) -> &'a [&'a str] {
542 &self.args
543 }
544
545 /// Checks whether an option with the specified name exists.
546 pub fn has_opt(&self, name: &str) -> bool {
547 self.opts.contains_key(name)
548 }
549
550 /// Returns the option argument with the specified name.
551 ///
552 /// If the option has multiple arguments, this method returns the first argument.
553 /// If the option is a boolean flag, this method returns [None].
554 /// If the option is not specified in the command line arguments, the return value
555 /// of this method is [None].
556 pub fn opt_arg(&'a self, name: &str) -> Option<&'a str> {
557 if let Some(opt_vec) = self.opts.get(name) {
558 if opt_vec.len() > 0 {
559 return Some(opt_vec[0]);
560 }
561 }
562 None
563 }
564
565 /// Returns the option arguments with the specified name.
566 ///
567 /// If the option has one or multiple arguments, this method returns an array of the arguments.
568 /// If the option is a boolean flag, this method returns an empty vector.
569 /// If the option is not specified in the command line arguments, the return value
570 /// of this method is [None].
571 pub fn opt_args(&'a self, name: &str) -> Option<&'a [&'a str]> {
572 match self.opts.get(name) {
573 Some(vec) => Some(&vec),
574 None => None,
575 }
576 }
577
578 /// Retrieves the option configurations which was used to parse command line arguments.
579 pub fn opt_cfgs(&'a self) -> &[OptCfg] {
580 &self.cfgs
581 }
582}
583
584#[cfg(test)]
585mod tests_of_cmd {
586 use super::Cmd;
587
588 mod tests_of_new {
589 use super::Cmd;
590
591 #[test]
592 fn should_create_a_new_instance() {
593 let cmd = Cmd::new().unwrap();
594 println!("cmd = {cmd:?}");
595 println!("cmd._leaked_strs = {:?}", cmd._leaked_strs);
596 assert!(cmd.name().starts_with("cliargs-"));
597 assert!(cmd._leaked_strs.len() > 0);
598 }
599 }
600
601 mod tests_of_with_strings {
602 use super::Cmd;
603
604 #[test]
605 fn should_create_a_new_instance() {
606 let mut cmd = Cmd::with_strings([
607 "/path/to/app".to_string(),
608 "--foo".to_string(),
609 "bar".to_string(),
610 ]);
611
612 cmd.args.push(cmd._leaked_strs[2]);
613 cmd.opts
614 .insert(&cmd._leaked_strs[1][2..], Vec::with_capacity(0));
615
616 println!("cmd = {cmd:?}");
617 println!("cmd._leaked_strs = {:?}", cmd._leaked_strs);
618 assert_eq!(cmd.name(), "app");
619 }
620
621 #[test]
622 fn should_get_command_name_from_absolute_path() {
623 let cmd = Cmd::with_strings([
624 "/path/to/app".to_string(),
625 "--foo".to_string(),
626 "--bar".to_string(),
627 "baz".to_string(),
628 "--bar".to_string(),
629 "qux".to_string(),
630 "quux".to_string(),
631 "corge".to_string(),
632 ]);
633 assert_eq!(cmd.name(), "app");
634 }
635
636 #[test]
637 fn should_get_command_name_from_relative_path() {
638 let cmd = Cmd::with_strings([
639 "../path/to/app".to_string(),
640 "--foo".to_string(),
641 "--bar".to_string(),
642 "baz".to_string(),
643 "--bar".to_string(),
644 "qux".to_string(),
645 "quux".to_string(),
646 "corge".to_string(),
647 ]);
648 assert_eq!(cmd.name(), "app");
649 }
650
651 #[test]
652 fn should_get_command_name_from_base_name_only() {
653 let cmd = Cmd::with_strings([
654 "app".to_string(),
655 "--foo".to_string(),
656 "--bar".to_string(),
657 "baz".to_string(),
658 "--bar".to_string(),
659 "qux".to_string(),
660 "quux".to_string(),
661 "corge".to_string(),
662 ]);
663 assert_eq!(cmd.name(), "app");
664 }
665
666 #[test]
667 fn should_get_command_name_when_command_line_arguments_is_empty() {
668 let cmd = Cmd::with_strings([]);
669 assert_eq!(cmd.name(), "");
670 }
671 }
672
673 mod tests_of_with_os_strings {
674 use super::Cmd;
675 use std::ffi;
676
677 #[test]
678 fn should_create_a_new_instance() {
679 let cmd = Cmd::with_os_strings([
680 ffi::OsString::from("/path/to/app"),
681 ffi::OsString::from("--foo"),
682 ffi::OsString::from("bar_baz"),
683 ffi::OsString::from("qux"),
684 ])
685 .unwrap();
686
687 assert_eq!(cmd.name(), "app");
688 }
689
690 #[cfg(not(windows))] // Because OsStr is valid WTF8 and OsString is valid WTF16 on Windows
691 #[test]
692 fn should_fail_because_os_args_contain_invalid_unicode() {
693 let bad_arg = b"bar\xFFbaz";
694 let bad_os_str = unsafe { ffi::OsStr::from_encoded_bytes_unchecked(bad_arg) };
695 let bad_os_string = bad_os_str.to_os_string();
696
697 match Cmd::with_os_strings([
698 ffi::OsString::from("/path/to/app"),
699 ffi::OsString::from("--foo"),
700 bad_os_string.clone(),
701 ffi::OsString::from("qux"),
702 ]) {
703 Ok(_) => assert!(false),
704 Err(crate::errors::InvalidOsArg::OsArgsContainInvalidUnicode { index, os_arg }) => {
705 assert_eq!(index, 2);
706 assert_eq!(os_arg, bad_os_string);
707 }
708 }
709 }
710
711 #[cfg(not(windows))] // Because OsStr is valid WTF8 and OsString is valid WTF16 on Windows
712 #[test]
713 fn should_fail_because_command_name_contains_invalid_unicode() {
714 let bad_arg = b"bar\xFFbaz";
715 let bad_os_str = unsafe { ffi::OsStr::from_encoded_bytes_unchecked(bad_arg) };
716 let bad_os_string = bad_os_str.to_os_string();
717
718 match Cmd::with_os_strings([
719 bad_os_string.clone(),
720 ffi::OsString::from("--foo"),
721 ffi::OsString::from("qux"),
722 ]) {
723 Ok(_) => assert!(false),
724 Err(crate::errors::InvalidOsArg::OsArgsContainInvalidUnicode { index, os_arg }) => {
725 assert_eq!(index, 0);
726 assert_eq!(os_arg, bad_os_string);
727 }
728 }
729 }
730
731 #[test]
732 fn should_get_command_name_from_absolute_path() {
733 if let Ok(cmd) = Cmd::with_os_strings([
734 ffi::OsString::from("/path/to/app"),
735 ffi::OsString::from("--foo"),
736 ffi::OsString::from("--bar"),
737 ffi::OsString::from("baz"),
738 ffi::OsString::from("--bar"),
739 ffi::OsString::from("qux"),
740 ffi::OsString::from("quux"),
741 ffi::OsString::from("corge"),
742 ]) {
743 assert_eq!(cmd.name(), "app");
744 } else {
745 assert!(false);
746 }
747 }
748
749 #[test]
750 fn should_get_command_name_from_relative_path() {
751 if let Ok(cmd) = Cmd::with_os_strings([
752 ffi::OsString::from("../path/to/app"),
753 ffi::OsString::from("--foo"),
754 ffi::OsString::from("--bar"),
755 ffi::OsString::from("baz"),
756 ffi::OsString::from("--bar"),
757 ffi::OsString::from("qux"),
758 ffi::OsString::from("quux"),
759 ffi::OsString::from("corge"),
760 ]) {
761 assert_eq!(cmd.name(), "app");
762 } else {
763 assert!(false);
764 }
765 }
766
767 #[test]
768 fn should_get_command_name_from_base_name_only() {
769 if let Ok(cmd) = Cmd::with_os_strings([
770 ffi::OsString::from("app"),
771 ffi::OsString::from("--foo"),
772 ffi::OsString::from("--bar"),
773 ffi::OsString::from("baz"),
774 ffi::OsString::from("--bar"),
775 ffi::OsString::from("qux"),
776 ffi::OsString::from("quux"),
777 ffi::OsString::from("corge"),
778 ]) {
779 assert_eq!(cmd.name(), "app");
780 } else {
781 assert!(false);
782 }
783 }
784
785 #[test]
786 fn should_get_command_name_when_command_line_arguments_is_empty() {
787 if let Ok(cmd) = Cmd::with_os_strings([]) {
788 assert_eq!(cmd.name(), "");
789 } else {
790 assert!(false);
791 }
792 }
793 }
794
795 mod tests_of_getters {
796 use super::Cmd;
797
798 #[test]
799 fn should_get_command_name_when_command_line_arguments_is_empty() {
800 let cmd = Cmd::with_strings([]);
801
802 assert_eq!(cmd.name(), "");
803 }
804
805 #[test]
806 fn should_get_command_arguments() {
807 let mut cmd = Cmd::with_strings([
808 "/path/to/app".to_string(),
809 "--foo".to_string(),
810 "--bar".to_string(),
811 "baz".to_string(),
812 "--bar".to_string(),
813 "qux".to_string(),
814 "quux".to_string(),
815 "corge".to_string(),
816 ]);
817
818 cmd.args.push(cmd._leaked_strs[6]);
819 cmd.args.push(cmd._leaked_strs[7]);
820 cmd.opts
821 .insert(&cmd._leaked_strs[1][2..], Vec::with_capacity(0));
822 cmd.opts.insert(
823 &cmd._leaked_strs[2][2..],
824 vec![&cmd._leaked_strs[3], &cmd._leaked_strs[5]],
825 );
826
827 assert_eq!(cmd.args(), ["quux", "corge"]);
828 }
829
830 #[test]
831 fn should_check_option_is_specified() {
832 let mut cmd = Cmd::with_strings([
833 "/path/to/app".to_string(),
834 "--foo".to_string(),
835 "--bar".to_string(),
836 "baz".to_string(),
837 "--bar".to_string(),
838 "qux".to_string(),
839 "quux".to_string(),
840 "corge".to_string(),
841 ]);
842
843 cmd.args.push(cmd._leaked_strs[6]);
844 cmd.args.push(cmd._leaked_strs[7]);
845 cmd.opts
846 .insert(&cmd._leaked_strs[1][2..], Vec::with_capacity(0));
847 cmd.opts.insert(
848 &cmd._leaked_strs[2][2..],
849 vec![&cmd._leaked_strs[3], &cmd._leaked_strs[5]],
850 );
851
852 assert_eq!(cmd.has_opt("foo"), true);
853 assert_eq!(cmd.has_opt("bar"), true);
854 assert_eq!(cmd.has_opt("baz"), false);
855 }
856
857 #[test]
858 fn should_get_single_option_argument() {
859 let mut cmd = Cmd::with_strings([
860 "/path/to/app".to_string(),
861 "--foo".to_string(),
862 "--bar".to_string(),
863 "baz".to_string(),
864 "--bar".to_string(),
865 "qux".to_string(),
866 "quux".to_string(),
867 "corge".to_string(),
868 ]);
869
870 cmd.args.push(cmd._leaked_strs[6]);
871 cmd.args.push(cmd._leaked_strs[7]);
872 cmd.opts
873 .insert(&cmd._leaked_strs[1][2..], Vec::with_capacity(0));
874 cmd.opts.insert(
875 &cmd._leaked_strs[2][2..],
876 vec![&cmd._leaked_strs[3], &cmd._leaked_strs[5]],
877 );
878
879 assert_eq!(cmd.opt_arg("foo"), None);
880 assert_eq!(cmd.opt_arg("bar"), Some("baz"));
881 assert_eq!(cmd.opt_arg("baz"), None);
882 }
883
884 #[test]
885 fn should_get_multiple_option_arguments() {
886 let mut cmd = Cmd::with_strings([
887 "/path/to/app".to_string(),
888 "--foo".to_string(),
889 "--bar".to_string(),
890 "baz".to_string(),
891 "--bar".to_string(),
892 "qux".to_string(),
893 "quux".to_string(),
894 "corge".to_string(),
895 ]);
896
897 cmd.args.push(cmd._leaked_strs[6]);
898 cmd.args.push(cmd._leaked_strs[7]);
899 cmd.opts
900 .insert(&cmd._leaked_strs[1][2..], Vec::with_capacity(0));
901 cmd.opts.insert(
902 &cmd._leaked_strs[2][2..],
903 vec![&cmd._leaked_strs[3], &cmd._leaked_strs[5]],
904 );
905
906 assert_eq!(cmd.opt_args("foo"), Some(&[] as &[&str]));
907 assert_eq!(cmd.opt_args("bar"), Some(&["baz", "qux"] as &[&str]));
908 assert_eq!(cmd.opt_args("baz"), None);
909 }
910 }
911
912 mod tests_of_moving_cmd {
913 use crate::Cmd;
914 use crate::OptCfg;
915 use crate::OptCfgParam::*;
916
917 #[test]
918 fn should_move_by_passing_a_parameter() {
919 fn move_cmd(cmd: Cmd) {
920 assert_eq!(cmd.name(), "app");
921 assert_eq!(cmd.args(), &["baz", "qux", "quux", "corge"]);
922 assert_eq!(cmd.opt_args("foo").unwrap(), &Vec::<&str>::new());
923 assert_eq!(cmd.opt_args("bar").unwrap(), &["ABC", "DEF"]);
924 assert_eq!(
925 cmd._leaked_strs,
926 &[
927 "/path/to/app",
928 "--foo",
929 "--bar=ABC",
930 "baz",
931 "--bar=DEF",
932 "qux",
933 "quux",
934 "corge",
935 "foo",
936 "bar",
937 ]
938 );
939 assert_eq!(cmd.opt_cfgs().len(), 2);
940 assert_eq!(cmd.opt_cfgs()[0].store_key, "");
941 assert_eq!(cmd.opt_cfgs()[0].names, &["foo"]);
942 assert_eq!(cmd.opt_cfgs()[0].has_arg, false);
943 assert_eq!(cmd.opt_cfgs()[0].is_array, false);
944 assert_eq!(cmd.opt_cfgs()[0].defaults, None);
945 assert_eq!(cmd.opt_cfgs()[0].desc, "");
946 assert_eq!(cmd.opt_cfgs()[0].arg_in_help, "");
947 assert_eq!(cmd.opt_cfgs()[1].store_key, "");
948 assert_eq!(cmd.opt_cfgs()[1].names, &["bar"]);
949 assert_eq!(cmd.opt_cfgs()[1].has_arg, true);
950 assert_eq!(cmd.opt_cfgs()[1].is_array, true);
951 assert_eq!(cmd.opt_cfgs()[1].defaults, None);
952 assert_eq!(cmd.opt_cfgs()[1].desc, "");
953 assert_eq!(cmd.opt_cfgs()[1].arg_in_help, "");
954 }
955
956 let cfgs = vec![
957 OptCfg::with([names(&["foo"])]),
958 OptCfg::with([names(&["bar"]), has_arg(true), is_array(true)]),
959 ];
960
961 let mut cmd = Cmd::with_strings([
962 "/path/to/app".to_string(),
963 "--foo".to_string(),
964 "--bar=ABC".to_string(),
965 "baz".to_string(),
966 "--bar=DEF".to_string(),
967 "qux".to_string(),
968 "quux".to_string(),
969 "corge".to_string(),
970 ]);
971 let _ = cmd.parse_with(cfgs);
972
973 move_cmd(cmd);
974 }
975
976 #[test]
977 fn should_move_by_returning() {
978 fn move_cmd() -> Cmd<'static> {
979 let cfgs = vec![
980 OptCfg::with([names(&["foo"])]),
981 OptCfg::with([names(&["bar"]), has_arg(true), is_array(true)]),
982 ];
983
984 let mut cmd = Cmd::with_strings([
985 "/path/to/app".to_string(),
986 "--foo".to_string(),
987 "--bar=ABC".to_string(),
988 "baz".to_string(),
989 "--bar=DEF".to_string(),
990 "qux".to_string(),
991 "quux".to_string(),
992 "corge".to_string(),
993 ]);
994 let _ = cmd.parse_with(cfgs);
995 cmd
996 }
997
998 let cmd = move_cmd();
999 assert_eq!(cmd.name(), "app");
1000 assert_eq!(cmd.args(), &["baz", "qux", "quux", "corge"]);
1001 assert_eq!(cmd.opt_args("foo").unwrap(), &Vec::<&str>::new());
1002 assert_eq!(cmd.opt_args("bar").unwrap(), &["ABC", "DEF"]);
1003 assert_eq!(
1004 cmd._leaked_strs,
1005 &[
1006 "/path/to/app",
1007 "--foo",
1008 "--bar=ABC",
1009 "baz",
1010 "--bar=DEF",
1011 "qux",
1012 "quux",
1013 "corge",
1014 "foo",
1015 "bar",
1016 ]
1017 );
1018 assert_eq!(cmd.opt_cfgs().len(), 2);
1019 assert_eq!(cmd.opt_cfgs()[0].store_key, "");
1020 assert_eq!(cmd.opt_cfgs()[0].names, &["foo"]);
1021 assert_eq!(cmd.opt_cfgs()[0].has_arg, false);
1022 assert_eq!(cmd.opt_cfgs()[0].is_array, false);
1023 assert_eq!(cmd.opt_cfgs()[0].defaults, None);
1024 assert_eq!(cmd.opt_cfgs()[0].desc, "");
1025 assert_eq!(cmd.opt_cfgs()[0].arg_in_help, "");
1026 assert_eq!(cmd.opt_cfgs()[1].store_key, "");
1027 assert_eq!(cmd.opt_cfgs()[1].names, &["bar"]);
1028 assert_eq!(cmd.opt_cfgs()[1].has_arg, true);
1029 assert_eq!(cmd.opt_cfgs()[1].is_array, true);
1030 assert_eq!(cmd.opt_cfgs()[1].defaults, None);
1031 assert_eq!(cmd.opt_cfgs()[1].desc, "");
1032 assert_eq!(cmd.opt_cfgs()[1].arg_in_help, "");
1033 }
1034
1035 #[test]
1036 fn should_move_by_mem_replace() {
1037 fn move_cmd() -> Cmd<'static> {
1038 let cfgs = vec![
1039 OptCfg::with([names(&["foo"])]),
1040 OptCfg::with([names(&["bar"]), has_arg(true), is_array(true)]),
1041 ];
1042
1043 let mut cmd = Cmd::with_strings([
1044 "/path/to/app".to_string(),
1045 "--foo".to_string(),
1046 "--bar=ABC".to_string(),
1047 "baz".to_string(),
1048 "--bar=DEF".to_string(),
1049 "qux".to_string(),
1050 "quux".to_string(),
1051 "corge".to_string(),
1052 ]);
1053 let _ = cmd.parse_with(cfgs);
1054
1055 let mut cmd1 = Cmd::with_strings([]);
1056 let _ = std::mem::replace(&mut cmd1, cmd);
1057 cmd1
1058 }
1059
1060 let cmd = move_cmd();
1061 assert_eq!(cmd.name(), "app");
1062 assert_eq!(cmd.args(), &["baz", "qux", "quux", "corge"]);
1063 assert_eq!(cmd.opt_args("foo").unwrap(), &Vec::<&str>::new());
1064 assert_eq!(cmd.opt_args("bar").unwrap(), &["ABC", "DEF"]);
1065 assert_eq!(
1066 cmd._leaked_strs,
1067 &[
1068 "/path/to/app",
1069 "--foo",
1070 "--bar=ABC",
1071 "baz",
1072 "--bar=DEF",
1073 "qux",
1074 "quux",
1075 "corge",
1076 "foo",
1077 "bar",
1078 ]
1079 );
1080 assert_eq!(cmd.opt_cfgs().len(), 2);
1081 assert_eq!(cmd.opt_cfgs()[0].store_key, "");
1082 assert_eq!(cmd.opt_cfgs()[0].names, &["foo"]);
1083 assert_eq!(cmd.opt_cfgs()[0].has_arg, false);
1084 assert_eq!(cmd.opt_cfgs()[0].is_array, false);
1085 assert_eq!(cmd.opt_cfgs()[0].defaults, None);
1086 assert_eq!(cmd.opt_cfgs()[0].desc, "");
1087 assert_eq!(cmd.opt_cfgs()[0].arg_in_help, "");
1088 assert_eq!(cmd.opt_cfgs()[1].store_key, "");
1089 assert_eq!(cmd.opt_cfgs()[1].names, &["bar"]);
1090 assert_eq!(cmd.opt_cfgs()[1].has_arg, true);
1091 assert_eq!(cmd.opt_cfgs()[1].is_array, true);
1092 assert_eq!(cmd.opt_cfgs()[1].defaults, None);
1093 assert_eq!(cmd.opt_cfgs()[1].desc, "");
1094 assert_eq!(cmd.opt_cfgs()[1].arg_in_help, "");
1095 }
1096
1097 #[test]
1098 fn should_move_by_mem_swap() {
1099 fn move_cmd() -> Cmd<'static> {
1100 let cfgs = vec![
1101 OptCfg::with([names(&["foo"])]),
1102 OptCfg::with([names(&["bar"]), has_arg(true), is_array(true)]),
1103 ];
1104
1105 let mut cmd = Cmd::with_strings([
1106 "/path/to/app".to_string(),
1107 "--foo".to_string(),
1108 "--bar=ABC".to_string(),
1109 "baz".to_string(),
1110 "--bar=DEF".to_string(),
1111 "qux".to_string(),
1112 "quux".to_string(),
1113 "corge".to_string(),
1114 ]);
1115 let _ = cmd.parse_with(cfgs);
1116
1117 let mut cmd1 = Cmd::with_strings([]);
1118 let _ = std::mem::swap(&mut cmd1, &mut cmd);
1119 cmd1
1120 }
1121
1122 let cmd = move_cmd();
1123 assert_eq!(cmd.name(), "app");
1124 assert_eq!(cmd.args(), &["baz", "qux", "quux", "corge"]);
1125 assert_eq!(cmd.opt_args("foo").unwrap(), &Vec::<&str>::new());
1126 assert_eq!(cmd.opt_args("bar").unwrap(), &["ABC", "DEF"]);
1127 assert_eq!(
1128 cmd._leaked_strs,
1129 &[
1130 "/path/to/app",
1131 "--foo",
1132 "--bar=ABC",
1133 "baz",
1134 "--bar=DEF",
1135 "qux",
1136 "quux",
1137 "corge",
1138 "foo",
1139 "bar",
1140 ]
1141 );
1142 assert_eq!(cmd.opt_cfgs().len(), 2);
1143 assert_eq!(cmd.opt_cfgs()[0].store_key, "");
1144 assert_eq!(cmd.opt_cfgs()[0].names, &["foo"]);
1145 assert_eq!(cmd.opt_cfgs()[0].has_arg, false);
1146 assert_eq!(cmd.opt_cfgs()[0].is_array, false);
1147 assert_eq!(cmd.opt_cfgs()[0].defaults, None);
1148 assert_eq!(cmd.opt_cfgs()[0].desc, "");
1149 assert_eq!(cmd.opt_cfgs()[0].arg_in_help, "");
1150 assert_eq!(cmd.opt_cfgs()[1].store_key, "");
1151 assert_eq!(cmd.opt_cfgs()[1].names, &["bar"]);
1152 assert_eq!(cmd.opt_cfgs()[1].has_arg, true);
1153 assert_eq!(cmd.opt_cfgs()[1].is_array, true);
1154 assert_eq!(cmd.opt_cfgs()[1].defaults, None);
1155 assert_eq!(cmd.opt_cfgs()[1].desc, "");
1156 assert_eq!(cmd.opt_cfgs()[1].arg_in_help, "");
1157 }
1158 }
1159}