Skip to main content

canic_cli/
lib.rs

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