larpa/desc.rs
1//! Introspectable command description.
2//!
3//! This module contains [`CommandDesc`], which is automatically generated for every
4//! [`Command`][crate::Command] and allows generating `--help` output, a command usage string,
5//! man pages, shell completions, etc. by simply inspecting its contents.
6
7use std::{
8 ffi::{OsStr, OsString},
9 fmt,
10 path::{Path, PathBuf},
11};
12
13use crate::private::{ArgumentDescImpl, ArgumentNameImpl, CommandDescImpl, CommandDescInner};
14
15/// Introspectable command description.
16#[derive(Debug, Clone)]
17pub struct CommandDesc(pub(crate) CommandDescImpl);
18
19impl CommandDesc {
20 fn inner(&self) -> &'static CommandDescInner {
21 self.0.0
22 }
23
24 /// Returns the canonical name of the command.
25 ///
26 /// This is derived from the name of the crate that defines the command.
27 /// The *invoked name* of a command is `argv[0]`, which is typically the name of the executable,
28 /// and may differ from the canonical name.
29 #[inline]
30 pub fn canonical_name(&self) -> &str {
31 self.inner().canonical_name
32 }
33
34 #[inline]
35 pub fn description(&self) -> Option<&str> {
36 self.inner().description
37 }
38
39 /// Returns the version string of the command.
40 ///
41 /// This defaults to the Cargo package version (via `CARGO_PKG_VERSION`), but can be customized
42 /// via [`#[larpa(version = "custom-version")]`][version].
43 ///
44 /// Returns [`None`] when no version was specified manually and `CARGO_PKG_VERSION` was not set
45 /// (likely because the package wasn't built by Cargo).
46 ///
47 /// [version]: crate::attrs::top_level::version
48 #[inline]
49 pub fn version(&self) -> Option<&str> {
50 self.inner().version
51 }
52
53 #[inline]
54 pub fn authors(&self) -> Option<&str> {
55 self.inner().authors
56 }
57
58 /// Returns the program license identifier.
59 ///
60 /// By default, this is taken from the package's `Cargo.toml`, but it can be overridden with
61 /// [`#[larpa(license)]`][license].
62 ///
63 /// [license]: crate::attrs::top_level::license
64 #[inline]
65 pub fn license(&self) -> Option<&str> {
66 self.inner().license
67 }
68
69 #[inline]
70 pub fn homepage(&self) -> Option<&str> {
71 self.inner().homepage
72 }
73
74 #[inline]
75 pub fn repository(&self) -> Option<&str> {
76 self.inner().repository
77 }
78
79 #[inline]
80 pub fn args(&self) -> &[ArgumentDesc] {
81 self.inner().args
82 }
83
84 #[inline]
85 pub fn is_subcommand_optional(&self) -> bool {
86 self.inner().subcommand_optional
87 }
88
89 /// Returns `true` if there may be invokable subcommands beyond the ones in
90 /// [`CommandDesc::subcommands`].
91 ///
92 /// This is `true` when the command is an `enum` with a variant annotated with
93 /// [`#[larpa(fallback)]`][fallback].
94 ///
95 /// [fallback]: crate::attrs::variant::fallback
96 #[inline]
97 pub fn has_subcommand_fallback(&self) -> bool {
98 self.inner()
99 .subcommands
100 .as_ref()
101 .is_some_and(|d| d.has_fallback)
102 }
103
104 /// Invokes the dynamic subcommand discovery function.
105 ///
106 /// For `enum`s with a [`#[larpa(fallback)]`][fallback] variant, a dynamic subcommand discovery
107 /// function may be provided by the user that finds additional applicable subcommands at
108 /// runtime.
109 ///
110 /// If [`#[larpa(discover)]`][discover] was not used, this will return an empty [`Vec`].
111 ///
112 /// [fallback]: crate::attrs::variant::fallback
113 /// [discover]: crate::attrs::variant::discover
114 pub fn discover_subcommands(&self) -> Vec<DiscoveredSubcommand> {
115 match self
116 .inner()
117 .subcommands
118 .as_ref()
119 .and_then(|d| d.discover_subcommands)
120 {
121 Some(desc) => desc(self),
122 None => Vec::new(),
123 }
124 }
125
126 /// Returns the list of built-in subcommands, if this command has any.
127 #[inline]
128 pub fn subcommands(&self) -> &[SubcommandDesc] {
129 match self.inner().subcommands.as_ref() {
130 Some(desc) => desc.subcommands,
131 None => &[],
132 }
133 }
134}
135
136/// Description of a built-in subcommand.
137///
138/// Returned by [`CommandDesc::subcommands`].
139#[derive(Debug)]
140pub struct SubcommandDesc(pub(crate) CommandDesc);
141
142impl SubcommandDesc {
143 #[inline]
144 pub fn canonical_name(&self) -> &str {
145 self.0.canonical_name()
146 }
147
148 #[inline]
149 pub fn description(&self) -> Option<&str> {
150 self.0.description()
151 }
152
153 #[inline]
154 pub fn args(&self) -> &[ArgumentDesc] {
155 self.0.args()
156 }
157
158 #[inline]
159 pub fn subcommands(&self) -> &[SubcommandDesc] {
160 self.0.subcommands()
161 }
162}
163
164/// A dynamically discovered subcommand (for example, from searching `$PATH`).
165///
166/// This type can be returned by a user-defined external subcommand discovery function.
167/// For more information on how to set this up, please refer to the documentation for the
168/// [`#[larpa(discover)]`][discover] attribute.
169///
170/// [discover]: crate::attrs::variant::discover
171#[derive(Debug)]
172pub struct DiscoveredSubcommand {
173 name: OsString,
174 description: Option<String>,
175 path: Option<PathBuf>,
176}
177
178impl DiscoveredSubcommand {
179 /// Creates a [`DiscoveredSubcommand`] with the given subcommand name.
180 ///
181 /// It is assumed that `$cmd $name` (where `$cmd` is the canonical name of the command,
182 /// and `$name` is the `name` argument of this function) will invoke this subcommand.
183 pub fn new(name: impl Into<OsString>) -> Self {
184 Self {
185 name: name.into(),
186 description: None,
187 path: None,
188 }
189 }
190
191 /// Sets the description of this subcommand.
192 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
193 self.description = Some(desc.into());
194 self
195 }
196
197 /// Sets the path to the external subcommand.
198 pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
199 self.path = Some(path.into());
200 self
201 }
202
203 #[inline]
204 pub fn name(&self) -> &OsStr {
205 &self.name
206 }
207
208 #[inline]
209 pub fn description(&self) -> Option<&str> {
210 self.description.as_deref()
211 }
212
213 #[inline]
214 pub fn path(&self) -> Option<&Path> {
215 self.path.as_deref()
216 }
217}
218
219/// Description of a command-line argument.
220///
221/// Returned by [`CommandDesc::args`] and [`SubcommandDesc::args`].
222#[derive(Debug)]
223pub struct ArgumentDesc(pub(crate) ArgumentDescImpl);
224
225impl ArgumentDesc {
226 /// Returns whether the argument is positional.
227 ///
228 /// Positional arguments have neither a short nor a long flag associated with them, and are
229 /// matched purely by their position in the command line.
230 #[inline]
231 pub fn is_positional(&self) -> bool {
232 self.short().is_none() && self.long().is_none()
233 }
234
235 /// Returns whether the argument is optional.
236 #[inline]
237 pub fn is_optional(&self) -> bool {
238 self.0.optional
239 }
240
241 /// Returns whether the argument is required.
242 ///
243 /// This is the inverse of [`is_optional`][Self::is_optional].
244 ///
245 /// If a required argument is not provided, parsing will fail.
246 #[inline]
247 pub fn is_required(&self) -> bool {
248 !self.is_optional()
249 }
250
251 /// Returns whether the argument can be provided multiple times.
252 #[inline]
253 pub fn is_repeating(&self) -> bool {
254 self.0.repeating
255 }
256
257 /// Returns whether this argument takes a value.
258 ///
259 /// If `false`, the argument is a flag that only cares about whether (or how many times) it is
260 /// present.
261 #[inline]
262 pub fn takes_value(&self) -> bool {
263 self.value_name().is_some()
264 }
265
266 #[inline]
267 pub fn value_name(&self) -> Option<&str> {
268 self.0.name.0.value_name
269 }
270
271 /// Returns the description of this argument.
272 #[inline]
273 pub fn description(&self) -> Option<&str> {
274 self.0.description
275 }
276
277 /// Returns the custom default value (if any).
278 ///
279 /// Note that this will only return [`Some`] when using
280 /// [`#[larpa(default = "custom value")]`][dfl].
281 /// Using [`#[larpa(default)]`][dfl] alone will use the field type's [`Default`] implementation
282 /// and this method will return [`None`].
283 ///
284 /// [dfl]: crate::attrs::field::default
285 #[inline]
286 pub fn custom_default(&self) -> Option<&str> {
287 self.0.custom_default
288 }
289
290 /// Returns the argument's short form (eg. `-s`), without the leading `-`.
291 #[inline]
292 pub fn short(&self) -> Option<char> {
293 self.0.name.0.short
294 }
295
296 /// Returns the argument's long form (eg. `--long`), without the leading `--`.
297 #[inline]
298 pub fn long(&self) -> Option<&str> {
299 self.0.name.0.long
300 }
301
302 pub fn name(&self) -> &ArgumentName {
303 &self.0.name
304 }
305}
306
307/// A [`Display`][fmt::Display]able name of a command-line argument.
308#[derive(Debug)]
309pub struct ArgumentName(pub(crate) ArgumentNameImpl);
310
311impl fmt::Display for ArgumentName {
312 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313 match (self.0.short, self.0.long) {
314 (Some(short), None) => write!(f, "-{short}"),
315 (None, Some(long)) => write!(f, "--{long}"),
316 (Some(short), Some(long)) => write!(f, "-{short}/--{long}"),
317 (None, None) => f.write_str(self.0.value_name.unwrap()),
318 }
319 }
320}