-
-
Notifications
You must be signed in to change notification settings - Fork 45
feat: add default_subcommand and restart_token for naked task completions #410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
abb913b
f4dae64
8732b5f
43b9ad5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -236,6 +236,22 @@ pub fn parse_partial(spec: &Spec, input: &[String]) -> Result<ParseOutput, miett | |
| } | ||
| } else { | ||
| // Found a word that's not a flag or subcommand | ||
| // Check if we should use the default_subcommand | ||
| if let Some(default_name) = &spec.default_subcommand { | ||
| if let Some(subcommand) = out.cmd.find_subcommand(default_name) { | ||
| let mut subcommand = subcommand.clone(); | ||
| // Pass prefix words (global flags before this) to mount | ||
| subcommand.mount(&prefix_words)?; | ||
| out.available_flags.retain(|_, f| f.global); | ||
| out.available_flags.extend(gather_flags(&subcommand)); | ||
| out.cmds.push(subcommand.clone()); | ||
| out.cmd = subcommand.clone(); | ||
| prefix_words.clear(); | ||
| // Don't remove the current word - it's an argument to the default subcommand | ||
| // Don't increment idx - let Phase 2 handle this word as a positional arg | ||
| break; | ||
| } | ||
| } | ||
| // This could be a positional argument, so stop subcommand search | ||
| break; | ||
| } | ||
|
|
@@ -252,6 +268,18 @@ pub fn parse_partial(spec: &Spec, input: &[String]) -> Result<ParseOutput, miett | |
| while !input.is_empty() { | ||
| let mut w = input.pop_front().unwrap(); | ||
|
|
||
| // Check for restart_token - resets argument parsing for multiple command invocations | ||
| // e.g., `mise run lint ::: test ::: check` with restart_token=":::" | ||
| if let Some(ref restart_token) = out.cmd.restart_token { | ||
| if w == *restart_token { | ||
| // Reset argument parsing state | ||
| out.args.clear(); | ||
| next_arg = out.cmd.args.first(); | ||
| // Keep flags and continue parsing | ||
| continue; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. restart_token doesn't reset enable_flags after
|
||
| } | ||
|
|
||
| if w == "--" { | ||
| enable_flags = false; | ||
| continue; | ||
|
|
@@ -987,4 +1015,140 @@ mod tests { | |
| _ => panic!("Expected String, got {:?}", value), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_default_subcommand() { | ||
| // Test that default_subcommand routes to the specified subcommand | ||
| let run_cmd = SpecCommand::builder() | ||
| .name("run") | ||
| .arg(SpecArg::builder().name("task").build()) | ||
| .build(); | ||
| let mut cmd = SpecCommand::builder().name("test").build(); | ||
| cmd.subcommands.insert("run".to_string(), run_cmd); | ||
|
|
||
| let spec = Spec { | ||
| name: "test".to_string(), | ||
| bin: "test".to_string(), | ||
| cmd, | ||
| default_subcommand: Some("run".to_string()), | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| // "test mytask" should be parsed as if it were "test run mytask" | ||
| let input = vec!["test".to_string(), "mytask".to_string()]; | ||
| let parsed = parse(&spec, &input).unwrap(); | ||
|
|
||
| // Should have two commands: root and "run" | ||
| assert_eq!(parsed.cmds.len(), 2); | ||
| assert_eq!(parsed.cmds[1].name, "run"); | ||
|
|
||
| // Should have parsed the task argument | ||
| assert_eq!(parsed.args.len(), 1); | ||
| let arg = parsed.args.keys().next().unwrap(); | ||
| assert_eq!(arg.name, "task"); | ||
| let value = parsed.args.values().next().unwrap(); | ||
| assert_eq!(value.to_string(), "mytask"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_default_subcommand_explicit_still_works() { | ||
| // Test that explicit subcommand takes precedence | ||
| let run_cmd = SpecCommand::builder() | ||
| .name("run") | ||
| .arg(SpecArg::builder().name("task").build()) | ||
| .build(); | ||
| let other_cmd = SpecCommand::builder() | ||
| .name("other") | ||
| .arg(SpecArg::builder().name("other_arg").build()) | ||
| .build(); | ||
| let mut cmd = SpecCommand::builder().name("test").build(); | ||
| cmd.subcommands.insert("run".to_string(), run_cmd); | ||
| cmd.subcommands.insert("other".to_string(), other_cmd); | ||
|
|
||
| let spec = Spec { | ||
| name: "test".to_string(), | ||
| bin: "test".to_string(), | ||
| cmd, | ||
| default_subcommand: Some("run".to_string()), | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| // "test other foo" should use "other" subcommand, not default | ||
| let input = vec!["test".to_string(), "other".to_string(), "foo".to_string()]; | ||
| let parsed = parse(&spec, &input).unwrap(); | ||
|
|
||
| // Should have used "other" subcommand | ||
| assert_eq!(parsed.cmds.len(), 2); | ||
| assert_eq!(parsed.cmds[1].name, "other"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_restart_token() { | ||
| // Test that restart_token resets argument parsing | ||
| let run_cmd = SpecCommand::builder() | ||
| .name("run") | ||
| .arg(SpecArg::builder().name("task").build()) | ||
| .restart_token(":::".to_string()) | ||
| .build(); | ||
| let mut cmd = SpecCommand::builder().name("test").build(); | ||
| cmd.subcommands.insert("run".to_string(), run_cmd); | ||
|
|
||
| let spec = Spec { | ||
| name: "test".to_string(), | ||
| bin: "test".to_string(), | ||
| cmd, | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| // "test run task1 ::: task2" - should end up with task2 as the arg | ||
| let input = vec![ | ||
| "test".to_string(), | ||
| "run".to_string(), | ||
| "task1".to_string(), | ||
| ":::".to_string(), | ||
| "task2".to_string(), | ||
| ]; | ||
| let parsed = parse(&spec, &input).unwrap(); | ||
|
|
||
| // After restart, args were cleared and task2 was parsed | ||
| assert_eq!(parsed.args.len(), 1); | ||
| let value = parsed.args.values().next().unwrap(); | ||
| assert_eq!(value.to_string(), "task2"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_restart_token_multiple() { | ||
| // Test multiple restart tokens | ||
| let run_cmd = SpecCommand::builder() | ||
| .name("run") | ||
| .arg(SpecArg::builder().name("task").build()) | ||
| .restart_token(":::".to_string()) | ||
| .build(); | ||
| let mut cmd = SpecCommand::builder().name("test").build(); | ||
| cmd.subcommands.insert("run".to_string(), run_cmd); | ||
|
|
||
| let spec = Spec { | ||
| name: "test".to_string(), | ||
| bin: "test".to_string(), | ||
| cmd, | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| // "test run task1 ::: task2 ::: task3" - should end up with task3 as the arg | ||
| let input = vec![ | ||
| "test".to_string(), | ||
| "run".to_string(), | ||
| "task1".to_string(), | ||
| ":::".to_string(), | ||
| "task2".to_string(), | ||
| ":::".to_string(), | ||
| "task3".to_string(), | ||
| ]; | ||
| let parsed = parse(&spec, &input).unwrap(); | ||
|
|
||
| // After multiple restarts, args were cleared and task3 was parsed | ||
| assert_eq!(parsed.args.len(), 1); | ||
| let value = parsed.args.values().next().unwrap(); | ||
| assert_eq!(value.to_string(), "task3"); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.