Skip to main content

plexus_devtools/
cli.rs

1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum ToolMode {
3    Devtools,
4    Inspect,
5}
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Command {
9    Inspect,
10    Validate,
11    Diff,
12    Lint,
13    Viz,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct ParsedCli {
18    pub mode: ToolMode,
19    pub command: Command,
20    pub command_args: Vec<String>,
21}
22
23pub fn parse_cli(args: &[String]) -> Result<ParsedCli, String> {
24    let mode = tool_mode(args);
25    let bin_name = binary_name(args);
26    let (command, command_idx) = match args.get(1).map(|s| s.as_str()) {
27        Some("inspect") | Some("explain") => (Command::Inspect, 1),
28        Some("validate") => (Command::Validate, 1),
29        Some("diff") => (Command::Diff, 1),
30        Some("lint") => (Command::Lint, 1),
31        Some("viz") => (Command::Viz, 1),
32        Some(command) if !command.starts_with("--") => return Err(usage(bin_name)),
33        Some(_) | None if mode == ToolMode::Inspect => (Command::Inspect, 0),
34        _ => return Err(usage(bin_name)),
35    };
36
37    Ok(ParsedCli {
38        mode,
39        command,
40        command_args: args[command_idx + 1..].to_vec(),
41    })
42}
43
44pub fn usage(bin_name: &str) -> String {
45    if normalized_bin_name(bin_name) == "plexus-inspect" {
46        "usage:\n  plexus-inspect [inspect] --in <plan.plexus> [--format text|json|dot] [--out <report.txt|report.json|plan.dot>]\n  plexus-inspect validate --in <plan.plexus> [--caps-json <caps.json>] [--format text|json]\n  plexus-inspect diff --old <old.plexus> --new <new.plexus> [--format text|json]\n  plexus-inspect lint --in <plan.plexus> [--format text|json]\n  plexus-inspect viz --in <plan.plexus> [--format dot|svg] [--out <plan.dot|plan.svg>]"
47            .to_string()
48    } else {
49        "usage:\n  plexus-devtools explain --in <plan.plexus> [--format text|json|dot] [--out <report.txt|report.json|plan.dot>]\n  plexus-devtools inspect --in <plan.plexus> [--format text|json|dot] [--out <report.txt|report.json|plan.dot>]\n  plexus-devtools validate --in <plan.plexus> [--caps-json <caps.json>] [--format text|json]\n  plexus-devtools diff --old <old.plexus> --new <new.plexus> [--format text|json]\n  plexus-devtools lint --in <plan.plexus> [--format text|json]\n  plexus-devtools viz --in <plan.plexus> [--format dot|svg] [--out <plan.dot|plan.svg>]"
50            .to_string()
51    }
52}
53
54pub fn binary_name(args: &[String]) -> &str {
55    args.first()
56        .and_then(|arg| std::path::Path::new(arg).file_name())
57        .and_then(|name| name.to_str())
58        .unwrap_or("plexus-devtools")
59}
60
61fn tool_mode(args: &[String]) -> ToolMode {
62    if normalized_bin_name(binary_name(args)) == "plexus-inspect" {
63        ToolMode::Inspect
64    } else {
65        ToolMode::Devtools
66    }
67}
68
69fn normalized_bin_name(bin_name: &str) -> &str {
70    bin_name.rsplit('/').next().unwrap_or(bin_name)
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn inspect_binary_defaults_to_inspect_command() {
79        let args = vec![
80            "target/debug/plexus-inspect".to_string(),
81            "--in".to_string(),
82            "plan.plexus".to_string(),
83        ];
84        let parsed = parse_cli(&args).expect("parse inspect cli");
85        assert_eq!(parsed.mode, ToolMode::Inspect);
86        assert_eq!(parsed.command, Command::Inspect);
87        assert_eq!(parsed.command_args, vec!["--in", "plan.plexus"]);
88    }
89
90    #[test]
91    fn devtools_binary_accepts_validate_command() {
92        let args = vec![
93            "target/debug/plexus-devtools".to_string(),
94            "validate".to_string(),
95            "--in".to_string(),
96            "plan.plexus".to_string(),
97        ];
98        let parsed = parse_cli(&args).expect("parse devtools cli");
99        assert_eq!(parsed.mode, ToolMode::Devtools);
100        assert_eq!(parsed.command, Command::Validate);
101        assert_eq!(parsed.command_args, vec!["--in", "plan.plexus"]);
102    }
103
104    #[test]
105    fn unknown_command_returns_usage() {
106        let args = vec![
107            "target/debug/plexus-inspect".to_string(),
108            "unknown".to_string(),
109        ];
110        let err = parse_cli(&args).expect_err("unknown command should fail");
111        assert!(err.contains("plexus-inspect"));
112        assert!(err.contains("validate"));
113    }
114}