feat: more conditionals

This commit is contained in:
iff 2023-07-31 10:20:06 +02:00
parent 6b4d926dde
commit 9ebe08ad64
4 changed files with 56 additions and 30 deletions

View file

@ -48,9 +48,12 @@ suggest = "{{command[0]}} fix {{command[2:]}}"
pattern = [ pattern = [
"pattern 1", "pattern 1",
] ]
# this will add a `sudo` before the command if the `sudo` is found by `which` # this will add a `sudo` before the command if:
# - the `sudo` is found by `which`
# - the command last command does not contain `sudo`
# - the last command typed by the user does not contain `sudo`
suggest = ''' suggest = '''
#[executable(sudo)] #[executable(sudo), !cmd_contains(sudo)]
sudo {{command}}''' sudo {{command}}'''
``` ```
@ -60,6 +63,12 @@ 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).
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:
- `executable`: Check if the argument can be found by `which`
- `cmd_contains`: Check if the last user input contains the argument
- `err_contains`: Check if the error of the command contains the argument
## Current Progress ## Current Progress
We need more rule files! We need more rule files!

View file

@ -14,11 +14,11 @@ pub fn parse_rules(input: TokenStream) -> TokenStream {
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
struct Rule { struct Rule {
command: String, command: String,
match_output: Vec<MatchOutput>, match_err: Vec<MatchError>,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
struct MatchOutput { struct MatchError {
pattern: Vec<String>, pattern: Vec<String>,
suggest: Vec<String>, suggest: Vec<String>,
} }
@ -43,13 +43,13 @@ fn gen_string_hashmap(rules: Vec<Rule>) -> String {
for rule in rules { for rule in rules {
let command = rule.command.to_owned(); let command = rule.command.to_owned();
string_hashmap.push_str(&format!("(\"{}\", vec![", command)); string_hashmap.push_str(&format!("(\"{}\", vec![", command));
for match_output in rule.match_output { for match_err in rule.match_err {
let pattern = match_output let pattern = match_err
.pattern .pattern
.iter() .iter()
.map(|x| x.to_lowercase()) .map(|x| x.to_lowercase())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let suggest = match_output.suggest; let suggest = match_err.suggest;
string_hashmap.push_str(&format!( string_hashmap.push_str(&format!(
"(vec![\"{}\"], vec![\"{}\"]),", "(vec![\"{}\"], vec![\"{}\"]),",
pattern.join("\", \""), pattern.join("\", \""),

View file

@ -1,6 +1,6 @@
command = "privilege" command = "privilege"
[[match_output]] [[match_err]]
pattern = [ pattern = [
"as root", "as root",
"authentication is required", "authentication is required",

View file

@ -6,25 +6,25 @@ use crate::shell::{command_output, PRIVILEGE_LIST};
use crate::style::highlight_difference; use crate::style::highlight_difference;
pub fn correct_command(shell: &str, last_command: &str) -> Option<String> { pub fn correct_command(shell: &str, last_command: &str) -> Option<String> {
let command_output = command_output(shell, last_command); let err = command_output(shell, last_command);
let split_command = last_command.split_whitespace().collect::<Vec<&str>>(); let split_command = last_command.split_whitespace().collect::<Vec<&str>>();
let command = match PRIVILEGE_LIST.contains(&split_command[0]) { let executable = match PRIVILEGE_LIST.contains(&split_command[0]) {
true => split_command.get(1).expect("No command found."), true => split_command.get(1).expect("No command found."),
false => split_command.first().expect("No command found."), false => split_command.first().expect("No command found."),
}; };
if !PRIVILEGE_LIST.contains(command) { if !PRIVILEGE_LIST.contains(executable) {
let suggest = match_pattern("privilege", &command_output); let suggest = match_pattern("privilege", last_command, &err);
if let Some(suggest) = suggest { if let Some(suggest) = suggest {
let suggest = eval_suggest(&suggest, last_command); let suggest = eval_suggest(&suggest, last_command);
return Some(suggest); return Some(suggest);
} }
} }
let suggest = match_pattern(command, &command_output); let suggest = match_pattern(executable, last_command, &err);
if let Some(suggest) = suggest { if let Some(suggest) = suggest {
let suggest = eval_suggest(&suggest, last_command); let suggest = eval_suggest(&suggest, last_command);
if PRIVILEGE_LIST.contains(command) { if PRIVILEGE_LIST.contains(executable) {
return Some(format!("{} {}", split_command[0], suggest)); return Some(format!("{} {}", split_command[0], suggest));
} }
return Some(suggest); return Some(suggest);
@ -32,15 +32,15 @@ pub fn correct_command(shell: &str, last_command: &str) -> Option<String> {
None None
} }
fn match_pattern(command: &str, error_msg: &str) -> Option<String> { fn match_pattern(executable: &str, command: &str, error_msg: &str) -> Option<String> {
let rules = parse_rules!("rules"); let rules = parse_rules!("rules");
if rules.contains_key(command) { if rules.contains_key(executable) {
let suggest = rules.get(command).unwrap(); let suggest = rules.get(executable).unwrap();
for (pattern, suggest) in suggest { for (pattern, suggest) in suggest {
for pattern in pattern { for pattern in pattern {
if error_msg.contains(pattern) { if error_msg.contains(pattern) {
for suggest in suggest { for suggest in suggest {
if let Some(suggest) = check_suggest(suggest) { if let Some(suggest) = check_suggest(suggest, command, error_msg) {
return Some(suggest); return Some(suggest);
} }
} }
@ -53,27 +53,38 @@ fn match_pattern(command: &str, error_msg: &str) -> Option<String> {
} }
} }
fn check_suggest(suggest: &str) -> Option<String> { fn check_suggest(suggest: &str, command: &str, error_msg: &str) -> Option<String> {
if !suggest.starts_with('#') { if !suggest.starts_with('#') {
return Some(suggest.to_owned()); return Some(suggest.to_owned());
} }
let lines = suggest.lines().collect::<Vec<&str>>(); let lines = suggest.lines().collect::<Vec<&str>>();
let conditions = lines.first().unwrap(); let conditions = lines.first().unwrap().trim().replacen("#", "", 1);
let conditions = conditions.trim_matches(|c| c == '#' || c == '[' || c == ']'); let conditions = conditions
let conditions = conditions.split(',').collect::<Vec<&str>>(); .trim_start_matches('[')
for condition in conditions { .trim_end_matches(']');
let condition = condition.trim(); let conditions = conditions
let (condition, arg) = condition.split_once('(').unwrap(); .split(",")
let arg = arg.trim_matches(|c| c == '(' || c == ')'); .collect::<Vec<&str>>() ;
if eval_condition(condition, arg) == false { for condition in conditions {
let (mut condition, arg) = condition.split_once('(').unwrap();
condition = condition.trim();
let arg = arg.trim_matches(|c| c == '(' || c == ')');
let reverse = match condition.starts_with('!') {
true => {
condition = condition.trim_start_matches('!');
true
},
false => false,
};
if eval_condition(condition, arg, command, error_msg) == reverse {
return None; return None;
} }
} }
Some(lines[1..].join("\n")) Some(lines[1..].join("\n"))
} }
fn eval_condition(condition: &str, arg: &str) -> bool { fn eval_condition(condition: &str, arg: &str, command: &str, error_msg: &str) -> bool {
match condition { match condition {
"executable" => { "executable" => {
let output = std::process::Command::new("which") let output = std::process::Command::new("which")
@ -81,8 +92,14 @@ fn eval_condition(condition: &str, arg: &str) -> bool {
.output() .output()
.expect("failed to execute process"); .expect("failed to execute process");
output.status.success() output.status.success()
} },
_ => false, "err_contains" => {
error_msg.contains(arg)
},
"cmd_contains" => {
command.contains(arg)
},
_ => unreachable!("Unknown condition when evaluation condition: {}", condition)
} }
} }