1#![deny(clippy::all)]
2#![warn(clippy::nursery, clippy::pedantic)]
3
4use std::env;
5
6use anyhow::Result;
7
8mod commands;
9mod coverage;
10mod dev;
11mod dist;
12mod fixup;
13mod utils;
14
15const HELP: &str = "\
16cargo xtask - helper scripts for running common project tasks
17
18USAGE:
19 cargo xtask [OPTIONS] [TASK]...
20
21OPTIONS:
22 --cross Uses cross for cross-compilation instead of cargo
23 -i, --ignore-missing Ignores any missing tools; only warns during fixup
24 --target <TRIPLE> Sets the target triple that dist will build
25 -h, --help Prints help information
26
27TASKS:
28 check Watch for file changes and auto-trigger clippy linting
29 coverage Generate and print a code coverage report summary
30 coverage.html Generate and open an HTML code coverage report
31 dist Package project assets into distributable artifacts
32 doc Watch for file changes and auto-trigger doc generation
33 fixup Run all fixup xtasks, editing files in-place
34 fixup.github-actions Lint CI YAML files using actionlint static checker
35 fixup.markdown Format Markdown files in-place
36 fixup.rust Fix lints and format Rust files in-place
37 fixup.spelling Fix common misspellings across all files in-place
38 install Install required Rust components and Cargo dependencies
39 test Run all tests/doctests and generate Insta snapshots
40";
41
42enum Task {
43 Check,
44 Coverage,
45 CoverageHtml,
46 Dist,
47 Doc,
48 Fixup,
49 FixupGithubActions,
50 FixupMarkdown,
51 FixupRust,
52 FixupSpelling,
53 Install,
54 Test,
55}
56
57pub struct Config {
58 cross: bool,
59 run_tasks: Vec<Task>,
60 ignore_missing_commands: bool,
61 target: Option<String>,
62}
63
64fn main() -> Result<()> {
65 if env::args().len() == 1 {
67 print!("{HELP}");
68 std::process::exit(1);
69 }
70
71 let config = parse_args()?;
72 for task in &config.run_tasks {
73 match task {
74 Task::Check => dev::watch_clippy(&config)?,
75 Task::Coverage => coverage::report_summary(&config)?,
76 Task::CoverageHtml => coverage::html_report(&config)?,
77 Task::Dist => dist::dist(&config)?,
78 Task::Doc => dev::watch_doc(&config)?,
79 Task::Fixup => fixup::everything(&config)?,
80 Task::FixupGithubActions => fixup::github_actions(&config)?,
81 Task::FixupMarkdown => fixup::markdown(&config)?,
82 Task::FixupRust => fixup::rust(&config)?,
83 Task::FixupSpelling => fixup::spelling(&config)?,
84 Task::Install => dev::install_rust_deps(&config)?,
85 Task::Test => dev::test_with_snapshots(&config)?,
86 }
87 }
88
89 Ok(())
90}
91
92fn parse_args() -> Result<Config> {
93 use lexopt::prelude::*;
94
95 let mut cross = false;
97 let mut ignore_missing_commands = false;
98 let mut target: Option<String> = None;
99 let mut run_tasks = Vec::new();
100
101 let mut parser = lexopt::Parser::from_env();
102 while let Some(arg) = parser.next()? {
103 match arg {
104 Short('h') | Long("help") => {
105 print!("{HELP}");
106 std::process::exit(0);
107 }
108 Long("cross") => {
109 cross = true;
110 }
111 Short('i') | Long("ignore-missing") => {
112 ignore_missing_commands = true;
113 }
114 Long("target") => {
115 target = Some(parser.value()?.string()?);
116 }
117 Value(value) => {
118 let value = value.string()?;
119 let task = match value.as_str() {
120 "check" => Task::Check,
121 "coverage" => Task::Coverage,
122 "coverage.html" => Task::CoverageHtml,
123 "dist" => Task::Dist,
124 "doc" => Task::Doc,
125 "fixup" => Task::Fixup,
126 "fixup.github-actions" => Task::FixupGithubActions,
127 "fixup.markdown" => Task::FixupMarkdown,
128 "fixup.rust" => Task::FixupRust,
129 "fixup.spelling" => Task::FixupSpelling,
130 "install" => Task::Install,
131 "test" => Task::Test,
132 value => {
133 anyhow::bail!("unknown task '{value}'");
134 }
135 };
136 run_tasks.push(task);
137 }
138 _ => anyhow::bail!(arg.unexpected()),
139 }
140 }
141
142 if run_tasks.is_empty() {
143 anyhow::bail!("no task given");
144 }
145
146 Ok(Config {
147 cross,
148 run_tasks,
149 ignore_missing_commands,
150 target,
151 })
152}