Skip to main content

canic_cli/
lib.rs

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