first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
use crate::completer::{CommandExitStatus, CompletionContext, TopLevelCommandCaseSensitivity};
|
||||
use crate::parsers::SignatureAtTokenIndex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use itertools::Itertools;
|
||||
use memo_map::MemoMap;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use warp_command_signatures::{Argument, DynamicCompletionData, IsArgumentOptional, Signature};
|
||||
|
||||
use crate::completer::{CommandExitStatus, CompletionContext, TopLevelCommandCaseSensitivity};
|
||||
use crate::parsers::SignatureAtTokenIndex;
|
||||
|
||||
pub enum SignatureResult<'a> {
|
||||
/// Successfully parsed the signature. We are returning the signature at the token to complete on.
|
||||
Success(SignatureAtTokenIndex<'a>),
|
||||
@@ -514,5 +514,5 @@ fn should_complete_on_subcmd(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "registry_test.rs"]
|
||||
#[path = "registry_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::completer::testing::FakeCompletionContext;
|
||||
use crate::completer::CompletionContext;
|
||||
use crate::completer::TopLevelCommandCaseSensitivity;
|
||||
use crate::completer::{CompletionContext, TopLevelCommandCaseSensitivity};
|
||||
use crate::signatures::registry::SignatureResult;
|
||||
use crate::signatures::testing::{create_test_command_registry, test_signature};
|
||||
|
||||
@@ -193,7 +192,7 @@ fn test_alias_expansion_path_skips_flag_with_value_before_subcommand() {
|
||||
let ctx = FakeCompletionContext::new(registry).with_case_sensitivity();
|
||||
|
||||
let result =
|
||||
galaxyui::r#async::block_on(ctx.command_registry().signature_with_alias_expansion(
|
||||
galaxyui_core::r#async::block_on(ctx.command_registry().signature_with_alias_expansion(
|
||||
&["kubectl", "-n", "default", "get"],
|
||||
true,
|
||||
&ctx,
|
||||
@@ -216,7 +215,7 @@ fn test_alias_expansion_path_skips_multiple_flags_before_subcommand() {
|
||||
let ctx = FakeCompletionContext::new(registry).with_case_sensitivity();
|
||||
|
||||
let result =
|
||||
galaxyui::r#async::block_on(ctx.command_registry().signature_with_alias_expansion(
|
||||
galaxyui_core::r#async::block_on(ctx.command_registry().signature_with_alias_expansion(
|
||||
&[
|
||||
"kubectl",
|
||||
"--context",
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
use crate::completer::testing::FakeCompletionContext;
|
||||
use crate::completer::{CompletionContext, TopLevelCommandCaseSensitivity};
|
||||
use crate::signatures::registry::SignatureResult;
|
||||
use crate::signatures::testing::{create_test_command_registry, test_signature};
|
||||
|
||||
#[test]
|
||||
fn test_find_command_from_a_top_level_signature() {
|
||||
let bundle = warp_command_signatures::signature_by_name("bundle")
|
||||
.expect("global command signatures should include 'bundle'");
|
||||
|
||||
let registry = create_test_command_registry([bundle.clone(), test_signature()]);
|
||||
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"bundle exec ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.map(|c| c.signature);
|
||||
let signature = bundle;
|
||||
let exec_subcommand = signature
|
||||
.subcommands()
|
||||
.iter()
|
||||
.find(|sig| sig.name() == "exec");
|
||||
assert_eq!(found_signature, exec_subcommand);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_subcommand_signature_with_flags() {
|
||||
let kubectl = warp_command_signatures::signature_by_name("kubectl")
|
||||
.expect("global command signatures should include 'kubectl'");
|
||||
|
||||
let registry = create_test_command_registry([kubectl.clone(), test_signature()]);
|
||||
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"kubectl -n default get ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.expect("kubectl signature from line should exist");
|
||||
// Should parse this as entering the "get" subcommand even though there's a top level -n default flag.
|
||||
assert_eq!(found_signature.signature.name(), "get");
|
||||
assert_eq!(found_signature.token_index, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_option_by_name_exact_match_does_not_match_substring() {
|
||||
// Regression test: "-n" should match the "-n"/"--namespace" option, NOT
|
||||
// "--no-headers" (which contains the substring "-n"). The fix uses exact
|
||||
// equality instead of `contains`.
|
||||
let kubectl = warp_command_signatures::signature_by_name("kubectl")
|
||||
.expect("global command signatures should include 'kubectl'");
|
||||
|
||||
let registry = create_test_command_registry([kubectl, test_signature()]);
|
||||
|
||||
// "kubectl -n default api-resources " should resolve to "api-resources",
|
||||
// which has a "--no-headers" option. If "-n" incorrectly matched
|
||||
// "--no-headers" via substring, the parser would skip "default" as the
|
||||
// flag argument and never reach the "api-resources" subcommand.
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"kubectl -n default api-resources ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.expect("kubectl signature from line should exist");
|
||||
assert_eq!(found_signature.signature.name(), "api-resources");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flag_arg_consumes_token_matching_subcommand_name() {
|
||||
// When a recognized flag takes a required argument, the next token should be consumed
|
||||
// as that flag's argument even if it happens to match a subcommand name.
|
||||
// Here, --not-long takes 1 argument, so "one" is consumed as that argument
|
||||
// rather than being resolved as the "one" subcommand.
|
||||
let registry = create_test_command_registry([test_signature()]);
|
||||
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"test --not-long one foo ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.expect("test signature from line should exist");
|
||||
assert_eq!(found_signature.signature.name(), "test");
|
||||
assert_eq!(found_signature.token_index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_switches_before_subcommand() {
|
||||
// Multiple switch flags (no arguments) before a subcommand should all be
|
||||
// skipped, allowing the subcommand to be discovered.
|
||||
let registry = create_test_command_registry([test_signature()]);
|
||||
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"test -r -V one foo ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.expect("test signature from line should exist");
|
||||
assert_eq!(found_signature.signature.name(), "one");
|
||||
assert_eq!(found_signature.token_index, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unrecognized_flag_skipped_before_subcommand() {
|
||||
// Unrecognized flags (tokens starting with '-' not in the spec) should be
|
||||
// skipped so the parser can still discover subcommands after them.
|
||||
let registry = create_test_command_registry([test_signature()]);
|
||||
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"test --unknown-flag one foo ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.expect("test signature from line should exist");
|
||||
assert_eq!(found_signature.signature.name(), "one");
|
||||
assert_eq!(found_signature.token_index, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flag_with_missing_value_at_end_of_input() {
|
||||
// When a flag that takes a required argument appears at the end of input
|
||||
// with no value provided, the resolved signature stays on the parent command.
|
||||
let registry = create_test_command_registry([test_signature()]);
|
||||
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"test --not-long ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.expect("test signature from line should exist");
|
||||
assert_eq!(found_signature.signature.name(), "test");
|
||||
assert_eq!(found_signature.token_index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_flags_with_values_before_subcommand() {
|
||||
// Multiple valued flags before a subcommand should all be skipped,
|
||||
// allowing the subcommand to be discovered.
|
||||
let registry = create_test_command_registry([test_signature()]);
|
||||
|
||||
// --not-long takes 1 required arg ("val"), -r is a switch.
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"test --not-long val -r one foo ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.expect("test signature from line should exist");
|
||||
assert_eq!(found_signature.signature.name(), "one");
|
||||
assert_eq!(found_signature.token_index, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_only_flags_no_subcommand() {
|
||||
// When only flags appear after the command with no following subcommand,
|
||||
// the parent command should be returned.
|
||||
let registry = create_test_command_registry([test_signature()]);
|
||||
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"test --not-long val ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.expect("test signature from line should exist");
|
||||
assert_eq!(found_signature.signature.name(), "test");
|
||||
assert_eq!(found_signature.token_index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optional_flag_arg_does_not_consume_subcommand() {
|
||||
// --required-and-optional-args has 1 required arg + 1 optional arg.
|
||||
// The parser should only skip the required arg, so "one" is found as a
|
||||
// subcommand rather than being consumed as the optional arg.
|
||||
let registry = create_test_command_registry([test_signature()]);
|
||||
|
||||
let found_signature = registry
|
||||
.signature_from_line(
|
||||
"test --required-and-optional-args val one foo ",
|
||||
TopLevelCommandCaseSensitivity::CaseSensitive,
|
||||
)
|
||||
.expect("test signature from line should exist");
|
||||
assert_eq!(found_signature.signature.name(), "one");
|
||||
assert_eq!(found_signature.token_index, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alias_expansion_path_skips_flag_with_value_before_subcommand() {
|
||||
// Exercises signature_with_alias_expansion (not just signature_from_tokens)
|
||||
// to ensure the alias-expansion code path also skips flags before subcommands.
|
||||
let kubectl = warp_command_signatures::signature_by_name("kubectl")
|
||||
.expect("global command signatures should include 'kubectl'");
|
||||
|
||||
let registry = create_test_command_registry([kubectl]);
|
||||
let ctx = FakeCompletionContext::new(registry).with_case_sensitivity();
|
||||
|
||||
let result =
|
||||
galaxyui_core::r#async::block_on(ctx.command_registry().signature_with_alias_expansion(
|
||||
&["kubectl", "-n", "default", "get"],
|
||||
true,
|
||||
&ctx,
|
||||
));
|
||||
let SignatureResult::Success(found_signature) = result else {
|
||||
panic!("expected SignatureResult::Success");
|
||||
};
|
||||
assert_eq!(found_signature.signature.name(), "get");
|
||||
assert_eq!(found_signature.token_index, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alias_expansion_path_skips_multiple_flags_before_subcommand() {
|
||||
// Exercises signature_with_alias_expansion with multiple flags (valued and
|
||||
// switch) placed before the subcommand.
|
||||
let kubectl = warp_command_signatures::signature_by_name("kubectl")
|
||||
.expect("global command signatures should include 'kubectl'");
|
||||
|
||||
let registry = create_test_command_registry([kubectl]);
|
||||
let ctx = FakeCompletionContext::new(registry).with_case_sensitivity();
|
||||
|
||||
let result =
|
||||
galaxyui_core::r#async::block_on(ctx.command_registry().signature_with_alias_expansion(
|
||||
&[
|
||||
"kubectl",
|
||||
"--context",
|
||||
"staging-cluster",
|
||||
"-n",
|
||||
"project1",
|
||||
"get",
|
||||
],
|
||||
true,
|
||||
&ctx,
|
||||
));
|
||||
let SignatureResult::Success(found_signature) = result else {
|
||||
panic!("expected SignatureResult::Success");
|
||||
};
|
||||
assert_eq!(found_signature.signature.name(), "get");
|
||||
assert_eq!(found_signature.token_index, 5);
|
||||
}
|
||||
@@ -5,13 +5,12 @@
|
||||
//! coverage can run with the "v2" Cargo feature both enabled and disabled.
|
||||
use galaxy_js::TypedJsFunctionRef;
|
||||
|
||||
use super::{TEST_GENERATOR_1_COMMAND, TEST_GENERATOR_2_COMMAND};
|
||||
use crate::signatures::{
|
||||
Argument, ArgumentValue, Arity, Command, CommandSignature, GeneratorFn, GeneratorResults,
|
||||
GeneratorScript, Opt, Priority, Suggestion, TemplateType,
|
||||
};
|
||||
|
||||
use super::{TEST_GENERATOR_1_COMMAND, TEST_GENERATOR_2_COMMAND};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub(crate) static ref TEST_GENERATOR_1_JS_FUNCTION: TypedJsFunctionRef<String, GeneratorResults> = TypedJsFunctionRef::<String, GeneratorResults>::new_for_test();
|
||||
pub(crate) static ref TEST_GENERATOR_2_JS_FUNCTION: TypedJsFunctionRef<String, GeneratorResults> = TypedJsFunctionRef::<String, GeneratorResults>::new_for_test();
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
//! `galaxy_completer::signatures::CommandSignature`s, as well as `IntoWarpJs` implementations for
|
||||
//! Rust structs that may be passed to JS functions defined on the Command Signature (e.g.
|
||||
//! `GeneratorCompletionContext`).
|
||||
use galaxy_js::{
|
||||
util::{get_one_or_more_optional, get_one_or_more_required, get_optional, get_required},
|
||||
FromWarpJs, IntoWarpJs, JsFunctionRegistry,
|
||||
};
|
||||
use rquickjs::{FromJs, Function, Object, Value};
|
||||
use galaxy_js::util::{
|
||||
get_one_or_more_optional, get_one_or_more_required, get_optional, get_required,
|
||||
};
|
||||
use galaxy_js::{FromWarpJs, IntoWarpJs, JsFunctionRegistry};
|
||||
|
||||
use super::{
|
||||
Argument, ArgumentValue, Command, CommandSignature, GeneratorCompletionContext, GeneratorFn,
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
//! untokenized input.
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::{registry::CommandRegistry, Command};
|
||||
use super::registry::CommandRegistry;
|
||||
use super::Command;
|
||||
|
||||
/// Returns the highest-precedence matching `Command` signature object for the given `input`, if
|
||||
/// any, along with the index of the token in `input` matched to the returned `Command`.
|
||||
@@ -144,5 +145,5 @@ fn deepest_matching_subcommand_signature<'a>(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "lookup_test.rs"]
|
||||
#[path = "lookup_tests.rs"]
|
||||
mod test;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::signatures::{Argument, Command, CommandSignature, Opt};
|
||||
|
||||
use super::*;
|
||||
use crate::signatures::{Argument, Command, CommandSignature, Opt};
|
||||
|
||||
/// Creates a `test_command` signature with a `test_subcommand` subcommand
|
||||
/// and the given options on the root command.
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
use super::*;
|
||||
use crate::signatures::{Argument, Command, CommandSignature, Opt};
|
||||
|
||||
/// Creates a `test_command` signature with a `test_subcommand` subcommand
|
||||
/// and the given options on the root command.
|
||||
fn test_command_signature_with_options(options: Vec<Opt>) -> CommandSignature {
|
||||
CommandSignature {
|
||||
command: Command {
|
||||
name: "test_command".to_owned(),
|
||||
subcommands: vec![Command {
|
||||
name: "test_subcommand".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
options,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn valued_option() -> Opt {
|
||||
Opt {
|
||||
name: vec!["-n".to_owned(), "--name".to_owned()],
|
||||
arguments: vec![Argument {
|
||||
name: "value".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_for_input_on_root_command() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(CommandSignature {
|
||||
command: Command {
|
||||
name: "test_command".to_owned(),
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
|
||||
let (found_signature, index) = get_matching_signature_for_input("test_command ", ®istry)
|
||||
.expect("Signature should exist");
|
||||
assert_eq!(found_signature.name, "test_command");
|
||||
assert_eq!(index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_for_input_on_root_command_with_argument() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(CommandSignature {
|
||||
command: Command {
|
||||
name: "test_command".to_owned(),
|
||||
subcommands: vec![Command {
|
||||
name: "test_subcommand".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
arguments: vec![Argument {
|
||||
name: "arg1".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
|
||||
let (found_signature, index) =
|
||||
get_matching_signature_for_input("test_command some_arg_value ", ®istry)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_command");
|
||||
assert_eq!(index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_for_input_on_subcommand() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(CommandSignature {
|
||||
command: Command {
|
||||
name: "test_command".to_owned(),
|
||||
subcommands: vec![Command {
|
||||
name: "test_subcommand".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
arguments: vec![Argument {
|
||||
name: "arg1".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
|
||||
let (found_signature, index) =
|
||||
get_matching_signature_for_input("test_command test_subcommand ", ®istry)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_subcommand");
|
||||
assert_eq!(index, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_for_input_on_subcommand_with_argument() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(CommandSignature {
|
||||
command: Command {
|
||||
name: "test_command".to_owned(),
|
||||
subcommands: vec![
|
||||
Command {
|
||||
name: "test_subcommand1".to_owned(),
|
||||
arguments: vec![Argument {
|
||||
name: "test_subcommand_arg".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
Command {
|
||||
name: "test_subcommand2".to_owned(),
|
||||
arguments: vec![Argument {
|
||||
name: "test_subcommand_arg".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
arguments: vec![Argument {
|
||||
name: "test_command_arg".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
|
||||
let (found_signature, index) = get_matching_signature_for_input(
|
||||
"test_command test_subcommand1 some_arg_value ",
|
||||
®istry,
|
||||
)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_subcommand1");
|
||||
assert_eq!(index, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_for_input_without_trailing_whitespace() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(CommandSignature {
|
||||
command: Command {
|
||||
name: "test_command".to_owned(),
|
||||
subcommands: vec![Command {
|
||||
name: "test_subcommand".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
arguments: vec![Argument {
|
||||
name: "arg1".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
|
||||
let (found_signature, index) =
|
||||
get_matching_signature_for_input("test_command test_subcommand", ®istry)
|
||||
.expect("Signature should be found.");
|
||||
|
||||
// The matched signature should be that of the top-level command. Because there is no trailing
|
||||
// whitespace in the input, it's assumed we're still completing on the "test_subcommand", so we
|
||||
// should still be using the top-level command signature.
|
||||
assert_eq!(found_signature.name, "test_command");
|
||||
assert_eq!(index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_for_tokenized_input() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(CommandSignature {
|
||||
command: Command {
|
||||
name: "test_command".to_owned(),
|
||||
subcommands: vec![
|
||||
Command {
|
||||
name: "test_subcommand1".to_owned(),
|
||||
arguments: vec![Argument {
|
||||
name: "test_subcommand_arg".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
Command {
|
||||
name: "test_subcommand2".to_owned(),
|
||||
arguments: vec![Argument {
|
||||
name: "test_subcommand_arg".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
arguments: vec![Argument {
|
||||
name: "test_command_arg".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
|
||||
let (found_signature, token_index) = get_matching_signature_for_tokenized_input(
|
||||
&["test_command", "test_subcommand1", "some_arg_value"],
|
||||
true,
|
||||
®istry,
|
||||
)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_subcommand1");
|
||||
assert_eq!(token_index, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_for_tokenized_input_without_trailing_whitespace() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(CommandSignature {
|
||||
command: Command {
|
||||
name: "test_command".to_owned(),
|
||||
subcommands: vec![
|
||||
Command {
|
||||
name: "test_subcommand1".to_owned(),
|
||||
arguments: vec![Argument {
|
||||
name: "test_subcommand_arg".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
Command {
|
||||
name: "test_subcommand2".to_owned(),
|
||||
arguments: vec![Argument {
|
||||
name: "test_subcommand_arg".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
arguments: vec![Argument {
|
||||
name: "test_command_arg".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
|
||||
let (found_signature, token_index) = get_matching_signature_for_tokenized_input(
|
||||
&["test_command", "test_subcommand1"],
|
||||
false,
|
||||
®istry,
|
||||
)
|
||||
.expect("Signature should be found.");
|
||||
|
||||
// The matched signature should be that of the top-level command. Because there is no trailing
|
||||
// whitespace in the input, it's assumed we're still completing on the "test_subcommand", so we
|
||||
// should still be using the top-level command signature.
|
||||
assert_eq!(found_signature.name, "test_command");
|
||||
assert_eq!(token_index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_skips_flag_with_value_before_subcommand() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(test_command_signature_with_options(vec![valued_option()]));
|
||||
|
||||
// -n takes a value, so the parser should skip "-n val" and find test_subcommand.
|
||||
let (found_signature, token_index) = get_matching_signature_for_tokenized_input(
|
||||
&["test_command", "-n", "val", "test_subcommand"],
|
||||
true,
|
||||
®istry,
|
||||
)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_subcommand");
|
||||
assert_eq!(token_index, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_skips_long_flag_with_value_before_subcommand() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(test_command_signature_with_options(vec![
|
||||
Opt {
|
||||
name: vec!["--context".to_owned()],
|
||||
arguments: vec![Argument {
|
||||
name: "context".to_owned(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
valued_option(),
|
||||
]));
|
||||
|
||||
// Two valued flags before the subcommand should both be skipped.
|
||||
let (found_signature, token_index) = get_matching_signature_for_tokenized_input(
|
||||
&[
|
||||
"test_command",
|
||||
"--context",
|
||||
"staging",
|
||||
"-n",
|
||||
"project1",
|
||||
"test_subcommand",
|
||||
],
|
||||
true,
|
||||
®istry,
|
||||
)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_subcommand");
|
||||
assert_eq!(token_index, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_skips_switch_flag_before_subcommand() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(test_command_signature_with_options(vec![Opt {
|
||||
name: vec!["--verbose".to_owned()],
|
||||
arguments: vec![],
|
||||
..Default::default()
|
||||
}]));
|
||||
|
||||
let (found_signature, token_index) = get_matching_signature_for_tokenized_input(
|
||||
&["test_command", "--verbose", "test_subcommand"],
|
||||
true,
|
||||
®istry,
|
||||
)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_subcommand");
|
||||
assert_eq!(token_index, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_flag_at_end_without_value_does_not_panic() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(test_command_signature_with_options(vec![valued_option()]));
|
||||
|
||||
// "-n" with no value should not panic.
|
||||
let (found_signature, token_index) =
|
||||
get_matching_signature_for_tokenized_input(&["test_command", "-n"], true, ®istry)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_command");
|
||||
// No subcommand found, so entry_token_index (0) is returned.
|
||||
assert_eq!(token_index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_skips_unrecognized_flag_before_subcommand() {
|
||||
// Unrecognized flags (tokens starting with '-' not in the spec) should be
|
||||
// skipped so the parser can still discover subcommands after them.
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(test_command_signature_with_options(vec![]));
|
||||
|
||||
let (found_signature, token_index) = get_matching_signature_for_tokenized_input(
|
||||
&["test_command", "--unknown-flag", "test_subcommand"],
|
||||
true,
|
||||
®istry,
|
||||
)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_subcommand");
|
||||
assert_eq!(token_index, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_flag_arg_consumes_token_matching_subcommand_name() {
|
||||
// When a recognized flag takes a required argument, the next token is
|
||||
// consumed as the flag's value even if it matches a subcommand name.
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(test_command_signature_with_options(vec![valued_option()]));
|
||||
|
||||
let (found_signature, token_index) = get_matching_signature_for_tokenized_input(
|
||||
&["test_command", "-n", "test_subcommand", "extra"],
|
||||
true,
|
||||
®istry,
|
||||
)
|
||||
.expect("Signature should be found.");
|
||||
// "test_subcommand" was consumed as -n's value, so no subcommand is found.
|
||||
assert_eq!(found_signature.name, "test_command");
|
||||
assert_eq!(token_index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_only_flags_no_subcommand() {
|
||||
// When the input consists only of flags with no following subcommand,
|
||||
// the parent command should be returned at the entry index.
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(test_command_signature_with_options(vec![valued_option()]));
|
||||
|
||||
let (found_signature, token_index) =
|
||||
get_matching_signature_for_tokenized_input(&["test_command", "-n", "val"], true, ®istry)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_command");
|
||||
assert_eq!(token_index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_matching_signature_optional_flag_arg_does_not_consume_subcommand() {
|
||||
// A flag with 1 required + 1 optional argument should only skip the
|
||||
// required arg, so the next token can still match a subcommand.
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(test_command_signature_with_options(vec![Opt {
|
||||
name: vec!["--output".to_owned()],
|
||||
arguments: vec![
|
||||
Argument {
|
||||
name: "format".to_owned(),
|
||||
..Default::default()
|
||||
},
|
||||
Argument {
|
||||
name: "extra".to_owned(),
|
||||
optional: true,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
}]));
|
||||
|
||||
// "json" is the required arg, "test_subcommand" should not be consumed as
|
||||
// the optional arg.
|
||||
let (found_signature, token_index) = get_matching_signature_for_tokenized_input(
|
||||
&["test_command", "--output", "json", "test_subcommand"],
|
||||
true,
|
||||
®istry,
|
||||
)
|
||||
.expect("Signature should be found.");
|
||||
assert_eq!(found_signature.name, "test_subcommand");
|
||||
assert_eq!(token_index, 3);
|
||||
}
|
||||
@@ -13,8 +13,6 @@ use std::cmp::Ordering;
|
||||
|
||||
pub use lookup::*;
|
||||
pub use registry::*;
|
||||
|
||||
use galaxy_js::TypedJsFunctionRef;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -227,5 +225,5 @@ impl Opt {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "signatures_test.rs"]
|
||||
#[path = "signatures_tests.rs"]
|
||||
mod test;
|
||||
|
||||
@@ -51,5 +51,5 @@ impl CommandRegistry {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "registry_test.rs"]
|
||||
#[path = "registry_tests.rs"]
|
||||
mod test;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::signatures::{Command, Priority};
|
||||
|
||||
use super::*;
|
||||
use crate::signatures::{Command, Priority};
|
||||
|
||||
fn test_signature() -> CommandSignature {
|
||||
CommandSignature {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
use super::*;
|
||||
use crate::signatures::{Command, Priority};
|
||||
|
||||
fn test_signature() -> CommandSignature {
|
||||
CommandSignature {
|
||||
command: Command {
|
||||
name: "test".to_owned(),
|
||||
alias: vec![],
|
||||
description: None,
|
||||
arguments: vec![],
|
||||
subcommands: vec![],
|
||||
options: vec![],
|
||||
priority: Priority::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_registry_registers_signature() {
|
||||
let registry = CommandRegistry::new();
|
||||
registry.register_signature(test_signature());
|
||||
|
||||
let signature = registry
|
||||
.get_signature("test")
|
||||
.expect("Signature is registered.");
|
||||
assert_eq!(signature.command.name, "test");
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use super::Priority;
|
||||
|
||||
#[test]
|
||||
fn test_priority_normalization() {
|
||||
let too_small = Priority::new(-201);
|
||||
assert_eq!(Priority::min(), too_small);
|
||||
|
||||
let too_large = Priority::new(201);
|
||||
assert_eq!(Priority::max(), too_large);
|
||||
|
||||
let fourty_two = Priority::new(42);
|
||||
assert_eq!(42, fourty_two.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_priority_comparison() {
|
||||
let super_important = Priority::new(200);
|
||||
let important = Priority::new(40);
|
||||
let not_important = Priority::new(-80);
|
||||
|
||||
assert!(super_important == super_important);
|
||||
assert!(super_important > important);
|
||||
assert!(super_important > not_important);
|
||||
|
||||
assert!(important == important);
|
||||
assert!(important > not_important);
|
||||
|
||||
assert!(not_important == not_important);
|
||||
}
|
||||
Reference in New Issue
Block a user