Skip to main content

canic_cli/
lib.rs

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