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. 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}