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