cargo_e/
e_cli.rs

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    /// Run all examples for a given number of seconds.
8
9    /// Path to read/write the stdout of the executed command.
10    #[arg(
11        long,
12        value_name = "PATH",
13        help = "Path to read/write the stdout of the executed command."
14    )]
15    pub stdout: Option<std::path::PathBuf>,
16
17    /// Path to read/write the stderr of the executed command.
18    #[arg(
19        long,
20        value_name = "PATH",
21        help = "Path to read/write the stderr of the executed command."
22    )]
23    pub stderr: Option<std::path::PathBuf>,
24    ///
25    /// If provided with a value (e.g. `--run-all 10`), each target will run for 10 seconds.
26    /// If provided without a value (i.e. just `--run-all`), it means run forever.
27    /// If not provided at all, then the default wait time is used.
28    #[arg(
29        long,
30        num_args = 0..=1,
31        default_value_t = RunAll::NotSpecified,
32        default_missing_value ="",
33        value_parser,
34        help = "Run all optionally specifying run time (in seconds) per target. \
35                If the flag is present without a value, run forever."
36    )]
37    pub run_all: RunAll,
38
39    #[arg(long, help = "Create GIST run_report.md on exit.")]
40    pub gist: bool,
41    #[arg(long, help = "Build and run in release mode.")]
42    pub release: bool,
43    #[arg(
44        long,
45        short = 'q',
46        help = "Suppress cargo output when running the sample."
47    )]
48    pub quiet: bool,
49    // /// Comma-separated list of package names.
50    // #[clap(long, value_delimiter = ',', help = "Optional list of package names to run examples for. If omitted, defaults to ALL_PACKAGES.")]
51    // pub specified_packages: Vec<String>,
52    /// Pre-build examples before running.
53    #[clap(
54        long,
55        help = "If enabled, pre-build the examples before executing them."
56    )]
57    pub pre_build: bool,
58
59    #[clap(
60        long,
61        default_value_t = false,
62        help = "If enabled, execute the existing target directly."
63    )]
64    pub cached: bool,
65
66    /// Enable filter mode. cargo output is filtered and captured."
67    #[arg(
68        long = "filter",
69        short = 'f',
70        help = "Enable filter mode. cargo output is filtered and captured."
71    )]
72    pub filter: bool,
73    /// Print version and feature flags in JSON format.
74    #[arg(
75        long,
76        short = 'v',
77        help = "Print version and feature flags in JSON format."
78    )]
79    pub version: bool,
80
81    #[arg(
82        long,
83        short = 't',
84        help = "Launch the text-based user interface (TUI)."
85    )]
86    pub tui: bool,
87
88    #[arg(long, short = 'w', help = "Operate on the entire workspace.")]
89    pub workspace: bool,
90
91    /// Print the exit code of the process when run.
92    #[arg(
93        long = "pX",
94        default_value_t = false,
95        value_parser = clap::value_parser!(bool),
96        help = "Print the exit code of the process when run. (default: false)"
97    )]
98    pub print_exit_code: bool,
99
100    /// Print the program name before execution.
101    #[arg(
102        long = "pN",
103        default_value_t = false,
104        value_parser = clap::value_parser!(bool),
105        help = "Print the program name before execution. (default: false)"
106    )]
107    pub print_program_name: bool,
108
109    /// Print the program name before execution.
110    #[arg(
111        long = "pI",
112        default_value_t = true,
113        value_parser = clap::value_parser!(bool),
114        help = "Print the user instruction. (default: true)"
115    )]
116    pub print_instruction: bool,
117
118    #[arg(
119        long,
120        short = 'p',
121        default_value_t = true,
122        help = "Enable or disable paging (default: enabled)."
123    )]
124    pub paging: bool,
125
126    #[arg(
127        long,
128        short = 'r',
129        default_value_t = false,
130        help = "Relative numbers (default: enabled)."
131    )]
132    pub relative_numbers: bool,
133
134    #[arg(
135        long = "wait",
136        short = 'W',
137        default_value_t = 15,
138        help = "Set wait time in seconds (default: 15)."
139    )]
140    pub wait: u64,
141
142    /// Subcommands to run (e.g., `build|b`, `test|t`).
143    #[arg(
144        long = "subcommand",
145        short = 's',
146        value_parser,
147        default_value = "run",
148        help = "Specify subcommands (e.g., `build|b`, `test|t`)."
149    )]
150    pub subcommand: String,
151
152    #[arg(help = "Specify an explicit target to run.")]
153    pub explicit_example: Option<String>,
154
155    #[arg(
156        long = "run-at-a-time",
157        short = 'J',
158        default_value_t = 1,
159        value_parser = clap::value_parser!(usize),
160        help = "Number of targets to run at a time in --run-all mode (--run-at-a-time)"
161    )]
162    pub run_at_a_time: usize,
163
164    #[arg(
165        long = "nS",
166        default_value_t = false,
167        help = "Disable status lines during runtime loop output."
168    )]
169    pub no_status_lines: bool,
170
171    #[arg(
172        long = "nT",
173        default_value_t = false,
174        help = "Disable text-to-speech output."
175    )]
176    pub no_tts: bool,
177
178    /// Parse available targets from stdin (one per line).
179    #[arg(
180        long = "parse-available",
181        help = "Parse available targets from stdin (one per line)."
182    )]
183    pub parse_available: bool,
184
185    #[arg(
186        long = "default-binary-is-runner",
187        default_value_t = false,
188        help = "If enabled, treat the default binary as the runner for targets."
189    )]
190    pub default_binary_is_runner: bool,
191
192    #[arg(long = "nW", default_value_t = false, help = "Disable window popups.")]
193    pub no_window: bool,
194
195    /// Enable logging to a file or stdout.
196    #[arg(
197        long = "log",
198        value_name = "PATH",
199        help = "Enable logging to a file at the given path, or to stdout if not specified."
200    )]
201    pub log: Option<std::path::PathBuf>,
202
203    #[arg(
204        long = "manifest-path",
205        value_name = "PATH",
206        help = "Specify the path to the Cargo.toml manifest file."
207    )]
208    pub manifest_path: Option<std::path::PathBuf>,
209
210    #[arg(
211        long = "target",
212        value_name = "TARGET",
213        help = "Specify the target triple for the build."
214    )]
215    pub target: Option<String>,
216
217    /// Output the list of targets as JSON.
218    #[arg(
219        long = "json-all-targets",
220        help = "Output the list of all targets as JSON."
221    )]
222    pub json_all_targets: bool,
223
224    #[arg(last = true, help = "Additional arguments passed to the command.")]
225    pub extra: Vec<String>,
226}
227
228/// Print the version and the JSON array of feature flags.
229pub fn print_version_and_features() {
230    // Print the version string.
231    let version = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown");
232
233    // Build a list of feature flags. Enabled features are printed normally,
234    // while disabled features are prefixed with an exclamation mark.
235    let mut features = Vec::new();
236
237    if cfg!(feature = "tui") {
238        features.push("tui");
239    } else {
240        features.push("!tui");
241    }
242    if cfg!(feature = "concurrent") {
243        features.push("concurrent");
244    } else {
245        features.push("!concurrent");
246    }
247    if cfg!(target_os = "windows") {
248        features.push("windows");
249    } else {
250        features.push("!windows");
251    }
252    if cfg!(feature = "equivalent") {
253        features.push("equivalent");
254    } else {
255        features.push("!equivalent");
256    }
257
258    let json_features = format!(
259        "[{}]",
260        features
261            .iter()
262            .map(|f| format!("\"{}\"", f))
263            .collect::<Vec<String>>()
264            .join(", ")
265    );
266    println!("cargo-e {}", version);
267    println!("{}", json_features);
268    std::process::exit(0);
269}
270
271/// Returns a vector of feature flag strings.  
272/// Enabled features are listed as-is while disabled ones are prefixed with "!".
273pub fn get_feature_flags() -> Vec<&'static str> {
274    let mut flags = Vec::new();
275    if cfg!(feature = "tui") {
276        flags.push("tui");
277    } else {
278        flags.push("!tui");
279    }
280    if cfg!(feature = "concurrent") {
281        flags.push("concurrent");
282    } else {
283        flags.push("!concurrent");
284    }
285    if cfg!(target_os = "windows") {
286        flags.push("windows");
287    } else {
288        flags.push("!windows");
289    }
290    if cfg!(feature = "equivalent") {
291        flags.push("equivalent");
292    } else {
293        flags.push("!equivalent");
294    }
295    flags
296}
297
298use std::str::FromStr;
299
300/// Represents the state of the `--run-all` flag.
301#[derive(Debug, Clone, PartialEq, Default)]
302pub enum RunAll {
303    /// The flag was not specified.
304    #[default]
305    NotSpecified,
306    /// The flag was specified without a value—indicating “run forever.”
307    Forever,
308    /// The flag was specified with a timeout value.
309    Timeout(u64),
310}
311
312impl FromStr for RunAll {
313    type Err = String;
314    fn from_str(s: &str) -> Result<Self, Self::Err> {
315        // An empty string means the flag was provided without a value → run forever.
316        if s.is_empty() {
317            Ok(RunAll::Forever)
318        } else if s.eq_ignore_ascii_case("not_specified") {
319            Ok(RunAll::NotSpecified)
320        } else {
321            // Otherwise, try parsing a u64 value.
322            s.parse::<u64>()
323                .map(RunAll::Timeout)
324                .map_err(|e| e.to_string())
325        }
326    }
327}
328
329impl std::fmt::Display for RunAll {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        match self {
332            RunAll::NotSpecified => write!(f, "not_specified"),
333            RunAll::Forever => write!(f, "forever"),
334            RunAll::Timeout(secs) => write!(f, "{}", secs),
335        }
336    }
337}
338
339pub fn custom_cli(args: &mut Vec<String>) -> (Option<usize>, Vec<&String>) {
340    // If the first argument after the binary name is "e", remove it.
341    if args.len() > 1 && args[1].as_str() == "e" {
342        args.remove(1);
343    }
344    let mut run_at_a_time: Option<usize> = None;
345    // default
346    let mut filtered_args = vec![];
347    for arg in &*args {
348        if let Some(num) = arg
349            .strip_prefix("--run-")
350            .and_then(|s| s.strip_suffix("-at-a-time"))
351        {
352            if let Ok(n) = num.parse() {
353                println!("run-at-a-time: {}", n);
354                run_at_a_time = Some(n);
355            }
356            // Don't push this arg to filtered_args
357            continue;
358        }
359        filtered_args.push(arg);
360    }
361    (run_at_a_time, filtered_args)
362}