Skip to main content

appcore_args/
spec.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: spec.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/08/19 12:52:57 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/08/19 13:34:54 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11use std::fmt;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct CliSpec {
15    name: String,
16    about: String,
17    version: Option<String>,
18    commands: Vec<CommandSpec>,
19    options: Vec<OptionSpec>,
20    arguments: Vec<ArgumentSpec>,
21    command_required: bool,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct CommandSpec {
26    name: String,
27    aliases: Vec<String>,
28    about: String,
29    commands: Vec<CommandSpec>,
30    options: Vec<OptionSpec>,
31    arguments: Vec<ArgumentSpec>,
32    command_required: bool,
33    hidden: bool,
34}
35
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct OptionSpec {
38    long: String,
39    short: Option<char>,
40    value: ValueMode,
41    value_name: String,
42    value_type: ValueType,
43    possible_values: Vec<String>,
44    about: String,
45    required: bool,
46    repeatable: bool,
47    detached_optional_value: bool,
48    terminal: bool,
49    hidden: bool,
50    conflicts_with: Vec<String>,
51    requires: Vec<String>,
52}
53
54#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct ArgumentSpec {
56    name: String,
57    about: String,
58    value_type: ValueType,
59    possible_values: Vec<String>,
60    required: bool,
61    multiple: bool,
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub enum ValueMode {
66    Forbidden,
67    Required,
68    Optional,
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub enum ValueType {
73    String,
74    Bool,
75    I64,
76    U64,
77}
78
79impl fmt::Display for ValueType {
80    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81        formatter.write_str(match self {
82            Self::String => "text",
83            Self::Bool => "true or false",
84            Self::I64 => "a signed integer",
85            Self::U64 => "an unsigned integer",
86        })
87    }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub struct SpecError {
92    message: String,
93}
94
95impl CliSpec {
96    pub fn new(name: impl Into<String>) -> Self {
97        Self {
98            name: name.into(),
99            about: String::new(),
100            version: None,
101            commands: Vec::new(),
102            options: Vec::new(),
103            arguments: Vec::new(),
104            command_required: false,
105        }
106    }
107    pub fn about(mut self, about: impl Into<String>) -> Self {
108        self.about = about.into();
109        self
110    }
111    pub fn version(mut self, version: impl Into<String>) -> Self {
112        self.version = Some(version.into());
113        self
114    }
115    pub fn command(mut self, command: CommandSpec) -> Self {
116        self.commands.push(command);
117        self
118    }
119    pub fn option(mut self, option: OptionSpec) -> Self {
120        self.options.push(option);
121        self
122    }
123    pub fn argument(mut self, argument: ArgumentSpec) -> Self {
124        self.arguments.push(argument);
125        self
126    }
127    pub fn command_required(mut self, required: bool) -> Self {
128        self.command_required = required;
129        self
130    }
131    pub fn validate(&self) -> Result<(), SpecError> {
132        crate::spec_validation::validate_spec(self)
133    }
134    pub fn name(&self) -> &str {
135        &self.name
136    }
137    pub fn about_text(&self) -> &str {
138        &self.about
139    }
140    pub fn version_text(&self) -> Option<&str> {
141        self.version.as_deref()
142    }
143    pub fn commands(&self) -> &[CommandSpec] {
144        &self.commands
145    }
146    pub fn options(&self) -> &[OptionSpec] {
147        &self.options
148    }
149    pub fn arguments(&self) -> &[ArgumentSpec] {
150        &self.arguments
151    }
152    pub fn is_command_required(&self) -> bool {
153        self.command_required
154    }
155}
156
157impl CommandSpec {
158    pub fn new(name: impl Into<String>) -> Self {
159        Self {
160            name: name.into(),
161            aliases: Vec::new(),
162            about: String::new(),
163            commands: Vec::new(),
164            options: Vec::new(),
165            arguments: Vec::new(),
166            command_required: false,
167            hidden: false,
168        }
169    }
170    pub fn alias(mut self, alias: impl Into<String>) -> Self {
171        self.aliases.push(alias.into());
172        self
173    }
174    pub fn about(mut self, about: impl Into<String>) -> Self {
175        self.about = about.into();
176        self
177    }
178    pub fn command(mut self, command: CommandSpec) -> Self {
179        self.commands.push(command);
180        self
181    }
182    pub fn option(mut self, option: OptionSpec) -> Self {
183        self.options.push(option);
184        self
185    }
186    pub fn argument(mut self, argument: ArgumentSpec) -> Self {
187        self.arguments.push(argument);
188        self
189    }
190    pub fn command_required(mut self, required: bool) -> Self {
191        self.command_required = required;
192        self
193    }
194    pub fn hidden(mut self, hidden: bool) -> Self {
195        self.hidden = hidden;
196        self
197    }
198    pub fn name(&self) -> &str {
199        &self.name
200    }
201    pub fn aliases(&self) -> &[String] {
202        &self.aliases
203    }
204    pub fn matches(&self, value: &str) -> bool {
205        self.name == value || self.aliases.iter().any(|alias| alias == value)
206    }
207    pub fn about_text(&self) -> &str {
208        &self.about
209    }
210    pub fn commands(&self) -> &[CommandSpec] {
211        &self.commands
212    }
213    pub fn options(&self) -> &[OptionSpec] {
214        &self.options
215    }
216    pub fn arguments(&self) -> &[ArgumentSpec] {
217        &self.arguments
218    }
219    pub fn is_command_required(&self) -> bool {
220        self.command_required
221    }
222    pub fn is_hidden(&self) -> bool {
223        self.hidden
224    }
225}
226
227impl OptionSpec {
228    pub fn flag(long: impl Into<String>) -> Self {
229        Self::new(long, ValueMode::Forbidden)
230    }
231    pub fn value(long: impl Into<String>) -> Self {
232        Self::new(long, ValueMode::Required)
233    }
234    fn new(long: impl Into<String>, value: ValueMode) -> Self {
235        Self {
236            long: long.into(),
237            short: None,
238            value,
239            value_name: "VALUE".into(),
240            value_type: ValueType::String,
241            possible_values: Vec::new(),
242            about: String::new(),
243            required: false,
244            repeatable: false,
245            detached_optional_value: false,
246            terminal: false,
247            hidden: false,
248            conflicts_with: Vec::new(),
249            requires: Vec::new(),
250        }
251    }
252    pub fn short(mut self, short: char) -> Self {
253        self.short = Some(short);
254        self
255    }
256    pub fn optional_value(mut self) -> Self {
257        self.value = ValueMode::Optional;
258        self
259    }
260    pub fn value_name(mut self, name: impl Into<String>) -> Self {
261        self.value_name = name.into();
262        self
263    }
264    pub fn value_type(mut self, value_type: ValueType) -> Self {
265        self.value_type = value_type;
266        self
267    }
268    pub fn possible_value(mut self, value: impl Into<String>) -> Self {
269        self.possible_values.push(value.into());
270        self
271    }
272    pub fn about(mut self, about: impl Into<String>) -> Self {
273        self.about = about.into();
274        self
275    }
276    pub fn required(mut self, required: bool) -> Self {
277        self.required = required;
278        self
279    }
280    pub fn repeatable(mut self, repeatable: bool) -> Self {
281        self.repeatable = repeatable;
282        self
283    }
284    /// Allows an optional value to be supplied as the following argument.
285    ///
286    /// Optional values remain attached-only by default because a detached text
287    /// value may otherwise consume a positional argument. Prefer a bounded
288    /// [`ValueType`] such as [`ValueType::Bool`] when enabling this behavior.
289    pub fn detached_optional_value(mut self, enabled: bool) -> Self {
290        self.detached_optional_value = enabled;
291        self
292    }
293    pub fn terminal(mut self, terminal: bool) -> Self {
294        self.terminal = terminal;
295        self
296    }
297    pub fn hidden(mut self, hidden: bool) -> Self {
298        self.hidden = hidden;
299        self
300    }
301    pub fn conflicts_with(mut self, long: impl Into<String>) -> Self {
302        self.conflicts_with.push(long.into());
303        self
304    }
305    pub fn requires(mut self, long: impl Into<String>) -> Self {
306        self.requires.push(long.into());
307        self
308    }
309    pub fn long(&self) -> &str {
310        &self.long
311    }
312    pub fn short_name(&self) -> Option<char> {
313        self.short
314    }
315    pub fn value_mode(&self) -> ValueMode {
316        self.value
317    }
318    pub fn value_name_text(&self) -> &str {
319        &self.value_name
320    }
321    pub fn value_type_kind(&self) -> ValueType {
322        self.value_type
323    }
324    pub fn possible_values(&self) -> &[String] {
325        &self.possible_values
326    }
327    pub fn about_text(&self) -> &str {
328        &self.about
329    }
330    pub fn is_required(&self) -> bool {
331        self.required
332    }
333    pub fn is_repeatable(&self) -> bool {
334        self.repeatable
335    }
336    pub fn accepts_detached_optional_value(&self) -> bool {
337        self.detached_optional_value
338    }
339    pub fn is_terminal(&self) -> bool {
340        self.terminal
341    }
342    pub fn is_hidden(&self) -> bool {
343        self.hidden
344    }
345    pub fn conflicts(&self) -> &[String] {
346        &self.conflicts_with
347    }
348    pub fn requirements(&self) -> &[String] {
349        &self.requires
350    }
351}
352
353impl ArgumentSpec {
354    pub fn new(name: impl Into<String>) -> Self {
355        Self {
356            name: name.into(),
357            about: String::new(),
358            value_type: ValueType::String,
359            possible_values: Vec::new(),
360            required: false,
361            multiple: false,
362        }
363    }
364    pub fn about(mut self, about: impl Into<String>) -> Self {
365        self.about = about.into();
366        self
367    }
368    pub fn value_type(mut self, value_type: ValueType) -> Self {
369        self.value_type = value_type;
370        self
371    }
372    pub fn possible_value(mut self, value: impl Into<String>) -> Self {
373        self.possible_values.push(value.into());
374        self
375    }
376    pub fn required(mut self, required: bool) -> Self {
377        self.required = required;
378        self
379    }
380    pub fn multiple(mut self, multiple: bool) -> Self {
381        self.multiple = multiple;
382        self
383    }
384    pub fn name(&self) -> &str {
385        &self.name
386    }
387    pub fn about_text(&self) -> &str {
388        &self.about
389    }
390    pub fn value_type_kind(&self) -> ValueType {
391        self.value_type
392    }
393    pub fn possible_values(&self) -> &[String] {
394        &self.possible_values
395    }
396    pub fn is_required(&self) -> bool {
397        self.required
398    }
399    pub fn is_multiple(&self) -> bool {
400        self.multiple
401    }
402}
403
404impl SpecError {
405    fn new(message: impl Into<String>) -> Self {
406        Self {
407            message: message.into(),
408        }
409    }
410    pub(crate) fn new_internal(message: impl Into<String>) -> Self {
411        Self::new(message)
412    }
413}
414impl fmt::Display for SpecError {
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416        f.write_str(&self.message)
417    }
418}
419impl std::error::Error for SpecError {}