1use clap::Parser;
2
3#[derive(Parser, Debug, Clone)]
4#[command(author, version, about = "cargo-e is for Example.", long_about = None)]
5#[command(disable_version_flag = true)]
6pub struct Cli {
7 #[arg(
9 long = "cwd-wsr",
10 help = "Run targets from the workspace root (Cargo.toml parent) instead of the current working directory."
11 )]
12 pub cwd_wsr: bool,
13 #[arg(
17 long,
18 value_name = "PATH",
19 help = "Path to read/write the stdout of the executed command."
20 )]
21 pub stdout: Option<std::path::PathBuf>,
22
23 #[arg(
25 long,
26 value_name = "PATH",
27 help = "Path to read/write the stderr of the executed command."
28 )]
29 pub stderr: Option<std::path::PathBuf>,
30 #[arg(
35 long,
36 num_args = 0..=1,
37 default_value_t = RunAll::NotSpecified,
38 default_missing_value ="",
39 value_parser,
40 help = "Run all optionally specifying run time (in seconds) per target. \
41 If the flag is present without a value, run forever."
42 )]
43 pub run_all: RunAll,
44
45 #[arg(long, help = "Create GIST run_report.md on exit.")]
46 pub gist: bool,
47 #[arg(long, help = "Build and run in release mode.")]
48 pub release: bool,
49 #[arg(
50 long,
51 short = 'q',
52 help = "Suppress cargo output when running the sample."
53 )]
54 pub quiet: bool,
55 #[clap(
60 long,
61 help = "If enabled, pre-build the examples before executing them."
62 )]
63 pub pre_build: bool,
64
65 #[clap(
66 long,
67 default_value_t = false,
68 help = "If enabled, execute the existing target directly."
69 )]
70 pub cached: bool,
71 #[arg(
75 long = "detached",
76 help = "Run the targets in detached mode. (cmd /c show | alacritty)"
77 )]
78 pub detached: bool,
79 #[arg(
80 long = "scan-dir",
81 value_name = "DIR",
82 help = "Scan the given directory for targets to run."
83 )]
84 pub scan_dir: Option<std::path::PathBuf>,
85 #[arg(
87 long = "filter",
88 short = 'f',
89 help = "Enable filter mode. cargo output is filtered and captured."
90 )]
91 pub filter: bool,
92 #[arg(
94 long,
95 short = 'v',
96 help = "Print version and feature flags in JSON format."
97 )]
98 pub version: bool,
99
100 #[arg(
101 long,
102 short = 't',
103 help = "Launch the text-based user interface (TUI)."
104 )]
105 pub tui: bool,
106
107 #[arg(long, short = 'w', help = "Operate on the entire workspace.")]
108 pub workspace: bool,
109
110 #[arg(
112 long = "pX",
113 default_value_t = false,
114 value_parser = clap::value_parser!(bool),
115 help = "Print the exit code of the process when run. (default: false)"
116 )]
117 pub print_exit_code: bool,
118
119 #[arg(
121 long = "pN",
122 default_value_t = false,
123 value_parser = clap::value_parser!(bool),
124 help = "Print the program name before execution. (default: false)"
125 )]
126 pub print_program_name: bool,
127
128 #[arg(
130 long = "pI",
131 default_value_t = true,
132 value_parser = clap::value_parser!(bool),
133 help = "Print the user instruction. (default: true)"
134 )]
135 pub print_instruction: bool,
136
137 #[arg(
138 long,
139 short = 'p',
140 default_value_t = true,
141 help = "Enable or disable paging (default: enabled)."
142 )]
143 pub paging: bool,
144
145 #[arg(
146 long,
147 short = 'r',
148 default_value_t = false,
149 help = "Relative numbers (default: enabled)."
150 )]
151 pub relative_numbers: bool,
152
153 #[arg(
154 long = "wait",
155 short = 'W',
156 default_value_t = 15,
157 help = "Set wait time in seconds (default: 15)."
158 )]
159 pub wait: u64,
160
161 #[arg(
163 long = "subcommand",
164 short = 's',
165 value_parser,
166 default_value = "run",
167 help = "Specify subcommands (e.g., `build|b`, `test|t`)."
168 )]
169 pub subcommand: String,
170
171 #[arg(help = "Specify an explicit target to run.")]
172 pub explicit_example: Option<String>,
173
174 #[arg(
175 long = "run-at-a-time",
176 short = 'J',
177 default_value_t = 1,
178 value_parser = clap::value_parser!(usize),
179 help = "Number of targets to run at a time in --run-all mode (--run-at-a-time)"
180 )]
181 pub run_at_a_time: usize,
182
183 #[arg(
184 long = "nS",
185 default_value_t = false,
186 help = "Disable status lines during runtime loop output."
187 )]
188 pub no_status_lines: bool,
189
190 #[arg(
191 long = "nT",
192 default_value_t = false,
193 help = "Disable text-to-speech output."
194 )]
195 pub no_tts: bool,
196
197 #[arg(
199 long = "parse-available",
200 help = "Parse available targets from stdin (one per line)."
201 )]
202 pub parse_available: bool,
203
204 #[arg(
205 long = "default-binary-is-runner",
206 default_value_t = false,
207 help = "If enabled, treat the default binary as the runner for targets."
208 )]
209 pub default_binary_is_runner: bool,
210
211 #[arg(long = "nW", default_value_t = false, help = "Disable window popups.")]
212 pub no_window: bool,
213
214 #[arg(
216 long = "log",
217 value_name = "PATH",
218 help = "Enable logging to a file at the given path, or to stdout if not specified."
219 )]
220 pub log: Option<std::path::PathBuf>,
221
222 #[arg(
223 long = "manifest-path",
224 value_name = "PATH",
225 help = "Specify the path to the Cargo.toml manifest file."
226 )]
227 pub manifest_path: Option<std::path::PathBuf>,
228
229 #[arg(
230 long = "target",
231 value_name = "TARGET",
232 help = "Specify the target triple for the build."
233 )]
234 pub target: Option<String>,
235
236 #[arg(
238 long = "json-all-targets",
239 help = "Output the list of all targets as JSON."
240 )]
241 pub json_all_targets: bool,
242
243 #[arg(last = true, help = "Additional arguments passed to the command.")]
244 pub extra: Vec<String>,
245
246 #[clap(
247 long,
248 value_name = "SECONDS",
249 help = "Time in seconds to keep detached windows open before killing."
250 )]
251 pub detached_hold: Option<u32>,
252 #[clap(
253 long,
254 value_name = "SECONDS",
255 help = "Time in seconds for detached windows to delay before executing target"
256 )]
257 pub detached_delay: Option<u32>,
258}
259
260pub fn print_version_and_features() {
262 let version = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown");
264
265 let mut features = Vec::new();
268
269 if cfg!(feature = "tui") {
270 features.push("tui");
271 } else {
272 features.push("!tui");
273 }
274 if cfg!(feature = "concurrent") {
275 features.push("concurrent");
276 } else {
277 features.push("!concurrent");
278 }
279 if cfg!(target_os = "windows") {
280 features.push("windows");
281 } else {
282 features.push("!windows");
283 }
284 if cfg!(feature = "equivalent") {
285 features.push("equivalent");
286 } else {
287 features.push("!equivalent");
288 }
289
290 let json_features = format!(
291 "[{}]",
292 features
293 .iter()
294 .map(|f| format!("\"{}\"", f))
295 .collect::<Vec<String>>()
296 .join(", ")
297 );
298 println!("cargo-e {}", version);
299 println!("{}", json_features);
300 std::process::exit(0);
301}
302
303pub fn get_feature_flags() -> Vec<&'static str> {
306 let mut flags = Vec::new();
307 if cfg!(feature = "tui") {
308 flags.push("tui");
309 } else {
310 flags.push("!tui");
311 }
312 if cfg!(feature = "concurrent") {
313 flags.push("concurrent");
314 } else {
315 flags.push("!concurrent");
316 }
317 if cfg!(target_os = "windows") {
318 flags.push("windows");
319 } else {
320 flags.push("!windows");
321 }
322 if cfg!(feature = "equivalent") {
323 flags.push("equivalent");
324 } else {
325 flags.push("!equivalent");
326 }
327 flags
328}
329
330use std::str::FromStr;
331
332#[derive(Debug, Clone, PartialEq, Default)]
334pub enum RunAll {
335 #[default]
337 NotSpecified,
338 Forever,
340 Timeout(u64),
342}
343
344impl FromStr for RunAll {
345 type Err = String;
346 fn from_str(s: &str) -> Result<Self, Self::Err> {
347 if s.is_empty() {
349 Ok(RunAll::Forever)
350 } else if s.eq_ignore_ascii_case("not_specified") {
351 Ok(RunAll::NotSpecified)
352 } else {
353 s.parse::<u64>()
355 .map(RunAll::Timeout)
356 .map_err(|e| e.to_string())
357 }
358 }
359}
360
361impl std::fmt::Display for RunAll {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 match self {
364 RunAll::NotSpecified => write!(f, "not_specified"),
365 RunAll::Forever => write!(f, "forever"),
366 RunAll::Timeout(secs) => write!(f, "{}", secs),
367 }
368 }
369}
370
371pub fn custom_cli(args: &mut Vec<String>) -> (Option<usize>, Vec<&String>) {
372 if args.len() > 1 && args[1].as_str() == "e" {
374 args.remove(1);
375 }
376 let mut run_at_a_time: Option<usize> = None;
377 let mut filtered_args = vec![];
379 for arg in &*args {
380 if let Some(num) = arg
381 .strip_prefix("--run-")
382 .and_then(|s| s.strip_suffix("-at-a-time"))
383 {
384 if let Ok(n) = num.parse() {
385 println!("run-at-a-time: {}", n);
386 run_at_a_time = Some(n);
387 }
388 continue;
390 }
391 filtered_args.push(arg);
392 }
393 (run_at_a_time, filtered_args)
394}