2024-11-15 15:43:04 +01:00
|
|
|
use crate::replaces;
|
|
|
|
|
use crate::suggestions::*;
|
|
|
|
|
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
struct Rule {
|
|
|
|
|
match_err: Vec<MatchError>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
struct MatchError {
|
|
|
|
|
pattern: Vec<String>,
|
|
|
|
|
suggest: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn runtime_match(
|
|
|
|
|
executable: &str,
|
|
|
|
|
last_command: &str,
|
|
|
|
|
error_msg: &str,
|
|
|
|
|
shell: &str,
|
|
|
|
|
) -> Option<String> {
|
2024-11-18 20:40:41 +01:00
|
|
|
let file = get_rule(executable);
|
2024-11-19 16:19:22 +01:00
|
|
|
file.as_ref()?;
|
2024-11-15 23:38:17 +01:00
|
|
|
|
2024-11-16 00:20:50 +01:00
|
|
|
let file = std::fs::read_to_string(file.unwrap()).unwrap();
|
2024-11-15 15:43:04 +01:00
|
|
|
let rule: Rule = toml::from_str(&file).unwrap();
|
2024-11-17 15:06:14 +01:00
|
|
|
let split_command = split_command(last_command);
|
2024-11-15 15:43:04 +01:00
|
|
|
|
2024-11-15 21:17:11 +01:00
|
|
|
let mut pure_suggest;
|
|
|
|
|
|
2024-11-15 15:43:04 +01:00
|
|
|
for match_err in rule.match_err {
|
|
|
|
|
for pattern in match_err.pattern {
|
|
|
|
|
if error_msg.contains(&pattern) {
|
|
|
|
|
'suggest: for suggest in &match_err.suggest {
|
|
|
|
|
if suggest.starts_with('#') {
|
|
|
|
|
let mut lines = suggest.lines().collect::<Vec<&str>>();
|
|
|
|
|
let mut conditions = String::new();
|
|
|
|
|
for (i, line) in lines[0..].iter().enumerate() {
|
|
|
|
|
conditions.push_str(line);
|
|
|
|
|
if line.ends_with(']') {
|
|
|
|
|
lines = lines[i + 1..].to_vec();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let conditions = conditions
|
|
|
|
|
.trim_start_matches(['#', '['])
|
|
|
|
|
.trim_end_matches(']')
|
|
|
|
|
.split(',')
|
|
|
|
|
.collect::<Vec<&str>>();
|
|
|
|
|
|
|
|
|
|
for condition in conditions {
|
|
|
|
|
let (mut condition, arg) = condition.split_once('(').unwrap();
|
|
|
|
|
condition = condition.trim();
|
|
|
|
|
let arg = arg.trim_start_matches('(').trim_end_matches(')');
|
|
|
|
|
let reverse = match condition.starts_with('!') {
|
|
|
|
|
true => {
|
|
|
|
|
condition = condition.trim_start_matches('!');
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
false => false,
|
|
|
|
|
};
|
|
|
|
|
if eval_condition(
|
|
|
|
|
condition,
|
|
|
|
|
arg,
|
|
|
|
|
shell,
|
|
|
|
|
last_command,
|
|
|
|
|
error_msg,
|
|
|
|
|
&split_command,
|
|
|
|
|
) == reverse
|
|
|
|
|
{
|
|
|
|
|
continue 'suggest;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-15 21:17:11 +01:00
|
|
|
pure_suggest = lines.join("\n").to_owned();
|
|
|
|
|
} else {
|
|
|
|
|
pure_suggest = suggest.to_owned();
|
|
|
|
|
}
|
|
|
|
|
// replacing placeholders
|
|
|
|
|
if pure_suggest.contains("{{command}}") {
|
|
|
|
|
pure_suggest = pure_suggest.replace("{{command}}", last_command);
|
2024-11-15 15:43:04 +01:00
|
|
|
}
|
2024-11-17 14:57:22 +01:00
|
|
|
return eval_suggest(&pure_suggest, last_command, error_msg, shell);
|
2024-11-15 15:43:04 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn eval_condition(
|
|
|
|
|
condition: &str,
|
|
|
|
|
arg: &str,
|
|
|
|
|
shell: &str,
|
|
|
|
|
last_command: &str,
|
|
|
|
|
error_msg: &str,
|
2024-11-17 15:06:14 +01:00
|
|
|
split_command: &[String],
|
2024-11-15 15:43:04 +01:00
|
|
|
) -> bool {
|
|
|
|
|
match condition {
|
|
|
|
|
"executable" => check_executable(shell, arg),
|
|
|
|
|
"err_contains" => error_msg.contains(arg),
|
|
|
|
|
"cmd_contains" => last_command.contains(arg),
|
|
|
|
|
"min_length" => split_command.len() >= arg.parse::<usize>().unwrap(),
|
|
|
|
|
"length" => split_command.len() == arg.parse::<usize>().unwrap(),
|
|
|
|
|
"max_length" => split_command.len() <= arg.parse::<usize>().unwrap() + 1,
|
2024-11-18 13:38:21 +01:00
|
|
|
"shell" => shell == arg,
|
2024-11-15 15:43:04 +01:00
|
|
|
_ => unreachable!("Unknown condition when evaluation condition: {}", condition),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-17 14:57:22 +01:00
|
|
|
fn eval_suggest(suggest: &str, last_command: &str, error_msg: &str, shell: &str) -> Option<String> {
|
2024-11-15 15:43:04 +01:00
|
|
|
let mut suggest = suggest.to_owned();
|
|
|
|
|
if suggest.contains("{{command}}") {
|
|
|
|
|
suggest = suggest.replace("{{command}}", "{last_command}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut last_command = last_command.to_owned();
|
2024-11-16 22:30:42 +01:00
|
|
|
let mut opt_list = Vec::new();
|
2024-11-15 15:43:04 +01:00
|
|
|
|
2024-11-16 22:30:42 +01:00
|
|
|
replaces::opts(&mut suggest, &mut last_command, &mut opt_list);
|
2024-11-16 22:48:52 +01:00
|
|
|
let split_command = split_command(&last_command);
|
|
|
|
|
|
|
|
|
|
replaces::cmd_reg(&mut suggest, &last_command);
|
2024-11-15 15:43:04 +01:00
|
|
|
replaces::err(&mut suggest, error_msg);
|
2024-11-16 22:48:52 +01:00
|
|
|
replaces::command(&mut suggest, &split_command);
|
2024-11-15 15:43:04 +01:00
|
|
|
replaces::shell(&mut suggest, shell);
|
2024-11-16 22:48:52 +01:00
|
|
|
replaces::typo(&mut suggest, &split_command, shell);
|
2024-11-15 15:43:04 +01:00
|
|
|
|
2024-11-16 22:30:42 +01:00
|
|
|
for (tag, value) in opt_list {
|
|
|
|
|
suggest = suggest.replace(&tag, &value);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-15 15:43:04 +01:00
|
|
|
Some(suggest)
|
|
|
|
|
}
|
2024-11-18 20:40:41 +01:00
|
|
|
|
|
|
|
|
fn get_rule(executable: &str) -> Option<String> {
|
2024-11-25 21:31:40 +08:00
|
|
|
let xdg_config_home = if cfg!(windows) {
|
|
|
|
|
std::env::var("APPDATA").unwrap()
|
|
|
|
|
} else {
|
|
|
|
|
std::env::var("XDG_CONFIG_HOME")
|
|
|
|
|
.unwrap_or_else(|_| std::env::var("HOME").unwrap() + "/.config")
|
|
|
|
|
};
|
2024-11-18 20:40:41 +01:00
|
|
|
|
|
|
|
|
let user_rule_dir = format!("{}/pay-respects/rules", xdg_config_home);
|
|
|
|
|
let user_rule_file = format!("{}/{}.toml", user_rule_dir, executable);
|
|
|
|
|
|
|
|
|
|
if std::path::Path::new(&user_rule_file).exists() {
|
|
|
|
|
return Some(user_rule_file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let check_dirs = |dirs: Vec<&str>| -> Option<String> {
|
|
|
|
|
for dir in dirs {
|
|
|
|
|
let rule_dir = format!("{}/pay-respects/rules", dir);
|
|
|
|
|
let rule_file = format!("{}/{}.toml", rule_dir, executable);
|
|
|
|
|
if std::path::Path::new(&rule_file).exists() {
|
|
|
|
|
return Some(rule_file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let xdg_config_dirs = std::env::var("XDG_CONFIG_DIRS").unwrap_or("/etc/xdg".to_owned());
|
|
|
|
|
let xdg_config_dirs = xdg_config_dirs.split(':').collect::<Vec<&str>>();
|
|
|
|
|
|
|
|
|
|
if let Some(file) = check_dirs(xdg_config_dirs) {
|
|
|
|
|
return Some(file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let xdg_data_dirs =
|
|
|
|
|
std::env::var("XDG_DATA_DIRS").unwrap_or("/usr/local/share:/usr/share".to_owned());
|
|
|
|
|
let xdg_data_dirs = xdg_data_dirs.split(':').collect::<Vec<&str>>();
|
|
|
|
|
|
|
|
|
|
if let Some(file) = check_dirs(xdg_data_dirs) {
|
|
|
|
|
return Some(file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|