patchy/
flags.rs

1use core::fmt;
2use core::fmt::Display;
3use std::env;
4
5use colored::Colorize as _;
6use once_cell::sync::Lazy;
7
8use crate::{commands::help::format_description, types::CommandArgs};
9
10pub struct Flag<'a> {
11    pub short: &'a str,
12    pub long: &'a str,
13    pub description: &'a str,
14}
15
16/// Extracts value out of a `flag` which can have an assignment
17///
18/// # Examples
19///
20/// ```rust
21/// use patchy::flags::Flag;
22///
23/// let my_flag = Flag {
24///     short: "-r=",
25///     long: "--remote-name=",
26///     description: "some flag",
27/// };
28///
29/// let long_version = my_flag.extract_from_arg("--remote-name=abc");
30/// let short_version = my_flag.extract_from_arg("-r=abcdefg");
31/// let invalid = my_flag.extract_from_arg("-m=abcdefg");
32///
33/// assert_eq!(long_version, Some("abc".into()));
34/// assert_eq!(short_version, Some("abcdefg".into()));
35/// assert_eq!(invalid, None);
36/// ```
37impl Flag<'_> {
38    pub fn is_in(&self, args: &CommandArgs) -> bool {
39        args.contains(self.short) || args.contains(self.long)
40    }
41
42    pub fn extract_from_arg(&self, arg: &str) -> Option<String> {
43        if arg.starts_with(self.short) {
44            arg.get(self.short.len()..).map(Into::into)
45        } else if arg.starts_with(self.long) {
46            arg.get(self.long.len()..).map(Into::into)
47        } else {
48            None
49        }
50    }
51}
52
53impl Display for Flag<'_> {
54    /// Formats a flag into a colored format with a description, printable to the terminal
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        write!(
57            f,
58            "{}{}{}\n    {}",
59            self.short.bright_magenta(),
60            ", ".bright_black(),
61            self.long.bright_magenta(),
62            format_description(self.description)
63        )
64    }
65}
66
67/// Checks whether an input argument is a valid flag
68pub fn is_valid_flag(arg: &str, available_flags: &[&Flag]) -> bool {
69    // TODO: flags that don't end in "=" should be compared fully, not just the beginning
70    available_flags
71        .iter()
72        .flat_map(|flag| [flag.short, flag.long])
73        .any(|flag| arg.starts_with(flag))
74}
75
76/// Makes the program output more detailed information
77pub static IS_VERBOSE: Lazy<bool> = Lazy::new(|| {
78    let args: CommandArgs = env::args().collect();
79    args.contains("--verbose")
80});