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. It builds that
42//! command's future on the heap and awaits the box, so an unoptimized build does not keep room
43//! for every command's future on the stack while running one of them.
44//!
45//! ```
46//! use usage_argv::RunAsync;
47//!
48//! struct Install {
49//! force: bool,
50//! }
51//!
52//! impl RunAsync for Install {
53//! type Output = Result<(), String>;
54//! async fn run_async(self) -> Self::Output {
55//! // .await here
56//! Ok(())
57//! }
58//! }
59//! ```
60//!
61//! The trait declares `-> impl Future<Output = Self::Output>` rather than `async fn`, which is
62//! the same thing on the implementing side and **deliberately imposes no `Send` bound**: a CLI
63//! on a single-threaded runtime keeps futures that hold an `Rc` across an await, and one that
64//! spawns gets `Send` by inference, since it leaks out of the concrete commands the dispatch
65//! reaches. What this cannot do is *demand* `Send` in generic code, which is the trade the
66//! alternative — `-> impl Future + Send` in the trait — makes in the other direction, and
67//! there is no way to have both without duplicating the trait.
68//!
69//! The sync pair can carry a future too, since [`Output`](Run::Output) is whatever the command
70//! produces: a boxed `Pin<Box<dyn Future<Output = T>>>` (plus `+ Send` if the CLI wants it) is
71//! a value like any other. That names a type; the async traits exist so that it is not
72//! necessary.
73//!
74//! # An example
75//!
76//! ```
77//! use usage_argv::Run;
78//!
79//! struct Install {
80//! force: bool,
81//! }
82//! struct Sponsors;
83//!
84//! impl Run for Install {
85//! type Output = Result<(), String>;
86//! fn run(self) -> Self::Output {
87//! if self.force {
88//! Ok(())
89//! } else {
90//! Err("refusing without --force".into())
91//! }
92//! }
93//! }
94//!
95//! impl Run for Sponsors {
96//! type Output = Result<(), String>;
97//! fn run(self) -> Self::Output {
98//! println!("thanks");
99//! Ok(())
100//! }
101//! }
102//!
103//! // What `#[usage(run)]` on the subcommand enum generates, written out.
104//! enum Command {
105//! Install(Install),
106//! Sponsors(Sponsors),
107//! }
108//!
109//! impl Run for Command
110//! where
111//! Install: Run,
112//! Sponsors: Run<Output = <Install as Run>::Output>,
113//! {
114//! type Output = <Install as Run>::Output;
115//! fn run(self) -> Self::Output {
116//! match self {
117//! Command::Install(inner) => Run::run(inner),
118//! Command::Sponsors(inner) => Run::run(inner),
119//! }
120//! }
121//! }
122//!
123//! assert!(Command::Install(Install { force: true }).run().is_ok());
124//! ```
125
126/// A command that can be carried out with nothing but what it parsed.
127///
128/// The output is the implementation's own: `Result<(), E>` for a CLI whose commands can
129/// fail, `()` for one whose commands cannot, [`ExitCode`](std::process::ExitCode) for one
130/// that decides its own status. A generated dispatcher takes its output from the first
131/// command it routes to and requires the rest to agree, since a `match` has one type.
132pub trait Run {
133 /// What running the command produces.
134 type Output;
135
136 /// Carry out the command.
137 fn run(self) -> Self::Output;
138}
139
140/// A command that is handed something shared when it runs.
141///
142/// `Ctx` is whatever the CLI has to give: `&Config`, `&mut App`, an owned handle. It is a
143/// parameter of the trait rather than of the method so that one command may be runnable with
144/// several — a leaf that needs only a config can implement `RunWith<&Config>` while its
145/// siblings implement `RunWith<&mut App>`, as long as the enum dispatching them agrees on
146/// one.
147pub trait RunWith<Ctx> {
148 /// What running the command produces.
149 type Output;
150
151 /// Carry out the command, with `ctx`.
152 fn run_with(self, ctx: Ctx) -> Self::Output;
153}
154
155/// An async command: [`Run`], awaited.
156///
157/// The signature is `-> impl Future` rather than `async fn` so that no `Send` bound is implied
158/// either way — an implementation still writes `async fn run_async(self)`, and whether its
159/// future is `Send` is decided by what the command does rather than by this trait. See the
160/// [module docs](self#async-commands).
161pub trait RunAsync {
162 /// What running the command produces, once awaited.
163 type Output;
164
165 /// Carry out the command.
166 fn run_async(self) -> impl core::future::Future<Output = Self::Output>;
167}
168
169/// An async command that is handed something shared when it runs: [`RunWith`], awaited.
170///
171/// A borrowed context is the ordinary case, and the future borrows it for as long as it runs:
172/// `impl<'a> RunAsyncWith<&'a App> for Install`.
173pub trait RunAsyncWith<Ctx> {
174 /// What running the command produces, once awaited.
175 type Output;
176
177 /// Carry out the command, with `ctx`.
178 fn run_async_with(self, ctx: Ctx) -> impl core::future::Future<Output = Self::Output>;
179}