feat: regex matching

This commit is contained in:
iff 2023-08-01 19:35:22 +02:00
parent a5762bf121
commit 3e367a09dd
8 changed files with 85 additions and 93 deletions

View file

@ -6,4 +6,4 @@ edition = "2021"
[dependencies] [dependencies]
colored = "2.0" colored = "2.0"
rule_parser = { path = "rule_parser" } rule_parser = { path = "rule_parser" }
regex = "1.0" regex-lite = "0.1"

View file

@ -82,7 +82,7 @@ The placeholder is evaluated as following:
- `{{command[1]}}`: The first argument of the command (the command itself has index of 0) - `{{command[1]}}`: The first argument of the command (the command itself has index of 0)
- `{{command[2:5]}}`: The second to fifth arguments. If any of the side is not specified, them it defaults to the start (if it is left) or the end (if it is right). - `{{command[2:5]}}`: The second to fifth arguments. If any of the side is not specified, them it defaults to the start (if it is left) or the end (if it is right).
- `{{typo[2](fix1, fix2)}}`: This will try to change the second argument to candidates in the parenthesis. The argument in parentheses must have at least 2 values. Single arguments are reserved for specific matches, for instance, `path` to search all commands found in the `$PATH` environment. - `{{typo[2](fix1, fix2)}}`: This will try to change the second argument to candidates in the parenthesis. The argument in parentheses must have at least 2 values. Single arguments are reserved for specific matches, for instance, `path` to search all commands found in the `$PATH` environment.
- `{{opt(fix1, -*)}}`: Optional patterns that are found in the command. Note that all patterns matching this placeholder will not take a place when indexing (for example, in `rm {{opt(-r)}} file` the index of the file is always 1 regardless where `-r` was placed by the user). - `{{opt::<Regular Expression>}}`: Optional patterns that are found in the command with RegEx (see RegEx crate for syntax). Note that all patterns matching this placeholder will not take a place when indexing.
The suggestion can have additional conditions to check. To specify the conditions, add a `#[...]` at the first line (just like derive macros in Rust). Available conditions: The suggestion can have additional conditions to check. To specify the conditions, add a `#[...]` at the first line (just like derive macros in Rust). Available conditions:

View file

@ -55,9 +55,9 @@ fn gen_string_hashmap(rules: Vec<Rule>) -> String {
.map(|x| x.to_lowercase()) .map(|x| x.to_lowercase())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
string_hashmap.push_str(&format!( string_hashmap.push_str(&format!(
"(vec![\"{}\"], vec![\"{}\"]),", "(vec![\"{}\"], vec![r###\"{}\"###]),",
pattern.join("\", \""), pattern.join("\", \""),
suggest.join("\", \"") suggest.join("\"###, r###\"")
)); ));
} }
string_hashmap.push_str("]),"); string_hashmap.push_str("]),");

View file

@ -8,5 +8,6 @@ pattern = [
] ]
suggest = [ suggest = [
''' '''
{{typo[0](path)}} {{command[1:]}}''' {{typo[0](path)}} {{command[1:]}}
'''
] ]

View file

@ -6,62 +6,62 @@ pattern = [
] ]
suggest = [ suggest = [
""" """
{{command[0]}} {{typo[1](\ {{command[0]}} {{typo[1](
add,\ add,
am,\ am,
archive,\ archive,
bisect,\ bisect,
branch,\ branch,
bundle,\ bundle,
checkout,\ checkout,
cherry-pick,\ cherry-pick,
citool,\ citool,
clean,\ clean,
clone,\ clone,
commit,\ commit,
describe,\ describe,
diff,\ diff,
fetch,\ fetch,
format-patch,\ format-patch,
gc,\ gc,
gitk,\ gitk,
grep,\ grep,
gui,\ gui,
init,\ init,
log,\ log,
maintenance,\ maintenance,
merge,\ merge,
mv,\ mv,
notes,\ notes,
pull,\ pull,
push,\ push,
range-diff,\ range-diff,
rebase,\ rebase,
reset,\ reset,
restore,\ restore,
revert revert
rm,\ rm,
scalar,\ scalar,
shortlog,\ shortlog,
show,\ show,
sparse-checkout,\ sparse-checkout,
stash,\ stash,
status,\ status,
submodule,\ submodule,
switch,\ switch,
tag,\ tag,
worktree,\ worktree,
config,\ config,
fast-export,\ fast-export,
fast-import,\ fast-import,
filter-branch,\ filter-branch,
mergetool,\ mergetool,
pack-refs,\ pack-refs,
prune,\ prune,
reflog,\ reflog,
remote,\ remote,
repack,\ repack,
replace,\ replace,
)}} \ )}} {{command[2:]}}
{{command[2:]}}""" """
] ]

