1use std::{collections::BTreeMap, fmt, path::PathBuf};
2
3use clap::{
4 builder::styling::{AnsiColor, Color, Style},
5 crate_authors,
6};
7use clap_verbosity_flag::{InfoLevel, Verbosity};
8use diff::Diff;
9use serde::{Deserialize, Serialize};
10
11use crate::consts::{
12 TREE_SITTER_PLATFORM, TREE_SITTER_REPO, TREE_SITTER_VERSION, TSDL_BUILD_DIR, TSDL_CONFIG_FILE,
13 TSDL_FRESH, TSDL_OUT_DIR, TSDL_PREFIX, TSDL_SHOW_CONFIG,
14};
15
16const TSDL_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/tsdl.version"));
17
18#[derive(Clone, Debug, Deserialize, clap::Parser, Serialize)]
20#[command(author = crate_authors!("\n"), version = TSDL_VERSION, about, styles=get_styles(), allow_external_subcommands = true)]
21#[command(help_template(
22 "{before-help}{name} {version}
23{author-with-newline}{about-with-newline}
24{usage-heading} {usage}
25
26{all-args}{after-help}"
27))]
28pub struct Args {
29 #[command(subcommand)]
30 pub command: Command,
31
32 #[arg(short, long, env = "TSDL_CONFIG", default_value = TSDL_CONFIG_FILE, global = true)]
34 pub config: PathBuf,
35
36 #[arg(short, long, env = "TSDL_LOG", global = true)]
38 pub log: Option<PathBuf>,
39
40 #[arg(long, value_enum, default_value_t = LogColor::Auto, global = true)]
42 pub log_color: LogColor,
43
44 #[arg(long, value_enum, default_value_t = ProgressStyle::Auto, global = true)]
46 pub progress: ProgressStyle,
47
48 #[serde(skip_serializing, skip_deserializing)]
52 #[command(flatten)]
53 pub verbose: Verbosity<InfoLevel>,
54}
55
56#[derive(clap::ValueEnum, Clone, Debug, Deserialize, Serialize)]
57pub enum LogColor {
58 Auto,
59 No,
60 Yes,
61}
62
63#[derive(clap::ValueEnum, Clone, Debug, Deserialize, Serialize)]
64pub enum ProgressStyle {
65 Auto,
66 Fancy,
67 Plain,
68}
69
70#[derive(clap::Subcommand, Clone, Debug, Deserialize, Serialize)]
71pub enum Command {
72 #[command(visible_alias = "b")]
74 Build(BuildCommand),
75
76 #[serde(skip_serializing, skip_deserializing)]
78 #[command(visible_alias = "c")]
79 Config {
80 #[command(subcommand)]
81 command: ConfigCommand,
82 },
83
84 #[serde(skip_serializing, skip_deserializing)]
86 #[command(visible_alias = "u")]
87 Selfupdate,
88}
89
90impl Command {
91 #[must_use]
92 pub fn as_build(&self) -> Option<&BuildCommand> {
93 if let Command::Build(build) = self {
94 Some(build)
95 } else {
96 None
97 }
98 }
99
100 #[must_use]
101 pub fn as_config(&self) -> Option<&ConfigCommand> {
102 if let Command::Config { command } = self {
103 Some(command)
104 } else {
105 None
106 }
107 }
108}
109
110#[derive(clap::ValueEnum, Clone, Copy, Debug, Deserialize, Diff, PartialEq, Eq, Serialize)]
111#[diff(attr(
112 #[derive(Debug, PartialEq)]
113))]
114#[serde(rename_all = "kebab-case")]
115pub enum Target {
116 Native,
117 Wasm,
118 All,
119}
120
121impl Default for Target {
122 fn default() -> Self {
123 Self::Native
124 }
125}
126
127impl Target {
128 #[must_use]
129 pub fn native(&self) -> bool {
130 matches!(self, Self::All | Self::Native)
131 }
132
133 #[must_use]
134 pub fn wasm(&self) -> bool {
135 matches!(self, Self::All | Self::Wasm)
136 }
137}
138
139#[allow(clippy::struct_excessive_bools)]
140#[derive(clap::Args, Clone, Debug, Deserialize, Diff, PartialEq, Eq, Serialize)]
141#[diff(attr(
142 #[derive(Debug, PartialEq)]
143))]
144#[serde(rename_all = "kebab-case")]
145pub struct BuildCommand {
146 #[serde(skip_serializing, skip_deserializing)]
148 #[arg(verbatim_doc_comment)]
149 pub languages: Option<Vec<String>>,
150
151 #[clap(skip)]
153 pub parsers: Option<BTreeMap<String, ParserConfig>>,
154
155 #[serde(default)]
157 #[arg(short, long, env = "TSDL_BUILD_DIR", default_value = TSDL_BUILD_DIR)]
158 pub build_dir: PathBuf,
159
160 #[arg(short, long, env = "TSDL_NCPUS", default_value_t = num_cpus::get())]
162 #[serde(default)]
163 pub ncpus: usize,
164
165 #[arg(short, long, default_value_t = TSDL_FRESH)]
167 #[serde(default)]
168 pub fresh: bool,
169
170 #[arg(short, long, env = "TSDL_OUT_DIR", default_value = TSDL_OUT_DIR)]
172 #[serde(default)]
173 pub out_dir: PathBuf,
174
175 #[arg(short, long, env = "TSDL_PREFIX", default_value = TSDL_PREFIX)]
177 #[serde(default)]
178 pub prefix: String,
179
180 #[arg(long, default_value_t = TSDL_SHOW_CONFIG)]
182 #[serde(default)]
183 pub show_config: bool,
184
185 #[arg(short, long, value_enum, default_value_t = Target::default())]
187 pub target: Target,
188
189 #[command(flatten)]
190 #[serde(default)]
191 pub tree_sitter: TreeSitter,
192}
193
194impl Default for BuildCommand {
195 fn default() -> Self {
196 Self {
197 build_dir: PathBuf::from(TSDL_BUILD_DIR),
198 fresh: TSDL_FRESH,
199 languages: None,
200 ncpus: num_cpus::get(),
201 out_dir: PathBuf::from(TSDL_OUT_DIR),
202 parsers: None,
203 prefix: String::from(TSDL_PREFIX),
204 show_config: TSDL_SHOW_CONFIG,
205 target: Target::default(),
206 tree_sitter: TreeSitter::default(),
207 }
208 }
209}
210
211#[derive(Clone, Debug, Deserialize, Diff, Serialize, PartialEq, Eq)]
212#[diff(attr(
213 #[derive(Debug, PartialEq)]
214))]
215#[serde(untagged)]
216#[serde(rename_all = "kebab-case")]
217pub enum ParserConfig {
218 Full {
219 #[serde(alias = "cmd", alias = "script")]
220 build_script: Option<String>,
221 #[serde(rename = "ref")]
222 #[diff(attr(
223 #[derive(Debug, PartialEq)]
224 ))]
225 git_ref: String,
226 #[diff(attr(
227 #[derive(Debug, PartialEq)]
228 ))]
229 from: Option<String>,
230 },
231 Ref(String),
232}
233
234#[derive(clap::Args, Clone, Debug, Diff, Deserialize, PartialEq, Eq, Serialize)]
235#[diff(attr(
236 #[derive(Debug, PartialEq)]
237))]
238pub struct TreeSitter {
239 #[arg(short = 'V', long = "tree-sitter-version", default_value = TREE_SITTER_VERSION)]
241 pub version: String,
242
243 #[arg(short = 'R', long = "tree-sitter-repo", default_value = TREE_SITTER_REPO)]
245 pub repo: String,
246
247 #[clap(long = "tree-sitter-platform", default_value = TREE_SITTER_PLATFORM)]
249 pub platform: String,
250}
251
252impl Default for TreeSitter {
253 fn default() -> Self {
254 Self {
255 version: TREE_SITTER_VERSION.to_string(),
256 repo: TREE_SITTER_REPO.to_string(),
257 platform: TREE_SITTER_PLATFORM.to_string(),
258 }
259 }
260}
261
262#[derive(clap::Subcommand, Clone, Debug, Default)]
263pub enum ConfigCommand {
264 #[default]
265 Current,
266 Default,
267}
268
269impl fmt::Display for ConfigCommand {
270 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271 write!(f, "{:?}", format!("{self:?}").to_lowercase())
272 }
273}
274
275#[must_use]
276const fn get_styles() -> clap::builder::Styles {
277 clap::builder::Styles::styled()
278 .usage(
279 Style::new()
280 .bold()
281 .fg_color(Some(Color::Ansi(AnsiColor::Yellow))),
282 )
283 .header(
284 Style::new()
285 .bold()
286 .fg_color(Some(Color::Ansi(AnsiColor::Yellow))),
287 )
288 .literal(Style::new().fg_color(Some(Color::Ansi(AnsiColor::Blue))))
289 .invalid(
290 Style::new()
291 .bold()
292 .fg_color(Some(Color::Ansi(AnsiColor::Red))),
293 )
294 .error(
295 Style::new()
296 .bold()
297 .fg_color(Some(Color::Ansi(AnsiColor::Red))),
298 )
299 .valid(
300 Style::new()
301 .bold()
302 .fg_color(Some(Color::Ansi(AnsiColor::Blue))),
303 )
304 .placeholder(Style::new().fg_color(Some(Color::Ansi(AnsiColor::White))))
305}