Skip to main content

start_command/
args_parser_queries.rs

1//! Query/control option parsing for the start-command wrapper.
2//!
3//! These options address an execution that already exists instead of starting
4//! a new one: `--status`, `--list [--running]`, `--upload-log`, `--stop`,
5//! `--terminate`, `--attach [--read-only]`, `--resume [-- <command>]`,
6//! `--resume-all` and `--cleanup`. They are parsed here so `args_parser` stays
7//! focused on the options that shape a new execution (issue #162).
8
9use crate::args_parser::{WrapperOptions, VALID_OUTPUT_FORMATS};
10
11/// Setter applied to the parsed options when an identifier option matches.
12type IdentifierSetter = fn(&mut WrapperOptions, String);
13
14/// Setter applied to the parsed options when a flag option matches.
15type FlagSetter = fn(&mut WrapperOptions);
16
17/// Options that take a UUID or session name argument.
18const IDENTIFIER_OPTIONS: [(&str, IdentifierSetter); 6] = [
19    ("--status", |o, v| o.status = Some(v)),
20    ("--upload-log", |o, v| o.upload_log = Some(v)),
21    ("--stop", |o, v| o.stop = Some(v)),
22    ("--terminate", |o, v| o.terminate = Some(v)),
23    ("--attach", |o, v| o.attach = Some(v)),
24    ("--resume", |o, v| o.resume = Some(v)),
25];
26
27/// Options that are plain flags.
28const FLAG_OPTIONS: [(&str, FlagSetter); 6] = [
29    ("--list", |o| o.list = true),
30    ("--running", |o| o.running = true),
31    ("--read-only", |o| o.read_only = true),
32    ("--resume-all", |o| o.resume_all = true),
33    ("--cleanup", |o| o.cleanup = true),
34    ("--cleanup-dry-run", |o| {
35        o.cleanup = true;
36        o.cleanup_dry_run = true;
37    }),
38];
39
40/// Query modes that may be combined with `--output-format`.
41const OUTPUT_FORMAT_MODES: [&str; 4] = ["--status", "--list", "--resume", "--resume-all"];
42
43/// Parse one query/control option.
44///
45/// Returns the number of arguments consumed, or `Ok(0)` when `args[index]` is
46/// not a query/control option.
47pub fn parse_query_option(
48    args: &[String],
49    index: usize,
50    options: &mut WrapperOptions,
51) -> Result<usize, String> {
52    let arg = &args[index];
53
54    for (name, apply) in FLAG_OPTIONS {
55        if arg == name {
56            apply(options);
57            return Ok(1);
58        }
59    }
60
61    for (name, apply) in IDENTIFIER_OPTIONS {
62        if arg == name {
63            if index + 1 < args.len() && !args[index + 1].starts_with('-') {
64                apply(options, args[index + 1].clone());
65                return Ok(2);
66            }
67            return Err(format!(
68                "Option {} requires a UUID or session name argument",
69                arg
70            ));
71        }
72
73        let prefix = format!("{}=", name);
74        if let Some(value) = arg.strip_prefix(&prefix) {
75            if value.is_empty() {
76                return Err(format!(
77                    "Option {} requires a UUID or session name argument",
78                    name
79                ));
80            }
81            apply(options, value.to_string());
82            return Ok(1);
83        }
84    }
85
86    if arg == "--output-format" {
87        if index + 1 < args.len() && !args[index + 1].starts_with('-') {
88            options.output_format = Some(args[index + 1].to_lowercase());
89            return Ok(2);
90        }
91        return Err(format!("Option {} requires a format argument", arg));
92    }
93
94    if arg.starts_with("--output-format=") {
95        options.output_format = Some(arg.split('=').nth(1).unwrap_or("").to_lowercase());
96        return Ok(1);
97    }
98
99    Ok(0)
100}
101
102/// List the query/control modes selected by these options.
103pub fn active_query_modes(options: &WrapperOptions) -> Vec<&'static str> {
104    let mut modes = Vec::new();
105    if options.status.is_some() {
106        modes.push("--status");
107    }
108    if options.list {
109        modes.push("--list");
110    }
111    if options.upload_log.is_some() {
112        modes.push("--upload-log");
113    }
114    if options.stop.is_some() {
115        modes.push("--stop");
116    }
117    if options.terminate.is_some() {
118        modes.push("--terminate");
119    }
120    if options.attach.is_some() {
121        modes.push("--attach");
122    }
123    if options.resume.is_some() {
124        modes.push("--resume");
125    }
126    if options.resume_all {
127        modes.push("--resume-all");
128    }
129    if options.cleanup {
130        modes.push("--cleanup");
131    }
132    modes
133}
134
135/// True when any query/control mode is active.
136pub fn has_query_mode(options: &WrapperOptions) -> bool {
137    !active_query_modes(options).is_empty()
138}
139
140/// Validate query/control option combinations.
141pub fn validate_query_options(options: &WrapperOptions) -> Result<(), String> {
142    if let Some(ref format) = options.output_format {
143        if !VALID_OUTPUT_FORMATS.contains(&format.as_str()) {
144            return Err(format!(
145                "Invalid output format: \"{}\". Valid options are: {}",
146                format,
147                VALID_OUTPUT_FORMATS.join(", ")
148            ));
149        }
150    }
151
152    let modes = active_query_modes(options);
153
154    if modes.len() > 1 {
155        return Err(
156            "Cannot combine --status, --list, --upload-log, --stop, --terminate, --attach, --resume, --resume-all, or --cleanup in the same invocation"
157                .to_string(),
158        );
159    }
160
161    if options.output_format.is_some()
162        && !modes.iter().any(|mode| OUTPUT_FORMAT_MODES.contains(mode))
163    {
164        return Err(
165            "--output-format option is only valid with --status, --list, --resume, or --resume-all"
166                .to_string(),
167        );
168    }
169
170    if options.running && !options.list {
171        return Err("--running option is only valid with --list".to_string());
172    }
173
174    if options.read_only && options.attach.is_none() {
175        return Err("--read-only option is only valid with --attach".to_string());
176    }
177
178    Ok(())
179}