fix(commands): make flags not match if param was in quotes

This commit is contained in:
dusk 2025-01-12 19:30:21 +09:00
parent a21210f3ce
commit 413b8c1915
No known key found for this signature in database
3 changed files with 38 additions and 17 deletions

View file

@ -69,7 +69,7 @@ pub fn parse_command(prefix: String, input: String) -> CommandResult {
loop { loop {
let possible_tokens = local_tree.possible_tokens().cloned().collect::<Vec<_>>(); let possible_tokens = local_tree.possible_tokens().cloned().collect::<Vec<_>>();
println!("possible: {:?}", possible_tokens); println!("possible: {:?}", possible_tokens);
let next = next_token(possible_tokens.clone(), input.clone(), current_pos); let next = next_token(possible_tokens.clone(), &input, current_pos);
println!("next: {:?}", next); println!("next: {:?}", next);
match next { match next {
Some(Ok((found_token, arg, new_pos))) => { Some(Ok((found_token, arg, new_pos))) => {
@ -157,39 +157,40 @@ pub fn parse_command(prefix: String, input: String) -> CommandResult {
/// - error when matching /// - error when matching
fn next_token( fn next_token(
possible_tokens: Vec<Token>, possible_tokens: Vec<Token>,
input: SmolStr, input: &str,
current_pos: usize, current_pos: usize,
) -> Option<Result<(Token, Option<TokenMatchedValue>, usize), (Token, TokenMatchError)>> { ) -> Option<Result<(Token, Option<TokenMatchedValue>, usize), (Token, TokenMatchError)>> {
// get next parameter, matching quotes // get next parameter, matching quotes
let param = crate::string::next_param(input.clone(), current_pos); let matched = crate::string::next_param(&input, current_pos);
println!("matched: {param:?}\n---"); println!("matched: {matched:?}\n---");
// try checking if this is a flag // try checking if this is a flag
// todo!: this breaks full text matching if the full text starts with a flag // todo!: this breaks full text matching if the full text starts with a flag
// (but that's kinda already broken anyway) // (but that's kinda already broken anyway)
if let Some((value, new_pos)) = param.clone() if let Some(param) = matched.as_ref()
&& value.starts_with('-') && param.in_quotes.not()
&& param.value.starts_with('-')
{ {
return Some(Ok(( return Some(Ok((
Token::Flag, Token::Flag,
Some(TokenMatchedValue { Some(TokenMatchedValue {
raw: value, raw: param.value.into(),
param: None, param: None,
}), }),
new_pos, param.next_pos,
))); )));
} }
// iterate over tokens and run try_match // iterate over tokens and run try_match
for token in possible_tokens { for token in possible_tokens {
// for FullString just send the whole string // for FullString just send the whole string
let input_to_match = param.clone().map(|v| v.0); let input_to_match = matched.as_ref().map(|v| v.value);
match token.try_match(input_to_match) { match token.try_match(input_to_match) {
Some(Ok(value)) => { Some(Ok(value)) => {
return Some(Ok(( return Some(Ok((
token, token,
value, value,
param.map(|v| v.1).unwrap_or(current_pos), matched.map(|v| v.next_pos).unwrap_or(current_pos),
))) )))
} }
Some(Err(err)) => { Some(Err(err)) => {

View file

@ -42,17 +42,24 @@ lazy_static::lazy_static! {
}; };
} }
#[derive(Debug)]
pub(super) struct MatchedParam<'a> {
pub(super) value: &'a str,
pub(super) next_pos: usize,
pub(super) in_quotes: bool,
}
// very very simple quote matching // very very simple quote matching
// quotes need to be at start/end of words, and are ignored if a closing quote is not present // quotes need to be at start/end of words, and are ignored if a closing quote is not present
// WTB POSIX quoting: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html // WTB POSIX quoting: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html
pub(super) fn next_param(input: SmolStr, current_pos: usize) -> Option<(SmolStr, usize)> { pub(super) fn next_param<'a>(input: &'a str, current_pos: usize) -> Option<MatchedParam<'a>> {
if input.len() == current_pos { if input.len() == current_pos {
return None; return None;
} }
let leading_whitespace_count = let leading_whitespace_count =
input[..current_pos].len() - input[..current_pos].trim_start().len(); input[..current_pos].len() - input[..current_pos].trim_start().len();
let substr_to_match: SmolStr = input[current_pos + leading_whitespace_count..].into(); let substr_to_match = &input[current_pos + leading_whitespace_count..];
println!("stuff: {input} {current_pos} {leading_whitespace_count}"); println!("stuff: {input} {current_pos} {leading_whitespace_count}");
println!("to match: {substr_to_match}"); println!("to match: {substr_to_match}");
@ -68,20 +75,32 @@ pub(super) fn next_param(input: SmolStr, current_pos: usize) -> Option<(SmolStr,
.is_whitespace() .is_whitespace()
{ {
// return quoted string, without quotes // return quoted string, without quotes
return Some((substr_to_match[1..pos - 1].into(), current_pos + pos + 1)); return Some(MatchedParam {
value: &substr_to_match[1..pos - 1],
next_pos: current_pos + pos + 1,
in_quotes: true,
});
} }
} }
} }
} }
// find next whitespace character // find next whitespace character
for (pos, char) in substr_to_match.clone().char_indices() { for (pos, char) in substr_to_match.char_indices() {
if char.is_whitespace() { if char.is_whitespace() {
return Some((substr_to_match[..pos].into(), current_pos + pos + 1)); return Some(MatchedParam {
value: &substr_to_match[..pos],
next_pos: current_pos + pos + 1,
in_quotes: false,
});
} }
} }
// if we're here, we went to EOF and didn't match any whitespace // if we're here, we went to EOF and didn't match any whitespace
// so we return the whole string // so we return the whole string
Some((substr_to_match.clone(), current_pos + substr_to_match.len())) Some(MatchedParam {
value: substr_to_match,
next_pos: current_pos + substr_to_match.len(),
in_quotes: false,
})
} }

View file

@ -88,7 +88,7 @@ impl TokenMatchedValue {
type TryMatchResult = Option<Result<Option<TokenMatchedValue>, TokenMatchError>>; type TryMatchResult = Option<Result<Option<TokenMatchedValue>, TokenMatchError>>;
impl Token { impl Token {
pub fn try_match(&self, input: Option<SmolStr>) -> TryMatchResult { pub fn try_match(&self, input: Option<&str>) -> TryMatchResult {
let input = match input { let input = match input {
Some(input) => input, Some(input) => input,
None => { None => {
@ -305,6 +305,7 @@ impl FromStr for MemberPrivacyTarget {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
// todo: this doesnt parse all the possible ways
match s.to_lowercase().as_str() { match s.to_lowercase().as_str() {
"visibility" => Ok(Self::Visibility), "visibility" => Ok(Self::Visibility),
"name" => Ok(Self::Name), "name" => Ok(Self::Name),