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}