Skip to main content

canic_cli/
lib.rs

1mod backup;
2mod build;
3mod cli;
4mod cycles;
5mod endpoints;
6mod fleets;
7mod install;
8mod list;
9mod manifest;
10mod medic;
11mod metrics;
12mod output;
13mod replica;
14mod restore;
15mod scaffold;
16mod snapshot;
17mod status;
18mod support;
19#[cfg(test)]
20mod test_support;
21
22use crate::cli::{
23    clap::parse_matches,
24    globals::{
25        DISPATCH_ARGS, apply_global_icp, apply_global_network, command_local_global_option,
26        top_level_dispatch_command,
27    },
28    help::{first_arg_is_help, usage},
29};
30pub use cli::top_level_command;
31use std::ffi::OsString;
32use thiserror::Error as ThisError;
33
34const VERSION_TEXT: &str = concat!("canic ", env!("CARGO_PKG_VERSION"));
35
36///
37/// CliError
38///
39
40#[derive(Debug, ThisError)]
41pub enum CliError {
42    #[error("{0}")]
43    Usage(String),
44
45    #[error("backup: {0}")]
46    Backup(#[from] backup::BackupCommandError),
47
48    #[error("build: {0}")]
49    Build(#[from] build::BuildCommandError),
50
51    #[error("config: {0}")]
52    Config(String),
53
54    #[error("cycles: {0}")]
55    Cycles(#[from] cycles::CyclesCommandError),
56
57    #[error("endpoints: {0}")]
58    Endpoints(#[from] endpoints::EndpointsCommandError),
59
60    #[error("install: {0}")]
61    Install(#[from] install::InstallCommandError),
62
63    #[error("fleet: {0}")]
64    Fleets(#[from] fleets::FleetCommandError),
65
66    #[error("list: {0}")]
67    List(#[from] list::ListCommandError),
68
69    #[error("manifest: {0}")]
70    Manifest(#[from] manifest::ManifestCommandError),
71
72    #[error("medic: {0}")]
73    Medic(#[from] medic::MedicCommandError),
74
75    #[error("metrics: {0}")]
76    Metrics(#[from] metrics::MetricsCommandError),
77
78    #[error("snapshot: {0}")]
79    Snapshot(#[from] snapshot::SnapshotCommandError),
80
81    #[error("restore: {0}")]
82    Restore(#[from] restore::RestoreCommandError),
83
84    #[error("replica: {0}")]
85    Replica(#[from] replica::ReplicaCommandError),
86
87    #[error("status: {0}")]
88    Status(#[from] status::StatusCommandError),
89}
90
91/// Run the CLI from process arguments.
92pub fn run_from_env() -> Result<(), CliError> {
93    run(std::env::args_os().skip(1))
94}
95
96/// Run the CLI from an argument iterator.
97pub fn run<I>(args: I) -> Result<(), CliError>
98where
99    I: IntoIterator<Item = OsString>,
100{
101    let args = args.into_iter().collect::<Vec<_>>();
102    if first_arg_is_help(&args) {
103        println!("{}", usage());
104        return Ok(());
105    }
106    if let Some(option) = command_local_global_option(&args) {
107        return Err(CliError::Usage(format!(
108            "{option} is a top-level option; put it before the command\n\n{}",
109            usage()
110        )));
111    }
112
113    let matches =
114        parse_matches(top_level_dispatch_command(), args).map_err(|_| CliError::Usage(usage()))?;
115    if matches.get_flag("version") {
116        println!("{}", version_text());
117        return Ok(());
118    }
119    let global_icp = matches.get_one::<String>("icp").cloned();
120    let global_network = matches.get_one::<String>("network").cloned();
121
122    let Some((command, subcommand_matches)) = matches.subcommand() else {
123        return Err(CliError::Usage(usage()));
124    };
125    let mut tail = subcommand_matches
126        .get_many::<OsString>(DISPATCH_ARGS)
127        .map(|values| values.cloned().collect::<Vec<_>>())
128        .unwrap_or_default();
129    apply_global_icp(command, &mut tail, global_icp);
130    apply_global_network(command, &mut tail, global_network);
131    let tail = tail.into_iter();
132
133    match command {
134        "backup" => backup::run(tail).map_err(CliError::from),
135        "build" => build::run(tail).map_err(CliError::from),
136        "config" => list::run_config(tail).map_err(|err| CliError::Config(err.to_string())),
137        "cycles" => cycles::run(tail).map_err(CliError::from),
138        "endpoints" => endpoints::run(tail).map_err(CliError::from),
139        "fleet" => fleets::run(tail).map_err(CliError::from),
140        "install" => install::run(tail).map_err(CliError::from),
141        "list" => list::run(tail).map_err(CliError::from),
142        "manifest" => manifest::run(tail).map_err(CliError::from),
143        "medic" => medic::run(tail).map_err(CliError::from),
144        "metrics" => metrics::run(tail).map_err(CliError::from),
145        "replica" => replica::run(tail).map_err(CliError::from),
146        "snapshot" => snapshot::run(tail).map_err(CliError::from),
147        "status" => status::run(tail).map_err(CliError::from),
148        "restore" => restore::run(tail).map_err(CliError::from),
149        _ => unreachable!("top-level dispatch command only defines known commands"),
150    }
151}
152
153#[must_use]
154pub const fn version_text() -> &'static str {
155    VERSION_TEXT
156}
157
158#[cfg(test)]
159mod tests;