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