xacli_core/command/
arg.rs

1//! Argument definitions and parsing
2
3use std::fmt::Debug;
4
5use crate::{Completer, Prompter, Validator};
6
7/// Kind of argument (positional, flag, or option)
8#[derive(Debug, Clone, PartialEq, Eq, Default)]
9pub enum ArgKind {
10    /// Positional argument (e.g., `myapp <file>`)
11    #[default]
12    Positional,
13    /// Boolean flag without value (e.g., `--verbose`, `-v`)
14    Flag {
15        short: Option<char>,
16        long: String,
17        aliases: Vec<String>,
18    },
19    /// Option with value (e.g., `--output file.txt`, `-o file.txt`)
20    Option {
21        short: Option<char>,
22        long: String,
23        aliases: Vec<String>,
24    },
25}
26
27/// Argument value types
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum ArgType {
30    String,
31    Int,
32    Float,
33    Bool,
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub struct ArgSchema {
38    pub ty: ArgType,
39    pub required: bool,
40    pub default_value: Option<String>,
41    pub multiple: bool,
42    pub enum_options: Option<Vec<(String, String)>>,
43}
44
45pub struct Arg {
46    pub info: ArgInfo,
47    pub validators: Vec<Box<dyn Validator>>,
48    pub completers: Vec<Box<dyn Completer>>,
49    pub prompters: Vec<Box<dyn Prompter>>,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53pub struct ArgInfo {
54    pub name: String,
55    pub title: String,
56    pub description: String,
57    pub kind: ArgKind,
58    pub schema: ArgSchema,
59    pub env: Option<String>,
60}
61
62impl Arg {
63    // ========================================================================
64    // Constructors
65    // ========================================================================
66
67    /// Create a boolean flag (no value)
68    pub fn flag(long: impl Into<String>) -> Self {
69        let long_str = long.into();
70        Self {
71            info: ArgInfo {
72                name: long_str.clone(),
73                title: String::new(),
74                description: String::new(),
75                kind: ArgKind::Flag {
76                    short: None,
77                    long: long_str,
78                    aliases: Vec::new(),
79                },
80                schema: ArgSchema {
81                    ty: ArgType::Bool,
82                    required: false,
83                    default_value: None,
84                    multiple: false,
85                    enum_options: None,
86                },
87                env: None,
88            },
89            validators: Vec::new(),
90            completers: Vec::new(),
91            prompters: Vec::new(),
92        }
93    }
94
95    /// Create an option with value
96    pub fn option(long: impl Into<String>, ty: ArgType) -> Self {
97        let long_str = long.into();
98        Self {
99            info: ArgInfo {
100                name: long_str.clone(),
101                title: String::new(),
102                description: String::new(),
103                kind: ArgKind::Option {
104                    short: None,
105                    long: long_str,
106                    aliases: Vec::new(),
107                },
108                schema: ArgSchema {
109                    ty,
110                    required: false,
111                    default_value: None,
112                    multiple: false,
113                    enum_options: None,
114                },
115                env: None,
116            },
117            validators: Vec::new(),
118            completers: Vec::new(),
119            prompters: Vec::new(),
120        }
121    }
122
123    /// Create a positional argument
124    pub fn positional(name: impl Into<String>, ty: ArgType) -> Self {
125        Self {
126            info: ArgInfo {
127                name: name.into(),
128                title: String::new(),
129                description: String::new(),
130                kind: ArgKind::Positional,
131                schema: ArgSchema {
132                    ty,
133                    required: false,
134                    default_value: None,
135                    multiple: false,
136                    enum_options: None,
137                },
138                env: None,
139            },
140            validators: Vec::new(),
141            completers: Vec::new(),
142            prompters: Vec::new(),
143        }
144    }
145
146    // ========================================================================
147    // Chainable configuration methods
148    // ========================================================================
149
150    /// Set the help/title for the argument
151    pub fn title(mut self, title: impl Into<String>) -> Self {
152        self.info.title = title.into();
153        self
154    }
155
156    /// Set the description for the argument
157    pub fn description(mut self, description: impl Into<String>) -> Self {
158        self.info.description = description.into();
159        self
160    }
161
162    /// Set the short flag (e.g., `-v`)
163    pub fn short(mut self, short: char) -> Self {
164        match &mut self.info.kind {
165            ArgKind::Flag { short: s, .. } | ArgKind::Option { short: s, .. } => {
166                *s = Some(short);
167            }
168            ArgKind::Positional => (),
169        }
170        self
171    }
172
173    pub fn aliases(mut self, aliases: Vec<String>) -> Self {
174        match &mut self.info.kind {
175            ArgKind::Flag { aliases: a, .. } | ArgKind::Option { aliases: a, .. } => {
176                *a = aliases;
177            }
178            ArgKind::Positional => (),
179        }
180        self
181    }
182
183    pub fn ty(mut self, ty: ArgType) -> Self {
184        self.info.schema.ty = ty;
185        self
186    }
187
188    /// Mark as required
189    pub fn required(mut self) -> Self {
190        self.info.schema.required = true;
191        self
192    }
193
194    /// Set the default value
195    pub fn default(mut self, value: impl Into<String>) -> Self {
196        self.info.schema.default_value = Some(value.into());
197        self
198    }
199
200    /// Set whether this argument accepts multiple values
201    pub fn multiple(mut self) -> Self {
202        self.info.schema.multiple = true;
203        self
204    }
205
206    /// Set environment variable to read default value from
207    pub fn env(mut self, env_var: impl Into<String>) -> Self {
208        self.info.env = Some(env_var.into());
209        self
210    }
211
212    /// Set enum options for this argument (label, value)
213    pub fn enums(mut self, options: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
214        self.info.schema.enum_options = Some(
215            options
216                .into_iter()
217                .map(|(label, value)| (label.into(), value.into()))
218                .collect(),
219        );
220        self
221    }
222
223    /// Set a validator (accepts closures or Validator trait implementations)
224    pub fn validator(mut self, v: Box<dyn Validator + 'static>) -> Self {
225        self.validators.push(v);
226        self
227    }
228
229    /// Set a completer (accepts closures or Completer trait implementations)
230    pub fn completer(mut self, c: Box<dyn Completer + 'static>) -> Self {
231        self.completers.push(c);
232        self
233    }
234
235    /// Set a prompter (accepts Prompter trait implementations)
236    pub fn prompter(mut self, p: Box<dyn Prompter + 'static>) -> Self {
237        self.prompters.push(p);
238        self
239    }
240}