Skip to content

Commit 9618a25

Browse files
committed
refactor: qsv-skill-gen.rs to preserve positional docopt args when generating skills JSON file
1 parent e2ac69e commit 9618a25

File tree

1 file changed

+45
-10
lines changed

1 file changed

+45
-10
lines changed

src/bin/qsv-skill-gen.rs

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,37 @@ impl UsageParser {
158158
})
159159
}
160160

161+
/// Extract positional argument names in order from USAGE line
162+
fn extract_arg_order_from_usage(&self) -> Vec<String> {
163+
let mut arg_order = Vec::new();
164+
165+
// Find the main usage line (not --help line)
166+
if let Some(usage_line) = self
167+
.usage_text
168+
.lines()
169+
.skip_while(|l| !l.contains("Usage:"))
170+
.skip(1) // Skip "Usage:" line
171+
.find(|l| !l.trim().ends_with("--help") && l.contains("qsv"))
172+
{
173+
// Extract all <arg> and [<arg>] patterns in order
174+
let re = regex::Regex::new(r"(?:\[)?<([^>]+)>(?:\])?").unwrap();
175+
for cap in re.captures_iter(usage_line) {
176+
if let Some(arg_name) = cap.get(1) {
177+
arg_order.push(arg_name.as_str().to_string());
178+
}
179+
}
180+
}
181+
182+
arg_order
183+
}
184+
161185
/// Parse USAGE text using qsv-docopt Parser for robust parsing
162186
fn parse_with_docopt(&self) -> Result<(Vec<Argument>, Vec<Option_>), String> {
163187
// Parse USAGE text with docopt
164188
let parser =
165189
Parser::new(&self.usage_text).map_err(|e| format!("Docopt parsing failed: {e}"))?;
166190

167-
let mut args = Vec::new();
191+
let mut args_map = std::collections::HashMap::new();
168192
let mut options = Vec::new();
169193

170194
// Also parse manually to get descriptions
@@ -304,13 +328,16 @@ impl UsageParser {
304328

305329
let arg_type = self.infer_argument_type(&arg_name, &description);
306330

307-
args.push(Argument {
308-
name: arg_name.clone(),
309-
arg_type,
310-
required: !opts.arg.has_default(), // If it has a default, it's optional
311-
description,
312-
examples: Vec::new(),
313-
});
331+
args_map.insert(
332+
arg_name.clone(),
333+
Argument {
334+
name: arg_name.clone(),
335+
arg_type,
336+
required: !opts.arg.has_default(), // If it has a default, it's optional
337+
description,
338+
examples: Vec::new(),
339+
},
340+
);
314341
},
315342
Atom::Command(_) => {
316343
// Skip commands - we're only interested in args/options
@@ -319,8 +346,16 @@ impl UsageParser {
319346
}
320347
}
321348

322-
// Sort for consistent output
323-
args.sort_by(|a, b| a.name.cmp(&b.name));
349+
// Reorder args based on their appearance in the USAGE line
350+
let arg_order = self.extract_arg_order_from_usage();
351+
let mut args = Vec::new();
352+
for arg_name in arg_order {
353+
if let Some(arg) = args_map.remove(&arg_name) {
354+
args.push(arg);
355+
}
356+
}
357+
358+
// Sort options for consistent output
324359
options.sort_by(|a, b| a.flag.cmp(&b.flag));
325360

326361
Ok((args, options))

0 commit comments

Comments
 (0)