1#[global_allocator]
4static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
5
6use anyhow::Result;
7use clap::{Parser, Subcommand};
8use tracing_subscriber::{EnvFilter, fmt, prelude::*};
9use wowlab_cli::commands;
10use wowlab_common::cli;
11
12#[derive(Parser)]
13#[command(name = "wowlab")]
14#[command(about = "WoW data processing tools")]
15struct Cli {
16 #[arg(long, short, global = true)]
18 quiet: bool,
19
20 #[command(subcommand)]
21 command: Commands,
22}
23
24#[derive(Subcommand)]
25enum Commands {
26 Snapshot {
28 #[command(subcommand)]
29 command: commands::snapshot::SnapshotCommand,
30 },
31 Migrate,
33}
34
35#[tokio::main]
36async fn main() -> Result<()> {
37 let _ = dotenvy::dotenv();
38
39 let cli = Cli::parse();
40
41 let _app = cli::boot(
42 "wowlab",
43 env!("CARGO_PKG_VERSION"),
44 cli.quiet,
45 "WOWLAB_ROOT",
46 );
47
48 tracing_subscriber::registry()
49 .with(fmt::layer().without_time().with_target(false))
50 .with(
51 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,sqlx=error")),
52 )
53 .init();
54
55 match cli.command {
56 Commands::Snapshot { command } => command.run().await,
57 Commands::Migrate => commands::migrate::run_migrate(cli.quiet).await,
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use clap::{CommandFactory, Parser, error::ErrorKind};
64 use googletest::prelude::*;
65
66 use super::Cli;
67
68 #[gtest]
69 fn command_tree_and_help_contract_are_stable() -> Result<()> {
70 let mut command = Cli::command();
71
72 command.clone().debug_assert();
73
74 let help = command.render_help().to_string();
75
76 verify_that!(
77 help,
78 eq("WoW data processing tools\n\n\
79 Usage: wowlab [OPTIONS] <COMMAND>\n\n\
80 Commands:\n \
81 snapshot Snapshot data processing commands\n \
82 migrate Interactive migration: detect version, prompt for config, sync\n \
83 help Print this message or the help of the given subcommand(s)\n\n\
84 Options:\n \
85 -q, --quiet Suppress non-essential output\n \
86 -h, --help Print help\n")
87 )?;
88
89 let snapshot = command
90 .get_subcommands()
91 .find(|subcommand| subcommand.get_name() == "snapshot")
92 .or_fail()?;
93 let snapshot_commands = snapshot
94 .get_subcommands()
95 .map(clap::Command::get_name)
96 .collect::<Vec<_>>();
97
98 verify_that!(
99 snapshot_commands,
100 eq(&vec![
101 "sync",
102 "dump-spell",
103 "dump-trait",
104 "dump-assisted",
105 "assisted-rotas",
106 "decode-loadout",
107 ])
108 )?;
109
110 let sync = snapshot
111 .get_subcommands()
112 .find(|subcommand| subcommand.get_name() == "sync")
113 .or_fail()?;
114 let long_options = sync
115 .get_arguments()
116 .filter_map(clap::Arg::get_long)
117 .collect::<Vec<_>>();
118
119 verify_that!(
120 long_options,
121 eq(&vec!["patch", "data-dir", "dry-run", "json-only", "table"])
122 )
123 }
124
125 #[gtest]
126 fn invalid_argument_keeps_clap_exit_code_two() -> Result<()> {
127 let error = Cli::try_parse_from(["wowlab", "snapshot", "sync", "--definitely-invalid"])
128 .err()
129 .or_fail()?;
130
131 verify_that!(error.kind(), eq(ErrorKind::UnknownArgument))?;
132
133 verify_that!(error.exit_code(), eq(2))
134 }
135}