trueno_ptx_debug/cli.rs
1//! Declarative CLI definition for the `aprender-ptx-debug` binary.
2//!
3//! The parser lives in the library rather than in `src/bin/main.rs` so that
4//! integration tests can exercise it directly, matching the house pattern used
5//! by the other CLI crates in this workspace.
6//!
7//! Hand-rolled `match args[1]` dispatch is banned here: unknown flags fall
8//! through catch-all arms, a valued flag given without a value gets discarded,
9//! and an unparseable value degrades into a default instead of an error. clap
10//! derive makes each of those a hard parse failure.
11
12use clap::error::ErrorKind;
13use clap::{Args, CommandFactory, Parser, Subcommand};
14
15/// Trailing help text, preserved verbatim from the original usage banner.
16const AFTER_HELP: &str = "EXIT CODES:
17 0 - Analysis passed (score >= 90)
18 1 - Analysis passed with warnings (score 70-89)
19 2 - Analysis failed (score < 70)
20 3 - Critical bugs detected
21 10 - Parse error
22 11 - I/O error
23
24EXAMPLES:
25 aprender-ptx-debug analyze kernel.ptx --falsify
26 aprender-ptx-debug analyze kernel.ptx --min-score 90 --html report.html
27 aprender-ptx-debug gen-fkr kernel.ptx -o tests/kernel_fkr.rs";
28
29/// Top-level command line for `aprender-ptx-debug`.
30#[derive(Debug, Parser)]
31#[command(
32 name = "aprender-ptx-debug",
33 about = "Pure Rust PTX debugging and static analysis tool",
34 version,
35 subcommand_required = true,
36 arg_required_else_help = true,
37 after_help = AFTER_HELP
38)]
39pub struct Cli {
40 /// Subcommand to execute.
41 #[command(subcommand)]
42 pub command: Command,
43}
44
45/// Available subcommands.
46#[derive(Debug, Subcommand)]
47pub enum Command {
48 /// Analyze PTX file for bugs and issues
49 Analyze(AnalyzeArgs),
50
51 /// Generate FKR tests for jugar-probar
52 #[command(name = "gen-fkr")]
53 GenFkr(GenFkrArgs),
54
55 /// Show version information
56 Version,
57}
58
59/// Arguments for the `analyze` subcommand.
60#[derive(Debug, Args)]
61pub struct AnalyzeArgs {
62 /// PTX file to analyze
63 #[arg(value_name = "FILE")]
64 pub file: String,
65
66 /// Run full 100-point falsification framework.
67 ///
68 /// The framework is always evaluated by `analyze`, so this flag is accepted
69 /// for backwards compatibility and does not currently change the output.
70 #[arg(long)]
71 pub falsify: bool,
72
73 /// Fail if score < N
74 #[arg(long = "min-score", value_name = "N", default_value_t = 70.0)]
75 pub min_score: f64,
76
77 /// Write HTML report to file
78 #[arg(long, value_name = "FILE")]
79 pub html: Option<String>,
80
81 /// Output JSON format
82 #[arg(long)]
83 pub json: bool,
84}
85
86/// Arguments for the `gen-fkr` subcommand.
87#[derive(Debug, Args)]
88pub struct GenFkrArgs {
89 /// PTX file to generate tests from
90 #[arg(value_name = "FILE")]
91 pub file: String,
92
93 /// Output file (default: stdout)
94 #[arg(short = 'o', value_name = "FILE")]
95 pub output: Option<String>,
96}
97
98/// Render the version string used by both `--version` and the `version`
99/// subcommand, so the two surfaces cannot drift apart.
100#[must_use]
101pub fn version_string() -> String {
102 Cli::command().render_version()
103}
104
105/// Map a clap parse failure onto the process exit code.
106///
107/// `--help` and `--version` are reported by clap as errors but are successful
108/// invocations. Every other parse failure exits 1, preserving the exit status
109/// the hand-rolled parser used for an unknown command, a missing argument, or a
110/// bad option value.
111#[must_use]
112pub fn exit_code_for_parse_error(err: &clap::Error) -> i32 {
113 match err.kind() {
114 ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => 0,
115 _ => 1,
116 }
117}