View file

@ -30,8 +30,10 @@ pattern = [
suggest = [ suggest = [
''' '''
#[executable(sudo)] #[executable(sudo)]
sudo {{command}}''', sudo {{command}}
''',
''' '''
#[executable(doas)] #[executable(doas)]
doas {{command}}''', doas {{command}}
''',
] ]

View file

@ -4,13 +4,15 @@ command = "rm"
pattern = [ "is a directory", ] pattern = [ "is a directory", ]
suggest = [ suggest = [
''' '''
{{command}} --recursive''' {{command}} --recursive
'''
] ]
[[match_err]] [[match_err]]
pattern = [ "no such file or directory", ] pattern = [ "no such file or directory", ]
suggest = [ suggest = [
''' '''
{{command[0}} {{opt(-*)}} {{typo[-1](file)}}''' {{command[0}} {{opt::(?:\s)-[\w]+}} {{typo[-1](file)}}
'''
] ]

View file

@ -1,4 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use regex_lite::Regex;
use rule_parser::parse_rules; use rule_parser::parse_rules;
@ -118,8 +119,8 @@ fn eval_suggest(suggest: &str, last_command: &str) -> String {
suggest = suggest.replace("{{command}}", &last_command); suggest = suggest.replace("{{command}}", &last_command);
} }
while suggest.contains("{{opt") { while suggest.contains("{{opt::") {
let placeholder_start = "{{opt"; let placeholder_start = "{{opt::";
let placeholder_end = "}}"; let placeholder_end = "}}";
let start_index = suggest.find(placeholder_start).unwrap(); let start_index = suggest.find(placeholder_start).unwrap();
@ -130,31 +131,18 @@ fn eval_suggest(suggest: &str, last_command: &str) -> String {
let placeholder = start_index..end_index; let placeholder = start_index..end_index;
let args = start_index + placeholder_start.len()..end_index - placeholder_end.len(); let args = start_index + placeholder_start.len()..end_index - placeholder_end.len();
let range = suggest[args.to_owned()].trim_matches(|c| c == '(' || c == ')'); let opt = &suggest[args.to_owned()];
let candidates = range.split(',').collect::<Vec<&str>>(); let regex = opt.trim();
let mut opts = Vec::new(); let regex = Regex::new(regex).unwrap();
let mut split_command = split_command(&last_command); let opts = regex
for opt in candidates { .find_iter(&last_command)
let opt = opt.trim(); .map(|cap| cap.as_str().to_owned())
if opt.ends_with('*') { .collect::<Vec<String>>();
let opt = opt.trim_end_matches('*');
for split in split_command.iter_mut() {
if split.starts_with(opt) {
opts.push(split.to_owned());
split.clear();
}
}
continue;
}
for split in split_command.iter_mut() {
if split == opt {
opts.push(split.to_owned());
split.clear();
}
}
}
last_command = split_command.join(" ");
suggest.replace_range(placeholder, &opts.join(" ")); suggest.replace_range(placeholder, &opts.join(" "));
for opt in opts {
last_command = last_command.replace(&opt, "");
}
} }
let split_command = split_command(&last_command); let split_command = split_command(&last_command);
@ -243,7 +231,6 @@ fn eval_suggest(suggest: &str, last_command: &str) -> String {
} }
pub fn split_command(command: &str) -> Vec<String> { pub fn split_command(command: &str) -> Vec<String> {
use regex::Regex;
let regex = r#"([^\s"\\]+|"(?:\\.|[^"\\])*"|\\.)+"#; let regex = r#"([^\s"\\]+|"(?:\\.|[^"\\])*"|\\.)+"#;
let regex = Regex::new(regex).unwrap(); let regex = Regex::new(regex).unwrap();
let split_command = regex let split_command = regex