Skip to main content

canic_cli/
lib.rs

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