Skip to main content

canic_cli/
lib.rs

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