Skip to main content

rust_args_parser/
spec.rs

1use std::ffi::{OsStr, OsString};
2
3/// Color mode for help rendering.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum ColorMode {
6    Auto,
7    Always,
8    Never,
9}
10
11/// Global environment for a parse/render session.
12#[derive(Clone, Copy, Debug)]
13pub struct Env {
14    /// Wrap columns for help. `0` means no wrapping.
15    pub wrap_cols: usize,
16    /// Whether to colorize help (honors `NO_COLOR` when `color` feature is enabled).
17    pub color: ColorMode,
18    /// Whether to compute suggestions on errors (if enabled).
19    pub suggest: bool,
20    /// Built-ins
21    pub auto_help: bool,
22    pub version: Option<&'static str>,
23    pub author: Option<&'static str>,
24}
25impl Default for Env {
26    fn default() -> Self {
27        Self { wrap_cols: 0, color: ColorMode::Auto, suggest: true, auto_help: true, version: None, author: None }
28    }
29}
30
31/// Whether an option may be repeated.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum Repeat {
34    Single,
35    Many,
36}
37
38/// Group rule (applies to a set of options sharing the same group name).
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum GroupMode {
41    Xor,
42    ReqOne,
43}
44
45/// Provenance of a value in `Matches`.
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum Source {
48    Cli,
49    Env,
50    Default,
51}
52
53/// User-pluggable validator for a single value (OsStr-based, cross-platform).
54///
55/// The validator is expected to return a human-readable error message on failure.
56/// If you want to propagate a typed error, use `validator_try(...)`.
57pub type ValueValidatorFn<'a> = dyn Fn(&OsStr) -> crate::Result<()> + 'a;
58
59/// Command-level validator that can inspect the final `Matches`.
60pub type CmdValidatorFn<'a> = dyn Fn(&crate::Matches) -> crate::Result<()> + 'a;
61
62/// Command handler (executed for the **leaf** command after callbacks).
63pub type CmdHandlerFn<'a, Ctx> = dyn Fn(&crate::Matches, &mut Ctx) -> crate::Result<()> + 'a;
64
65/// Callback to apply a value/flag into user context.
66pub type OnValueFn<'a, Ctx> = dyn Fn(&OsStr, &mut Ctx) -> crate::Result<()> + 'a;
67pub type OnFlagFn<'a, Ctx> = dyn Fn(&mut Ctx) -> crate::Result<()> + 'a;
68
69/// Option (flag or value-bearing).
70pub struct OptSpec<'a, Ctx: ?Sized> {
71    name: &'a str,
72    short: Option<char>,
73    long: Option<&'a str>,
74    metavar: Option<&'a str>,
75    help: Option<&'a str>,
76    env: Option<&'a str>,
77    default: Option<OsString>,
78    group: Option<&'a str>,
79    repeat: Repeat,
80    takes_value: bool,
81    on_value: Option<Box<OnValueFn<'a, Ctx>>>, // value setter
82    on_flag: Option<Box<OnFlagFn<'a, Ctx>>>,   // flag setter
83    validator: Option<Box<ValueValidatorFn<'a>>>,
84}
85
86impl<'a, Ctx: ?Sized> OptSpec<'a, Ctx> {
87    /// Create a **flag** option. Other fields are set via builder methods.
88    pub fn flag<F>(name: &'a str, cb: F) -> Self
89    where
90        F: Fn(&mut Ctx) + 'a,
91    {
92        Self {
93            name,
94            short: None,
95            long: None,
96            metavar: None,
97            help: None,
98            env: None,
99            default: None,
100            group: None,
101            repeat: Repeat::Single,
102            takes_value: false,
103            on_value: None,
104            on_flag: Some(Box::new(move |ctx| {
105                cb(ctx);
106                Ok(())
107            })),
108            validator: None,
109        }
110    }
111
112    /// Create a **flag** option with a fallible callback.
113    pub fn flag_try<F, E>(name: &'a str, cb: F) -> Self
114    where
115        F: Fn(&mut Ctx) -> core::result::Result<(), E> + 'a,
116        E: std::error::Error + Send + Sync + 'static,
117    {
118        Self {
119            name,
120            short: None,
121            long: None,
122            metavar: None,
123            help: None,
124            env: None,
125            default: None,
126            group: None,
127            repeat: Repeat::Single,
128            takes_value: false,
129            on_value: None,
130            on_flag: Some(Box::new(move |ctx| cb(ctx).map_err(crate::Error::user))),
131            validator: None,
132        }
133    }
134
135    /// Create a **value** option. Other fields are set via builder methods.
136    pub fn value<F>(name: &'a str, cb: F) -> Self
137    where
138        F: Fn(&OsStr, &mut Ctx) + 'a,
139    {
140        Self {
141            name,
142            short: None,
143            long: None,
144            metavar: None,
145            help: None,
146            env: None,
147            default: None,
148            group: None,
149            repeat: Repeat::Single,
150            takes_value: true,
151            on_value: Some(Box::new(move |v, ctx| {
152                cb(v, ctx);
153                Ok(())
154            })),
155            on_flag: None,
156            validator: None,
157        }
158    }
159
160    /// Create a **value** option with a fallible callback.
161    pub fn value_try<F, E>(name: &'a str, cb: F) -> Self
162    where
163        F: Fn(&OsStr, &mut Ctx) -> core::result::Result<(), E> + 'a,
164        E: std::error::Error + Send + Sync + 'static,
165    {
166        Self {
167            name,
168            short: None,
169            long: None,
170            metavar: None,
171            help: None,
172            env: None,
173            default: None,
174            group: None,
175            repeat: Repeat::Single,
176            takes_value: true,
177            on_value: Some(Box::new(move |v, ctx| cb(v, ctx).map_err(crate::Error::user))),
178            on_flag: None,
179            validator: None,
180        }
181    }
182
183    // --- builders ---
184    #[must_use]
185    pub fn short(mut self, s: char) -> Self {
186        self.short = Some(s);
187        self
188    }
189    #[must_use]
190    pub fn long(mut self, l: &'a str) -> Self {
191        self.long = Some(l);
192        self
193    }
194    #[must_use]
195    pub fn metavar(mut self, mv: &'a str) -> Self {
196        self.metavar = Some(mv);
197        self
198    }
199    #[must_use]
200    pub fn help(mut self, h: &'a str) -> Self {
201        self.help = Some(h);
202        self
203    }
204    #[must_use]
205    pub fn env(mut self, name: &'a str) -> Self {
206        self.env = Some(name);
207        self
208    }
209    #[must_use]
210    pub fn default(mut self, val: impl Into<OsString>) -> Self {
211        self.default = Some(val.into());
212        self
213    }
214    #[must_use]
215    pub fn group(mut self, g: &'a str) -> Self {
216        self.group = Some(g);
217        self
218    }
219    #[must_use]
220    pub fn single(mut self) -> Self {
221        self.repeat = Repeat::Single;
222        self
223    }
224    #[must_use]
225    pub fn repeatable(mut self) -> Self {
226        self.repeat = Repeat::Many;
227        self
228    }
229
230    /// Value validator that returns a displayable error (converted into `Error::User`).
231    #[must_use]
232    pub fn validator<F, E>(mut self, v: F) -> Self
233    where
234        F: Fn(&OsStr) -> core::result::Result<(), E> + 'a,
235        E: core::fmt::Display,
236    {
237        self.validator = Some(Box::new(move |s| v(s).map_err(|e| crate::Error::User(e.to_string()))));
238        self
239    }
240
241    /// Value validator that returns a typed error (boxed into `Error::UserAny`).
242    #[must_use]
243    pub fn validator_try<F, E>(mut self, v: F) -> Self
244    where
245        F: Fn(&OsStr) -> core::result::Result<(), E> + 'a,
246        E: std::error::Error + Send + Sync + 'static,
247    {
248        self.validator = Some(Box::new(move |s| v(s).map_err(crate::Error::user)));
249        self
250    }
251
252    // --- getters (get_*; booleans use is_*) ---
253    #[must_use]
254    pub fn get_name(&self) -> &str {
255        self.name
256    }
257    #[must_use]
258    pub fn get_short(&self) -> Option<char> {
259        self.short
260    }
261    #[must_use]
262    pub fn get_long(&self) -> Option<&str> {
263        self.long
264    }
265    #[must_use]
266    pub fn get_metavar(&self) -> Option<&str> {
267        self.metavar
268    }
269    #[must_use]
270    pub fn get_help(&self) -> Option<&str> {
271        self.help
272    }
273    #[must_use]
274    pub fn get_env(&self) -> Option<&str> {
275        self.env
276    }
277    #[must_use]
278    pub fn get_default(&self) -> Option<&OsString> {
279        self.default.as_ref()
280    }
281    #[must_use]
282    pub fn get_group(&self) -> Option<&str> {
283        self.group
284    }
285    #[must_use]
286    pub fn is_value(&self) -> bool {
287        self.takes_value
288    }
289    #[must_use]
290    pub fn get_repeat(&self) -> Repeat {
291        self.repeat
292    }
293    #[must_use]
294    pub fn get_on_value(&self) -> Option<&OnValueFn<'a, Ctx>> {
295        self.on_value.as_deref()
296    }
297    #[must_use]
298    pub fn get_on_flag(&self) -> Option<&OnFlagFn<'a, Ctx>> {
299        self.on_flag.as_deref()
300    }
301    #[must_use]
302    pub fn get_validator(&self) -> Option<&ValueValidatorFn<'a>> {
303        self.validator.as_deref()
304    }
305}
306
307/// Positional cardinality.
308#[derive(Clone, Copy, Debug, PartialEq, Eq)]
309pub enum PosCardinality {
310    One { required: bool },
311    Many,
312    Range { min: usize, max: usize },
313}
314
315/// Positional argument specification.
316pub struct PosSpec<'a, Ctx: ?Sized> {
317    name: &'a str,
318    help: Option<&'a str>,
319    card: PosCardinality,
320    on_value: Box<OnValueFn<'a, Ctx>>,
321    validator: Option<Box<ValueValidatorFn<'a>>>,
322}
323impl<'a, Ctx: ?Sized> PosSpec<'a, Ctx> {
324    pub fn new<F>(name: &'a str, cb: F) -> Self
325    where
326        F: Fn(&OsStr, &mut Ctx) + 'a,
327    {
328        Self {
329            name,
330            help: None,
331            card: PosCardinality::One { required: false },
332            on_value: Box::new(move |v, ctx| {
333                cb(v, ctx);
334                Ok(())
335            }),
336            validator: None,
337        }
338    }
339
340    pub fn new_try<F, E>(name: &'a str, cb: F) -> Self
341    where
342        F: Fn(&OsStr, &mut Ctx) -> core::result::Result<(), E> + 'a,
343        E: std::error::Error + Send + Sync + 'static,
344    {
345        Self {
346            name,
347            help: None,
348            card: PosCardinality::One { required: false },
349            on_value: Box::new(move |v, ctx| cb(v, ctx).map_err(crate::Error::user)),
350            validator: None,
351        }
352    }
353
354    // builders
355    #[must_use]
356    pub fn help(mut self, h: &'a str) -> Self {
357        self.help = Some(h);
358        self
359    }
360    #[must_use]
361    pub fn required(mut self) -> Self {
362        self.card = PosCardinality::One { required: true };
363        self
364    }
365    #[must_use]
366    pub fn many(mut self) -> Self {
367        self.card = PosCardinality::Many;
368        self
369    }
370    #[must_use]
371    pub fn range(mut self, min: usize, max: usize) -> Self {
372        self.card = PosCardinality::Range { min, max };
373        self
374    }
375
376    /// Positional validator that returns a displayable error (converted into `Error::User`).
377    #[must_use]
378    pub fn validator<F, E>(mut self, v: F) -> Self
379    where
380        F: Fn(&OsStr) -> core::result::Result<(), E> + 'a,
381        E: core::fmt::Display,
382    {
383        self.validator = Some(Box::new(move |s| v(s).map_err(|e| crate::Error::User(e.to_string()))));
384        self
385    }
386
387    /// Positional validator that returns a typed error (boxed into `Error::UserAny`).
388    #[must_use]
389    pub fn validator_try<F, E>(mut self, v: F) -> Self
390    where
391        F: Fn(&OsStr) -> core::result::Result<(), E> + 'a,
392        E: std::error::Error + Send + Sync + 'static,
393    {
394        self.validator = Some(Box::new(move |s| v(s).map_err(crate::Error::user)));
395        self
396    }
397
398    // getters
399    #[must_use]
400    pub fn get_name(&self) -> &str {
401        self.name
402    }
403    #[must_use]
404    pub fn get_help(&self) -> Option<&str> {
405        self.help
406    }
407    #[must_use]
408    pub fn get_cardinality(&self) -> PosCardinality {
409        self.card
410    }
411    #[must_use]
412    pub fn is_required(&self) -> bool {
413        matches!(self.card, PosCardinality::One { required: true })
414            || matches!(self.card, PosCardinality::Range { min, .. } if min > 0)
415    }
416    #[must_use]
417    pub fn is_multiple(&self) -> bool {
418        !matches!(self.card, PosCardinality::One { .. })
419    }
420    pub fn get_on_value(&self) -> &OnValueFn<'a, Ctx> {
421        &*self.on_value
422    }
423    #[must_use]
424    pub fn get_validator(&self) -> Option<&ValueValidatorFn<'a>> {
425        self.validator.as_deref()
426    }
427}
428
429/// Group declaration.
430pub struct GroupDecl<'a> {
431    pub name: &'a str,
432    pub mode: GroupMode,
433}
434
435/// Command specification.
436pub struct CmdSpec<'a, Ctx: ?Sized> {
437    name: &'a str,
438    help: Option<&'a str>,
439    aliases: Vec<&'a str>,
440    opts: Vec<OptSpec<'a, Ctx>>,
441    positionals: Vec<PosSpec<'a, Ctx>>,
442    subcommands: Vec<CmdSpec<'a, Ctx>>,
443    groups: Vec<GroupDecl<'a>>,
444    validate_cmd: Option<Box<CmdValidatorFn<'a>>>,
445    handler: Option<Box<CmdHandlerFn<'a, Ctx>>>, // leaf command handler
446}
447impl<'a, Ctx: ?Sized> CmdSpec<'a, Ctx> {
448    #[must_use]
449    pub fn new(name: &'a str) -> Self {
450        Self {
451            name,
452            help: None,
453            aliases: Vec::new(),
454            opts: Vec::new(),
455            positionals: Vec::new(),
456            subcommands: Vec::new(),
457            groups: Vec::new(),
458            validate_cmd: None,
459            handler: None,
460        }
461    }
462    // builders
463    #[must_use]
464    pub fn help(mut self, s: &'a str) -> Self {
465        self.help = Some(s);
466        self
467    }
468    #[must_use]
469    pub fn alias(mut self, a: &'a str) -> Self {
470        self.aliases.push(a);
471        self
472    }
473    #[must_use]
474    pub fn opt(mut self, o: OptSpec<'a, Ctx>) -> Self {
475        self.opts.push(o);
476        self
477    }
478    #[must_use]
479    pub fn pos(mut self, p: PosSpec<'a, Ctx>) -> Self {
480        self.positionals.push(p);
481        self
482    }
483    #[must_use]
484    pub fn subcmd(mut self, c: Self) -> Self {
485        self.subcommands.push(c);
486        self
487    }
488    #[must_use]
489    pub fn group(mut self, name: &'a str, mode: GroupMode) -> Self {
490        self.groups.push(GroupDecl { name, mode });
491        self
492    }
493
494    /// Set per-command validator returning a displayable error (converted into `Error::User`).
495    #[must_use]
496    pub fn validator<F, E>(mut self, cb: F) -> Self
497    where
498        F: Fn(&crate::Matches) -> core::result::Result<(), E> + 'a,
499        E: core::fmt::Display,
500    {
501        self.validate_cmd = Some(Box::new(move |m| cb(m).map_err(|e| crate::Error::User(e.to_string()))));
502        self
503    }
504
505    /// Set per-command validator returning a typed error (boxed into `Error::UserAny`).
506    #[must_use]
507    pub fn validator_try<F, E>(mut self, cb: F) -> Self
508    where
509        F: Fn(&crate::Matches) -> core::result::Result<(), E> + 'a,
510        E: std::error::Error + Send + Sync + 'static,
511    {
512        self.validate_cmd = Some(Box::new(move |m| cb(m).map_err(crate::Error::user)));
513        self
514    }
515
516    /// Set a leaf command handler. Only the **selected leaf** handler is executed.
517    #[must_use]
518    pub fn handler<F>(mut self, cb: F) -> Self
519    where
520        F: Fn(&crate::Matches, &mut Ctx) + 'a,
521    {
522        self.handler = Some(Box::new(move |m, ctx| {
523            cb(m, ctx);
524            Ok(())
525        }));
526        self
527    }
528
529    /// Set a leaf command handler with a typed error (boxed into `Error::UserAny`).
530    #[must_use]
531    pub fn handler_try<F, E>(mut self, cb: F) -> Self
532    where
533        F: Fn(&crate::Matches, &mut Ctx) -> core::result::Result<(), E> + 'a,
534        E: std::error::Error + Send + Sync + 'static,
535    {
536        self.handler = Some(Box::new(move |m, ctx| cb(m, ctx).map_err(crate::Error::user)));
537        self
538    }
539
540    // getters
541    #[must_use]
542    pub fn get_name(&self) -> &str {
543        self.name
544    }
545    #[must_use]
546    pub fn get_help(&self) -> Option<&str> {
547        self.help
548    }
549    #[must_use]
550    pub fn get_aliases(&self) -> &[&'a str] {
551        &self.aliases
552    }
553    #[must_use]
554    pub fn get_opts(&self) -> &[OptSpec<'a, Ctx>] {
555        &self.opts
556    }
557    #[must_use]
558    pub fn get_positionals(&self) -> &[PosSpec<'a, Ctx>] {
559        &self.positionals
560    }
561    #[must_use]
562    pub fn get_subcommands(&self) -> &[Self] {
563        &self.subcommands
564    }
565    #[must_use]
566    pub fn get_groups(&self) -> &[GroupDecl<'a>] {
567        &self.groups
568    }
569    #[must_use]
570    pub fn get_validator(&self) -> Option<&CmdValidatorFn<'a>> {
571        self.validate_cmd.as_deref()
572    }
573    #[must_use]
574    pub fn get_handler(&self) -> Option<&CmdHandlerFn<'a, Ctx>> {
575        self.handler.as_deref()
576    }
577    #[must_use]
578    pub fn find_sub(&self, needle: &str) -> Option<&Self> {
579        self.subcommands.iter().find(|c| c.name == needle || c.aliases.iter().any(|a| *a == needle))
580    }
581}