1#[global_allocator]
4static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
5
6mod resolver;
7
8use std::process::ExitCode;
9
10use anyhow::ensure;
11use clap::Parser;
12use wowlab_common::{cli, output};
13use wowlab_fs::{
14 artifact::{GeneratedTextFile, Status},
15 path::{Path, PathBuf},
16};
17use wowlab_types::table_registry::PUBLISHED_SNAPSHOT_TABLES;
18
19const RESOLVER_OUTPUT: &str = "game-data-resolver.generated.ts";
20
21#[derive(Parser)]
22#[command(
23 name = "tablegen",
24 about = "Generate the studio bulk-table list from the snapshot registry"
25)]
26struct Args {
27 #[arg(long)]
29 output: PathBuf,
30
31 #[arg(long, short)]
33 quiet: bool,
34
35 #[arg(long)]
37 check: bool,
38}
39
40const HEADER: &str = "// @generated by tablegen from the Rust snapshot registry (crates/cli) -- do not edit by hand.";
42
43fn generate() -> String {
44 let mut out = String::with_capacity(2048);
45
46 out.push_str(HEADER);
47 out.push_str(
48 "\n\nexport type BulkTable = {\n \
49 readonly key: readonly [string, ...string[]];\n \
50 readonly name: string;\n};\n\nexport const BULK_TABLES = [\n",
51 );
52
53 for &table in PUBLISHED_SNAPSHOT_TABLES {
54 let name = table.database_name().trim_start_matches("game.");
55 let key = table
56 .snapshot_key()
57 .iter()
58 .map(|col| format!("\"{col}\""))
59 .collect::<Vec<_>>()
60 .join(", ");
61
62 out.push_str(" {\n key: [");
63 out.push_str(&key);
64 out.push_str("],\n name: \"");
65 out.push_str(name);
66 out.push_str("\",\n },\n");
67 }
68
69 out.push_str(
70 "] as const satisfies readonly BulkTable[];\n\n\
71 export type BulkTableName = (typeof BULK_TABLES)[number][\"name\"];\n",
72 );
73
74 out
75}
76
77fn run(args: &Args) -> anyhow::Result<ExitCode> {
78 let _app = cli::boot(
79 "tablegen",
80 env!("CARGO_PKG_VERSION"),
81 args.quiet || args.check,
82 "TABLEGEN_ROOT",
83 );
84
85 let resolver_output = args
86 .output
87 .parent()
88 .unwrap_or_else(|| Path::new("."))
89 .join(RESOLVER_OUTPUT);
90
91 ensure!(
92 args.output != resolver_output,
93 "--output must not use the reserved resolver filename {RESOLVER_OUTPUT}"
94 );
95
96 let outputs = [
97 (args.output.clone(), generate()),
98 (resolver_output, resolver::generate()?),
99 ];
100
101 if args.check {
102 let mut stale = false;
103
104 for (path, generated) in &outputs {
105 if GeneratedTextFile::new(path, generated).status()? != Status::Current {
106 stale = true;
107 output::error(&format!(
108 "{} is stale, run `cargo tablegen` to regenerate",
109 path.display()
110 ));
111 }
112 }
113
114 if stale {
115 return Ok(ExitCode::FAILURE);
116 }
117
118 output::success("generated studio files are up-to-date");
119
120 return Ok(ExitCode::SUCCESS);
121 }
122
123 for (path, generated) in &outputs {
124 GeneratedTextFile::new(path, generated).persist()?;
125
126 if !args.quiet {
127 output::success(&format!("wrote {}", path.display()));
128 }
129 }
130
131 Ok(ExitCode::SUCCESS)
132}
133
134fn main() -> ExitCode {
135 let args = Args::parse();
136
137 match run(&args) {
138 Ok(code) => code,
139 Err(e) => {
140 output::error(&format!("{e:#}"));
141
142 ExitCode::FAILURE
143 }
144 }
145}