Skip to main content

argot_cmd/model/
mod.rs

1//! Data model for argot commands.
2//!
3//! Every item in the argot command tree is represented by a [`Command`]. Related
4//! types — [`Argument`], [`Flag`], [`Example`] — attach metadata that drives
5//! both parsing and documentation generation.
6//!
7//! ## Builder Pattern
8//!
9//! All model types are constructed through consuming builders:
10//!
11//! ```
12//! # use argot_cmd::model::{Command, Argument, Flag, Example};
13//! let cmd = Command::builder("deploy")
14//!     .summary("Deploy the application")
15//!     .argument(
16//!         Argument::builder("env")
17//!             .description("Target environment")
18//!             .required()
19//!             .build()
20//!             .unwrap(),
21//!     )
22//!     .flag(
23//!         Flag::builder("dry-run")
24//!             .short('n')
25//!             .description("Simulate without making changes")
26//!             .build()
27//!             .unwrap(),
28//!     )
29//!     .build()
30//!     .unwrap();
31//!
32//! assert_eq!(cmd.canonical, "deploy");
33//! ```
34//!
35//! ## Handler Functions and Parsed Commands
36//!
37//! A [`HandlerFn`] is an `Arc`-wrapped closure that receives a [`ParsedCommand`]
38//! reference and returns `Result<(), Box<dyn Error>>`. The `Arc` wrapper means
39//! cloning a [`Command`] only bumps a reference count — no deep copy of the
40//! closure occurs.
41//!
42//! [`ParsedCommand`] is the output of a successful parse: it borrows the matched
43//! [`Command`] from the registry and owns the resolved argument and flag maps.
44
45/// Positional argument definition and builder.
46pub mod argument;
47/// Command definition, builder, handler type, and parsed command output.
48pub mod command;
49/// Usage example type for commands.
50pub mod example;
51/// Named flag definition and builder.
52pub mod flag;
53
54pub use argument::{Argument, ArgumentBuilder};
55#[cfg(feature = "async")]
56pub use command::AsyncHandlerFn;
57pub use command::{Command, CommandBuilder, HandlerFn, ParsedCommand};
58pub use example::Example;
59pub use flag::{Flag, FlagBuilder};
60
61use thiserror::Error;
62
63/// Error returned by builder `build()` methods.
64///
65/// Variants are returned from [`CommandBuilder::build`], [`ArgumentBuilder::build`],
66/// and [`FlagBuilder::build`] when validation fails. The list of variants includes
67/// checks for empty names, duplicate aliases, duplicate flags, duplicate arguments,
68/// duplicate subcommands, and variadic argument ordering.
69///
70/// # Examples
71///
72/// ```
73/// # use argot_cmd::model::{Command, BuildError};
74/// assert_eq!(Command::builder("").build().unwrap_err(), BuildError::EmptyCanonical);
75/// ```
76#[derive(Debug, Error, PartialEq)]
77pub enum BuildError {
78    /// The canonical name (or argument/flag name) was empty or whitespace.
79    #[error("canonical name must not be empty")]
80    EmptyCanonical,
81
82    /// Two aliases on the same command share the same string.
83    #[error("duplicate alias `{0}`")]
84    DuplicateAlias(String),
85
86    /// An alias is identical to the command's canonical name.
87    #[error("alias `{0}` duplicates the canonical name")]
88    AliasEqualsCanonical(String),
89
90    /// Two flags on the same command share the same long name.
91    #[error("duplicate flag name `{0}`")]
92    DuplicateFlagName(String),
93
94    /// Two flags on the same command share the same short character.
95    #[error("duplicate short flag `-{0}`")]
96    DuplicateShortFlag(char),
97
98    /// Two positional arguments on the same command share the same name.
99    #[error("duplicate argument name `{0}`")]
100    DuplicateArgumentName(String),
101
102    /// Two subcommands at the same level share the same canonical name.
103    #[error("duplicate subcommand `{0}`")]
104    DuplicateSubcommandName(String),
105
106    /// A variadic argument is not the last argument defined.
107    #[error("variadic argument `{0}` must be the last argument")]
108    VariadicNotLast(String),
109
110    /// A flag's `choices` list is empty, which would reject all values.
111    #[error("flag `{0}` has an empty choices list")]
112    EmptyChoices(String),
113
114    /// A mutual-exclusivity group contains fewer than two flag names.
115    #[error("exclusive group must contain at least two flags")]
116    ExclusiveGroupTooSmall,
117
118    /// A flag referenced in a mutual-exclusivity group is not defined on the command.
119    #[error("flag `{0}` in exclusive group is not defined on this command")]
120    ExclusiveGroupUnknownFlag(String),
121}