Skip to main content

usage_argv/
run.rs

1//! Dispatch: handing a parsed command to the code that carries it out.
2//!
3//! A parse ends with a value — an enum whose selected variant holds the command's own
4//! struct — and every CLI then writes the same thing: a `match` over that enum, one arm per
5//! command, each arm calling the one function that command exists to call. At mise's size
6//! that match is 210 arms of pure routing, and the compiler cannot tell that an arm calling
7//! the wrong function is wrong, because every arm has the same shape.
8//!
9//! So the derive writes it. A command implements [`Run`] (or [`RunWith`], when the CLI hands
10//! its commands shared state), the enum says `#[usage(run)]`, and the match is generated from
11//! the same declaration the parser and the spec come from. Nothing about it reaches the
12//! spec: which Rust function carries out a command is not part of what the CLI *is*, and a
13//! spec that recorded it could not be read by anything that is not this program. It is the
14//! same rule `#[usage(skip)]` follows.
15//!
16//! Every one of these traits takes `self` by value. A command is finished when it has run, and
17//! the values it parsed are its own — taking them by reference would mean every handler
18//! borrowing what nothing else can want.
19//!
20//! # Which one
21//!
22//! |                | no context | a context            |
23//! | -------------- | ---------- | -------------------- |
24//! | **sync**       | [`Run`]    | [`RunWith`]          |
25//! | **async**      | [`RunAsync`] | [`RunAsyncWith`]   |
26//!
27//! The context is whatever the CLI has to give — a resolved config, an output handle, a
28//! database connection — and the `With` traits are generic over it, so `RunWith<&mut App>` and
29//! `RunAsyncWith<Arc<Ctx>>` are ordinary implementations rather than shapes this crate has to
30//! anticipate.
31//!
32//! A context is a separate trait rather than one defaulted to `()` because the noise otherwise
33//! falls on the wrong side of a CLI: a hundred commands that need no context would each carry
34//! `fn run(self, _: ())`, which says nothing and cannot be left out. One type may implement
35//! several of these, and one enum may dispatch several, which is what a CLI part-way through
36//! adopting a context — or an async runtime — needs.
37//!
38//! # Async commands
39//!
40//! [`RunAsync`] and [`RunAsyncWith`] are the async pair: an implementation writes `async fn`,
41//! and the generated dispatch is an `async fn` that awaits the selected command.
42//!
43//! ```
44//! use usage_argv::RunAsync;
45//!
46//! struct Install {
47//!     force: bool,
48//! }
49//!
50//! impl RunAsync for Install {
51//!     type Output = Result<(), String>;
52//!     async fn run_async(self) -> Self::Output {
53//!         // .await here
54//!         Ok(())
55//!     }
56//! }
57//! ```
58//!
59//! The trait declares `-> impl Future<Output = Self::Output>` rather than `async fn`, which is
60//! the same thing on the implementing side and **deliberately imposes no `Send` bound**: a CLI
61//! on a single-threaded runtime keeps futures that hold an `Rc` across an await, and one that
62//! spawns gets `Send` by inference, since it leaks out of the concrete commands the dispatch
63//! reaches. What this cannot do is *demand* `Send` in generic code, which is the trade the
64//! alternative — `-> impl Future + Send` in the trait — makes in the other direction, and
65//! there is no way to have both without duplicating the trait.
66//!
67//! The sync pair can carry a future too, since [`Output`](Run::Output) is whatever the command
68//! produces: a boxed `Pin<Box<dyn Future<Output = T>>>` (plus `+ Send` if the CLI wants it) is
69//! a value like any other. That costs an allocation and names a type; the async traits exist so
70//! that neither is necessary.
71//!
72//! # An example
73//!
74//! ```
75//! use usage_argv::Run;
76//!
77//! struct Install {
78//!     force: bool,
79//! }
80//! struct Sponsors;
81//!
82//! impl Run for Install {
83//!     type Output = Result<(), String>;
84//!     fn run(self) -> Self::Output {
85//!         if self.force {
86//!             Ok(())
87//!         } else {
88//!             Err("refusing without --force".into())
89//!         }
90//!     }
91//! }
92//!
93//! impl Run for Sponsors {
94//!     type Output = Result<(), String>;
95//!     fn run(self) -> Self::Output {
96//!         println!("thanks");
97//!         Ok(())
98//!     }
99//! }
100//!
101//! // What `#[usage(run)]` on the subcommand enum generates, written out.
102//! enum Command {
103//!     Install(Install),
104//!     Sponsors(Sponsors),
105//! }
106//!
107//! impl Run for Command
108//! where
109//!     Install: Run,
110//!     Sponsors: Run<Output = <Install as Run>::Output>,
111//! {
112//!     type Output = <Install as Run>::Output;
113//!     fn run(self) -> Self::Output {
114//!         match self {
115//!             Command::Install(inner) => Run::run(inner),
116//!             Command::Sponsors(inner) => Run::run(inner),
117//!         }
118//!     }
119//! }
120//!
121//! assert!(Command::Install(Install { force: true }).run().is_ok());
122//! ```
123
124/// A command that can be carried out with nothing but what it parsed.
125///
126/// The output is the implementation's own: `Result<(), E>` for a CLI whose commands can
127/// fail, `()` for one whose commands cannot, [`ExitCode`](std::process::ExitCode) for one
128/// that decides its own status. A generated dispatcher takes its output from the first
129/// command it routes to and requires the rest to agree, since a `match` has one type.
130pub trait Run {
131    /// What running the command produces.
132    type Output;
133
134    /// Carry out the command.
135    fn run(self) -> Self::Output;
136}
137
138/// A command that is handed something shared when it runs.
139///
140/// `Ctx` is whatever the CLI has to give: `&Config`, `&mut App`, an owned handle. It is a
141/// parameter of the trait rather than of the method so that one command may be runnable with
142/// several — a leaf that needs only a config can implement `RunWith<&Config>` while its
143/// siblings implement `RunWith<&mut App>`, as long as the enum dispatching them agrees on
144/// one.
145pub trait RunWith<Ctx> {
146    /// What running the command produces.
147    type Output;
148
149    /// Carry out the command, with `ctx`.
150    fn run_with(self, ctx: Ctx) -> Self::Output;
151}
152
153/// An async command: [`Run`], awaited.
154///
155/// The signature is `-> impl Future` rather than `async fn` so that no `Send` bound is implied
156/// either way — an implementation still writes `async fn run_async(self)`, and whether its
157/// future is `Send` is decided by what the command does rather than by this trait. See the
158/// [module docs](self#async-commands).
159pub trait RunAsync {
160    /// What running the command produces, once awaited.
161    type Output;
162
163    /// Carry out the command.
164    fn run_async(self) -> impl core::future::Future<Output = Self::Output>;
165}
166
167/// An async command that is handed something shared when it runs: [`RunWith`], awaited.
168///
169/// A borrowed context is the ordinary case, and the future borrows it for as long as it runs:
170/// `impl<'a> RunAsyncWith<&'a App> for Install`.
171pub trait RunAsyncWith<Ctx> {
172    /// What running the command produces, once awaited.
173    type Output;
174
175    /// Carry out the command, with `ctx`.
176    fn run_async_with(self, ctx: Ctx) -> impl core::future::Future<Output = Self::Output>;
177}