Skip to main content

bestool/
actions.rs

1use clap::Subcommand;
2use miette::Result;
3use tracing::{debug, trace};
4
5pub use context::Context;
6pub mod context;
7
8/// Wire up a subcommand level.
9///
10/// Generates an `Action` enum (one variant per subcommand), and a `run(args,
11/// ctx)` dispatcher. The `prep` closure runs before dispatch: it returns the
12/// chosen `Action` along with a `Context` that has been populated with this
13/// level's args (and anything else the level wants to provide). Each variant
14/// then dispatches to `<modname>::run(sub_args, ctx)`.
15///
16/// Every level uses the same `(args, ctx)` signature, so contexts compose
17/// across arbitrary nesting depth and downstream handlers can extract any
18/// ancestor's args by type via `ctx.require::<T>()`.
19#[macro_export]
20macro_rules! subcommands {
21	(
22		[$argtype:ty => $prep:expr]
23		$(
24			$(#[cfg($cfg:meta)])*
25			$(#[clap($clap:meta)])*
26			$modname:ident => $enumname:ident($argname:ident)
27		),+
28	) => {
29		$(
30			$(#[cfg($cfg)])*
31			pub mod $modname;
32		)*
33
34		#[derive(Debug, Clone, Subcommand)]
35		pub enum Action {
36			$(
37				$(#[cfg($cfg)])*
38				$(#[clap($clap)])*
39				$enumname($modname::$argname),
40			)*
41		}
42
43		pub async fn run(args: $argtype, ctx: Context) -> Result<()> {
44			let prep = $prep;
45			let (action, ctx) = prep(args, ctx)?;
46			match action {
47				$(
48					$(#[cfg($cfg)])*
49					Action::$enumname(args) => $modname::run(args, ctx).await,
50				)*
51			}
52		}
53	};
54}
55#[allow(unused_imports)]
56pub use subcommands;
57
58use crate::args::Args;
59
60subcommands! {
61	[Args => |args: Args, mut ctx: Context| -> Result<(Action, Context)> {
62		debug!(version=%env!("CARGO_PKG_VERSION"), "starting up");
63		trace!(action=?args.action, "action");
64		let action = args.action.clone();
65		ctx.provide(args);
66		Ok((action, ctx))
67	}]
68
69	#[cfg(feature = "tamanu-psql")]
70	audit_psql => AuditPsql(AuditPsqlArgs),
71	#[cfg(feature = "caddy")]
72	caddy => Caddy(CaddyArgs),
73	#[cfg(feature = "__canopy")]
74	canopy => Canopy(CanopyArgs),
75	#[cfg(feature = "completions")]
76	completions => Completions(CompletionsArgs),
77	#[cfg(feature = "crypto")]
78	crypto => Crypto(CryptoArgs),
79	#[clap(hide = true)]
80	#[clap(name = "_docs")]
81	docs => Docs(DocsArgs),
82	#[cfg(feature = "file")]
83	file => File(FileArgs),
84	#[cfg(feature = "__iti")]
85	iti => Iti(ItiArgs),
86	#[cfg(feature = "kopia")]
87	#[clap(alias = "k")]
88	kopia => Kopia(KopiaArgs),
89	#[cfg(feature = "rdp")]
90	rdp => Rdp(RdpArgs),
91	#[cfg(feature = "self-update")]
92	#[clap(alias = "self")]
93	self_update => SelfUpdate(SelfUpdateArgs),
94	#[cfg(feature = "ssh")]
95	ssh => Ssh(SshArgs),
96	#[cfg(feature = "__tamanu")]
97	#[clap(alias = "t")]
98	tamanu => Tamanu(TamanuArgs)
99}
100
101pub async fn run_with_update_check(args: Args) -> Result<()> {
102	let action = args.action.clone();
103
104	#[cfg(all(feature = "download", feature = "self-update"))]
105	if !matches!(action, Action::SelfUpdate(_))
106		&& !crate::actions::self_update::is_package_manager_install()
107		&& let Err(err) = crate::download::check_for_update().await
108	{
109		debug!("Failed to check for updates: {}", err);
110	}
111
112	run(args, Context::new()).await
113}