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;
23mod token;
24
25use crate::cli::{
26 clap::parse_matches,
27 globals::{
28 DISPATCH_ARGS, apply_global_icp, apply_global_network, command_local_global_option,
29 top_level_dispatch_command,
30 },
31 help::{first_arg_is_help, usage},
32};
33pub use cli::top_level_command;
34use std::ffi::OsString;
35use thiserror::Error as ThisError;
36
37const VERSION_TEXT: &str = concat!("canic ", env!("CARGO_PKG_VERSION"));
38
39#[derive(Debug, ThisError)]
44pub enum CliError {
45 #[error("{0}")]
46 Usage(String),
47
48 #[error("backup: {0}")]
49 Backup(#[from] backup::BackupCommandError),
50
51 #[error("build: {0}")]
52 Build(#[from] build::BuildCommandError),
53
54 #[error("config: {0}")]
55 Config(String),
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("install: {0}")]
67 Install(#[from] install::InstallCommandError),
68
69 #[error("info: {0}")]
70 Info(#[from] info::InfoCommandError),
71
72 #[error("fleet: {0}")]
73 Fleets(#[from] fleets::FleetCommandError),
74
75 #[error("manifest: {0}")]
76 Manifest(#[from] manifest::ManifestCommandError),
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("token: {0}")]
100 Token(#[from] token::TokenCommandError),
101}
102
103pub fn run_from_env() -> Result<(), CliError> {
105 run(std::env::args_os().skip(1))
106}
107
108pub fn run<I>(args: I) -> Result<(), CliError>
110where
111 I: IntoIterator<Item = OsString>,
112{
113 let args = args.into_iter().collect::<Vec<_>>();
114 if first_arg_is_help(&args) {
115 println!("{}", usage());
116 return Ok(());
117 }
118 if let Some(option) = command_local_global_option(&args) {
119 return Err(CliError::Usage(format!(
120 "{option} is a top-level option; put it before the command\n\n{}",
121 usage()
122 )));
123 }
124
125 let matches =
126 parse_matches(top_level_dispatch_command(), args).map_err(|_| CliError::Usage(usage()))?;
127 if matches.get_flag("version") {
128 println!("{}", version_text());
129 return Ok(());
130 }
131 let global_icp = matches.get_one::<String>("icp").cloned();
132 let global_network = matches.get_one::<String>("network").cloned();
133
134 let Some((command, subcommand_matches)) = matches.subcommand() else {
135 return Err(CliError::Usage(usage()));
136 };
137 let mut tail = subcommand_matches
138 .get_many::<OsString>(DISPATCH_ARGS)
139 .map(|values| values.cloned().collect::<Vec<_>>())
140 .unwrap_or_default();
141 apply_global_icp(command, &mut tail, global_icp);
142 apply_global_network(command, &mut tail, global_network);
143 let tail = tail.into_iter();
144
145 match command {
146 "backup" => backup::run(tail).map_err(CliError::from),
147 "build" => build::run(tail).map_err(CliError::from),
148 "config" => list::run_config(tail).map_err(|err| CliError::Config(err.to_string())),
149 "cycles" => cycles::run(tail).map_err(CliError::from),
150 "deploy" => deploy::run(tail).map_err(CliError::from),
151 "endpoints" => endpoints::run(tail).map_err(CliError::from),
152 "fleet" => fleets::run(tail).map_err(CliError::from),
153 "info" => info::run(tail).map_err(CliError::from),
154 "install" => install::run(tail).map_err(CliError::from),
155 "manifest" => manifest::run(tail).map_err(CliError::from),
156 "medic" => medic::run(tail).map_err(CliError::from),
157 "metrics" => metrics::run(tail).map_err(CliError::from),
158 "replica" => replica::run(tail).map_err(CliError::from),
159 "scaffold" => scaffold::run(tail).map_err(CliError::from),
160 "snapshot" => snapshot::run(tail).map_err(CliError::from),
161 "status" => status::run(tail).map_err(CliError::from),
162 "token" => token::run(tail).map_err(CliError::from),
163 "restore" => restore::run(tail).map_err(CliError::from),
164 _ => unreachable!("top-level dispatch command only defines known commands"),
165 }
166}
167
168#[must_use]
169pub const fn version_text() -> &'static str {
170 VERSION_TEXT
171}
172
173#[cfg(test)]
174mod tests;