1use crate::UtilesError;
2use crate::cli::args::{Cli, Commands};
3use crate::cli::commands::{
4 about_main, addo_main, agg_hash_main, bounding_tile_main, burn_main, children_main,
5 commands_main, contains_main, copy_main, dev_main, edges_main, enumerate_main,
6 fmtstr_main, info_main, lint_main, merge_main, metadata_main, metadata_set_main,
7 neighbors_main, optimize_main, parent_main, pmtileid_main, quadkey_main,
8 rimraf_main, serve_main, shapes_main, tilejson_main, tiles_main, touch_main,
9 translate_main, update_main, vacuum_main, webpify_main, zxyify_main,
10};
11use crate::errors::UtilesResult;
12use crate::internal::signal::shutdown_signal;
13use crate::lager::{LagerConfig, LagerLevel, init_tracing};
14use clap::{CommandFactory, FromArgMatches};
15use tracing::{debug, error, trace};
16use utiles_core::VERSION;
17
18pub struct CliOpts {
19 pub argv: Option<Vec<String>>,
20 pub clid: Option<&'static str>,
21}
22
23impl Default for CliOpts {
24 fn default() -> Self {
25 Self {
26 argv: None,
27 clid: Option::from("rust"),
28 }
29 }
30}
31
32impl CliOpts {
33 #[must_use]
34 pub fn aboot_str(&self) -> String {
35 format!(
36 "utiles cli ({}) ~ v{}",
37 self.clid.unwrap_or("rust"),
38 VERSION
39 )
40 }
41}
42
43pub async fn cli_main(cliops: Option<CliOpts>) -> UtilesResult<u8> {
44 tokio::select! {
45 res = async {
46 cli_main_inner(
47 cliops
48 ).await
49 } => {
50 debug!("Done. :)");
51 res
52 }
53 () = async {
54 shutdown_signal().await;
55 } => {
56 debug!("Aborted. :(");
57 Err(
58 UtilesError::Error(
59 "Aborted.".to_string()
60 )
61 )
62 }
63 }
64}
65
66#[allow(clippy::unused_async)]
67pub(crate) async fn cli_main_inner(cliopts: Option<CliOpts>) -> UtilesResult<u8> {
68 let opts = cliopts.unwrap_or_default();
70 let argv = opts.argv.unwrap_or_else(|| std::env::args().collect());
71 let about_str = format!(
72 "utiles cli ({}) ~ v{}",
73 opts.clid.unwrap_or("rust"),
74 VERSION
75 );
76
77 let max_term_width = {
80 if let Ok(val) = std::env::var("UTILES_MAX_TERM_WIDTH") {
81 val.parse::<usize>().unwrap_or(120)
82 } else {
83 120
84 }
85 };
86 let cli = Cli::command()
88 .about(about_str)
89 .max_term_width(max_term_width);
90 let matches = cli.get_matches_from(
91 &argv,
93 );
94 let args = Cli::from_arg_matches(&matches).expect("from_arg_matches failed");
95
96 let logcfg = if let Commands::Dev(_) = args.command {
98 LagerConfig {
99 level: LagerLevel::Debug,
100 json: args.log_json,
101 }
102 } else {
103 let level = if args.trace {
104 LagerLevel::Trace
105 } else if args.debug {
106 LagerLevel::Debug
107 } else {
108 LagerLevel::Info
109 };
110 LagerConfig {
111 level,
112 json: args.log_json,
113 }
114 };
115 init_tracing(logcfg)?;
116
117 trace!(
118 args = ?args,
119 argv = ?argv,
120 );
121
122 let ti = std::time::Instant::now();
123 let res: UtilesResult<()> = match args.command {
124 Commands::About(args) => about_main(&args),
125 Commands::Commands(args) => {
126 let c = Cli::command();
127 commands_main(&c, &args)
128 }
129 Commands::Sqlite(dbcmds) => dbcmds.run().await,
130 Commands::Lint(args) => lint_main(&args).await,
131 Commands::Touch(args) => touch_main(&args).await,
132 Commands::Vacuum(args) => vacuum_main(&args).await,
133 Commands::Metadata(args) => metadata_main(&args).await,
134 Commands::MetadataSet(args) => metadata_set_main(&args).await,
135 Commands::Update(args) => update_main(&args).await,
136 Commands::Tilejson(args) => tilejson_main(&args).await,
137 Commands::Copy(args) => copy_main(args).await,
138 Commands::Info(args) => info_main(&args).await,
139 Commands::AggHash(args) => agg_hash_main(&args).await,
140 Commands::Dev(args) => dev_main(args).await,
141 Commands::Rimraf(args) => rimraf_main(args).await,
142 Commands::Contains { filepath, lnglat } => {
143 contains_main(&filepath, lnglat).await
144 }
145 Commands::Enumerate(args) => enumerate_main(&args).await,
146 Commands::Merge(args) => merge_main(args).await,
147 Commands::Burn(args) => burn_main(args).await,
148 Commands::Edges(args) => edges_main(args).await,
149 Commands::Zxyify(args) => zxyify_main(args).await,
150 Commands::Fmt(args) => fmtstr_main(args),
152 Commands::Quadkey(args) => quadkey_main(args),
153 Commands::Pmtileid(args) => pmtileid_main(args),
154 Commands::BoundingTile(args) => bounding_tile_main(args),
155 Commands::Tiles(args) => tiles_main(args, None).await,
156 Commands::Neighbors(args) => neighbors_main(args),
157 Commands::Children(args) => children_main(args),
158 Commands::Parent(args) => parent_main(args),
159 Commands::Shapes(args) => shapes_main(args),
160 Commands::Optimize(args) => optimize_main(args).await,
161 Commands::Webpify(args) => webpify_main(args).await,
162 Commands::Serve(args) => serve_main(args).await,
164 Commands::Addo => addo_main(None).await,
166 Commands::Translate => translate_main(None).await,
167 };
168 let elapsed = ti.elapsed();
169 let signed_duration = jiff::SignedDuration::try_from(elapsed)
170 .expect("jiff::SignedDuration::try_from failed");
171 trace!("utiles-cli-finished ~ {:#}", signed_duration);
172 match res {
173 Ok(()) => Ok(0),
174 Err(e) => {
175 error!("{}", e);
176 Err(e)
177 }
178 }
179}
180
181pub fn cli_main_sync(opts: Option<CliOpts>) -> UtilesResult<u8> {
183 tokio::runtime::Builder::new_multi_thread()
184 .enable_all()
185 .build()
186 .expect(
187 "tokio::runtime::Builder::new_multi_thread().enable_all().build() failed.",
188 )
189 .block_on(async { cli_main(opts).await })
190}
191
192#[cfg(test)]
193mod tests {
194 use crate::cli::args::Cli;
195
196 #[test]
197 fn verify_cli() {
198 use clap::CommandFactory;
199 Cli::command().debug_assert();
200 }
201}