cli_rs/
flag.rs

1use std::str::FromStr;
2
3use crate::{
4    cli_error::{CliError, CliResult},
5    input::{Completor, Input, InputType},
6};
7
8// todo existence
9// todo short flags with a space
10// todo short flag-sets
11pub struct Flag<'a, T: Default + FromStr + Clone> {
12    pub name: String,
13    pub description: Option<String>,
14    pub value: Option<T>,
15    pub bool_flag: bool,
16    pub completor: Option<Completor<'a>>,
17}
18
19impl<'a> Flag<'a, bool> {
20    pub fn bool(name: &str) -> Self {
21        Self {
22            name: name.to_string(),
23            value: None,
24            bool_flag: true,
25            completor: None,
26            description: None,
27        }
28    }
29}
30
31impl<'a, T: FromStr + Default + Clone> Flag<'a, T> {
32    pub fn new(name: &str) -> Self {
33        Self {
34            name: name.to_string(),
35            value: None,
36            bool_flag: false,
37            completor: None,
38            description: None,
39        }
40    }
41
42    pub fn get(&self) -> T {
43        self.value.clone().unwrap_or_default()
44    }
45
46    pub fn description(mut self, description: &str) -> Self {
47        self.description = Some(description.to_string());
48        self
49    }
50
51    pub fn completor<F>(mut self, completor: F) -> Self
52    where
53        F: FnMut(&str) -> CliResult<Vec<String>> + 'a,
54    {
55        self.completor = Some(Box::new(completor));
56        self
57    }
58}
59
60impl<'a, T: FromStr + Default + Clone> Input for Flag<'a, T> {
61    // for short flags with a space
62    // should probably return a Result<bool, ParseError>
63    fn parse(&mut self, token: &str) -> CliResult<bool> {
64        // just handle the short flag first
65        if token.len() == 2 && self.bool_flag {
66            if &token[0..1] != "-" {
67                // todo should this be an error?
68                return Ok(false);
69            }
70
71            if token[1..2].to_uppercase() == self.name[0..1].to_uppercase() {
72                self.value = Some("true".parse().unwrap_or_else(|_| unreachable!()));
73                return Ok(true);
74            }
75        }
76
77        // make it safe to index anywhere
78        let min_size = self.name.len() + 2; // --
79        if token.len() < min_size {
80            return Ok(false);
81        }
82
83        // name check
84        let flag_length = self.name.len();
85        if token[2..flag_length + 2] != self.name {
86            return Ok(false);
87        }
88
89        // extract value
90        if let Some(eq_idx) = token.find('=') {
91            let value = &token[eq_idx + 1..].to_string();
92
93            self.value = Some(value.parse().map_err(|_| {
94                CliError::from(format!("{} cannot be parsed for {}", value, self.name))
95            })?);
96
97            return Ok(true);
98        }
99
100        if self.bool_flag {
101            self.value = Some("true".parse().unwrap_or_else(|_| unreachable!()));
102            return Ok(true);
103        }
104
105        Ok(false)
106    }
107
108    fn display_name(&self) -> String {
109        self.name.clone()
110    }
111
112    fn type_name(&self) -> InputType {
113        InputType::Flag
114    }
115
116    fn parsed(&self) -> bool {
117        self.value.is_some()
118    }
119
120    fn complete(&mut self, value: &str) -> CliResult<Vec<String>> {
121        if let Some(completor) = &mut self.completor {
122            completor(value)
123        } else {
124            Ok(vec![])
125        }
126    }
127
128    fn is_bool_flag(&self) -> bool {
129        self.bool_flag
130    }
131
132    fn description(&self) -> Option<String> {
133        self.description.clone()
134    }
135
136    fn has_default(&self) -> bool {
137        true
138    }
139}