utiles/cli/
entry.rs

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    // print args
69    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    // check for `UTILES_MAX_TERM_WIDTH` env var and use it if it's set
78    // otherwise use the default value of 120
79    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    // set caller if provided
87    let cli = Cli::command()
88        .about(about_str)
89        .max_term_width(max_term_width);
90    let matches = cli.get_matches_from(
91        // argv.clone()
92        &argv,
93    );
94    let args = Cli::from_arg_matches(&matches).expect("from_arg_matches failed");
95
96    // if the command is "dev" init tracing w/ debug
97    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        // mercantile cli like
151        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        // server WIP
163        Commands::Serve(args) => serve_main(args).await,
164        // unimplemented
165        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
181// not sure why this is needed... cargo thinks it's unused???
182pub 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}