mirror of
https://github.com/TECHNOFAB11/pay-respects.git
synced 2025-12-12 14:30:10 +01:00
refactor: split runtime rules into module
This commit is contained in:
parent
000b50c211
commit
5aebf867c1
20 changed files with 514 additions and 249 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -474,6 +474,7 @@ dependencies = [
|
||||||
"curl",
|
"curl",
|
||||||
"inquire",
|
"inquire",
|
||||||
"pay-respects-parser",
|
"pay-respects-parser",
|
||||||
|
"pay-respects-utils",
|
||||||
"regex-lite",
|
"regex-lite",
|
||||||
"rust-i18n",
|
"rust-i18n",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -483,11 +484,19 @@ dependencies = [
|
||||||
"toml 0.8.19",
|
"toml 0.8.19",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pay-respects-module-runtime-rules"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"pay-respects-utils",
|
||||||
|
"regex-lite",
|
||||||
|
"serde",
|
||||||
|
"toml 0.8.19",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pay-respects-parser"
|
name = "pay-respects-parser"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c6304475497712e6fbcbb0f947c4958ae336f1dcc3d7c8f470664a20e9ef175"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -496,6 +505,13 @@ dependencies = [
|
||||||
"toml 0.8.19",
|
"toml 0.8.19",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pay-respects-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"regex-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@ resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"pay-respects",
|
"pay-respects",
|
||||||
"pay-respects-parser",
|
"pay-respects-parser",
|
||||||
|
"pay-respects-utils",
|
||||||
# optional modules
|
# optional modules
|
||||||
# "pay-respects-module-runtime-rules",
|
"pay-respects-module-runtime-rules",
|
||||||
# "pay-respects-module-request-ai"
|
# "pay-respects-module-request-ai",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
strip = true
|
||||||
|
|
|
||||||
11
pay-respects-module-runtime-rules/Cargo.toml
Normal file
11
pay-respects-module-runtime-rules/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "pay-respects-module-runtime-rules"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
regex-lite = "0.1"
|
||||||
|
|
||||||
|
toml = { version = "0.8" }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
pay-respects-utils = {version = "0.1.0", path = "../pay-respects-utils"}
|
||||||
16
pay-respects-module-runtime-rules/src/main.rs
Normal file
16
pay-respects-module-runtime-rules/src/main.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
mod replaces;
|
||||||
|
mod rules;
|
||||||
|
|
||||||
|
fn main() -> Result<(), std::io::Error>{
|
||||||
|
let executable = std::env::var("_PR_COMMAND").unwrap();
|
||||||
|
let shell = std::env::var("_PR_SHELL").unwrap();
|
||||||
|
let last_command = std::env::var("_PR_LAST_COMMAND").unwrap();
|
||||||
|
let error_msg = std::env::var("_PR_ERROR_MSG").unwrap();
|
||||||
|
let executables: Vec<String> = {
|
||||||
|
let executables = std::env::var("_PR_EXECUTABLES").unwrap();
|
||||||
|
executables.split(",").map(|s| s.to_string()).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
rules::runtime_match(&executable, &shell, &last_command, &error_msg, &executables);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
186
pay-respects-module-runtime-rules/src/replaces.rs
Normal file
186
pay-respects-module-runtime-rules/src/replaces.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
use pay_respects_utils::evals::*;
|
||||||
|
|
||||||
|
fn tag(name: &str, x: i32) -> String {
|
||||||
|
format!("{{{}{}}}", name, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_placeholder(
|
||||||
|
string: &str,
|
||||||
|
start: &str,
|
||||||
|
end: &str,
|
||||||
|
) -> (std::ops::Range<usize>, std::ops::Range<usize>) {
|
||||||
|
let start_index = string.find(start).unwrap();
|
||||||
|
let end_index = string[start_index..].find(end).unwrap() + start_index + end.len();
|
||||||
|
|
||||||
|
let placeholder = start_index..end_index;
|
||||||
|
|
||||||
|
let args = start_index + start.len()..end_index - end.len();
|
||||||
|
|
||||||
|
(placeholder, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opts(suggest: &mut String, last_command: &mut String, opt_list: &mut Vec<(String, String)>) {
|
||||||
|
let mut replace_tag = 0;
|
||||||
|
let tag_name = "opts";
|
||||||
|
|
||||||
|
while suggest.contains(" {{opt::") {
|
||||||
|
let (placeholder, args) = eval_placeholder(suggest, " {{opt::", "}}");
|
||||||
|
|
||||||
|
let opt = &suggest[args.to_owned()];
|
||||||
|
let regex = opt.trim();
|
||||||
|
let current_tag = tag(tag_name, replace_tag);
|
||||||
|
|
||||||
|
opt_list.push((current_tag.clone(), opt_regex(regex, last_command)));
|
||||||
|
suggest.replace_range(placeholder, ¤t_tag);
|
||||||
|
|
||||||
|
replace_tag += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cmd_reg(suggest: &mut String, last_command: &str) {
|
||||||
|
while suggest.contains("{{cmd::") {
|
||||||
|
let (placeholder, args) = eval_placeholder(suggest, "{{cmd::", "}}");
|
||||||
|
|
||||||
|
let regex = suggest[args.to_owned()].trim();
|
||||||
|
|
||||||
|
let command = cmd_regex(regex, last_command);
|
||||||
|
suggest.replace_range(placeholder, &command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn err(suggest: &mut String, error_msg: &str) {
|
||||||
|
while suggest.contains("{{err::") {
|
||||||
|
let (placeholder, args) = eval_placeholder(suggest, "{{err::", "}}");
|
||||||
|
|
||||||
|
let regex = suggest[args.to_owned()].trim();
|
||||||
|
|
||||||
|
let command = err_regex(regex, error_msg);
|
||||||
|
suggest.replace_range(placeholder, &command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(suggest: &mut String, split_command: &[String]) {
|
||||||
|
while suggest.contains("{{command") {
|
||||||
|
let (placeholder, args) = eval_placeholder(suggest, "{{command", "}}");
|
||||||
|
|
||||||
|
let range = suggest[args.to_owned()].trim_matches(|c| c == '[' || c == ']');
|
||||||
|
if let Some((start, end)) = range.split_once(':') {
|
||||||
|
let mut start_index = start.parse::<i32>().unwrap_or(0);
|
||||||
|
if start_index < 0 {
|
||||||
|
start_index += split_command.len() as i32;
|
||||||
|
};
|
||||||
|
let mut end_index;
|
||||||
|
let parsed_end = end.parse::<i32>();
|
||||||
|
if parsed_end.is_err() {
|
||||||
|
end_index = split_command.len() as i32;
|
||||||
|
} else {
|
||||||
|
end_index = parsed_end.unwrap();
|
||||||
|
if end_index < 0 {
|
||||||
|
end_index += split_command.len() as i32 + 1;
|
||||||
|
} else {
|
||||||
|
end_index += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let command = split_command[start_index as usize..end_index as usize].join(" ");
|
||||||
|
|
||||||
|
suggest.replace_range(placeholder, &command);
|
||||||
|
} else {
|
||||||
|
let range = range.parse::<usize>().unwrap_or(0);
|
||||||
|
let command = &split_command[range];
|
||||||
|
|
||||||
|
suggest.replace_range(placeholder, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn typo(suggest: &mut String, split_command: &[String], executables: &[String], shell: &str) {
|
||||||
|
while suggest.contains("{{typo") {
|
||||||
|
let (placeholder, args) = eval_placeholder(suggest, "{{typo", "}}");
|
||||||
|
|
||||||
|
let index = if suggest.contains('[') {
|
||||||
|
let split = suggest[args.to_owned()]
|
||||||
|
.split(&['[', ']'])
|
||||||
|
.collect::<Vec<&str>>();
|
||||||
|
let command_index = split[1];
|
||||||
|
if !command_index.contains(':') {
|
||||||
|
let command_index = command_index.parse::<i32>().unwrap();
|
||||||
|
|
||||||
|
let index = if command_index < 0 {
|
||||||
|
split_command.len() as i32 + command_index
|
||||||
|
} else {
|
||||||
|
command_index
|
||||||
|
};
|
||||||
|
index as usize..index as usize + 1
|
||||||
|
} else {
|
||||||
|
let (start, end) = command_index.split_once(':').unwrap();
|
||||||
|
let start = start.parse::<i32>().unwrap_or(0);
|
||||||
|
let start_index = if start < 0 {
|
||||||
|
split_command.len() as i32 + start
|
||||||
|
} else {
|
||||||
|
start
|
||||||
|
};
|
||||||
|
let end = end.parse::<i32>();
|
||||||
|
let end_index = if end.is_err() {
|
||||||
|
split_command.len() as i32
|
||||||
|
} else {
|
||||||
|
let end = end.unwrap();
|
||||||
|
if end < 0 {
|
||||||
|
split_command.len() as i32 + end + 1
|
||||||
|
} else {
|
||||||
|
end + 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
start_index as usize..end_index as usize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!("Typo suggestion must have a command index");
|
||||||
|
};
|
||||||
|
|
||||||
|
let match_list = if suggest.contains('(') {
|
||||||
|
let split = suggest[args.to_owned()]
|
||||||
|
.split_once("(")
|
||||||
|
.unwrap()
|
||||||
|
.1
|
||||||
|
.rsplit_once(")")
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
split.split(',').collect::<Vec<&str>>()
|
||||||
|
} else {
|
||||||
|
unreachable!("Typo suggestion must have a match list");
|
||||||
|
};
|
||||||
|
|
||||||
|
let match_list = match_list
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let command = if match_list[0].starts_with("{{shell") {
|
||||||
|
let function = match_list.join(",");
|
||||||
|
let (_, args) = eval_placeholder(&function, "{{shell", "}}");
|
||||||
|
let function = &function[args.to_owned()].trim_matches(|c| c == '(' || c == ')');
|
||||||
|
suggest_typo(
|
||||||
|
&split_command[index],
|
||||||
|
eval_shell_command(shell, function),
|
||||||
|
executables,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
suggest_typo(&split_command[index], match_list, executables)
|
||||||
|
};
|
||||||
|
|
||||||
|
suggest.replace_range(placeholder, &command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shell(suggest: &mut String, shell: &str) {
|
||||||
|
while suggest.contains("{{shell") {
|
||||||
|
let (placeholder, args) = eval_placeholder(suggest, "{{shell", "}}");
|
||||||
|
let range = suggest[args.to_owned()].trim_matches(|c| c == '(' || c == ')');
|
||||||
|
|
||||||
|
let command = eval_shell_command(shell, range);
|
||||||
|
|
||||||
|
suggest.replace_range(placeholder, &command.join("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::replaces;
|
use crate::replaces;
|
||||||
use crate::shell::Data;
|
use pay_respects_utils::evals::*;
|
||||||
use crate::suggestions::*;
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct Rule {
|
struct Rule {
|
||||||
|
|
@ -13,7 +12,13 @@ struct MatchError {
|
||||||
suggest: Vec<String>,
|
suggest: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runtime_match(executable: &str, data: &mut Data) {
|
pub fn runtime_match(
|
||||||
|
executable: &str,
|
||||||
|
shell: &str,
|
||||||
|
last_command: &str,
|
||||||
|
error_msg: &str,
|
||||||
|
executables: &[String],
|
||||||
|
) {
|
||||||
let file = get_rule(executable);
|
let file = get_rule(executable);
|
||||||
if file.is_none() {
|
if file.is_none() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -21,11 +26,7 @@ pub fn runtime_match(executable: &str, data: &mut Data) {
|
||||||
|
|
||||||
let file = std::fs::read_to_string(file.unwrap()).unwrap();
|
let file = std::fs::read_to_string(file.unwrap()).unwrap();
|
||||||
let rule: Rule = toml::from_str(&file).unwrap();
|
let rule: Rule = toml::from_str(&file).unwrap();
|
||||||
let split_command = &data.split.clone();
|
let split_command = split_command(last_command);
|
||||||
let shell = &data.shell.clone();
|
|
||||||
let last_command = &data.command.clone();
|
|
||||||
let error_msg = &data.error.clone();
|
|
||||||
let executables = &data.get_executables().clone();
|
|
||||||
|
|
||||||
let mut pure_suggest;
|
let mut pure_suggest;
|
||||||
|
|
||||||
|
|
@ -66,8 +67,8 @@ pub fn runtime_match(executable: &str, data: &mut Data) {
|
||||||
shell,
|
shell,
|
||||||
last_command,
|
last_command,
|
||||||
error_msg,
|
error_msg,
|
||||||
split_command,
|
&split_command,
|
||||||
data,
|
executables,
|
||||||
) == reverse
|
) == reverse
|
||||||
{
|
{
|
||||||
continue 'suggest;
|
continue 'suggest;
|
||||||
|
|
@ -82,13 +83,16 @@ pub fn runtime_match(executable: &str, data: &mut Data) {
|
||||||
if pure_suggest.contains("{{command}}") {
|
if pure_suggest.contains("{{command}}") {
|
||||||
pure_suggest = pure_suggest.replace("{{command}}", last_command);
|
pure_suggest = pure_suggest.replace("{{command}}", last_command);
|
||||||
}
|
}
|
||||||
data.add_candidate(&eval_suggest(
|
print!("{}",
|
||||||
|
eval_suggest(
|
||||||
&pure_suggest,
|
&pure_suggest,
|
||||||
last_command,
|
last_command,
|
||||||
error_msg,
|
error_msg,
|
||||||
executables,
|
executables,
|
||||||
shell,
|
shell,
|
||||||
));
|
)
|
||||||
|
);
|
||||||
|
print!("{}", "<_PR_BR>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,10 +106,10 @@ fn eval_condition(
|
||||||
last_command: &str,
|
last_command: &str,
|
||||||
error_msg: &str,
|
error_msg: &str,
|
||||||
split_command: &[String],
|
split_command: &[String],
|
||||||
data: &mut Data,
|
executables: &[String],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match condition {
|
match condition {
|
||||||
"executable" => data.has_executable(arg),
|
"executable" => executables.contains(&arg.to_string()),
|
||||||
"err_contains" => error_msg.contains(arg),
|
"err_contains" => error_msg.contains(arg),
|
||||||
"cmd_contains" => last_command.contains(arg),
|
"cmd_contains" => last_command.contains(arg),
|
||||||
"min_length" => split_command.len() >= arg.parse::<usize>().unwrap(),
|
"min_length" => split_command.len() >= arg.parse::<usize>().unwrap(),
|
||||||
|
|
@ -190,3 +194,4 @@ fn get_rule(executable: &str) -> Option<String> {
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +184,7 @@ fn parse_conditions(suggest: &str) -> (String, Vec<TokenStream2>) {
|
||||||
|
|
||||||
fn eval_condition(condition: &str, arg: &str) -> TokenStream2 {
|
fn eval_condition(condition: &str, arg: &str) -> TokenStream2 {
|
||||||
match condition {
|
match condition {
|
||||||
"executable" => quote! {data.has_executable(#arg)},
|
"executable" => quote! {executables.contains(&#arg.to_string())},
|
||||||
"err_contains" => quote! {error_msg.contains(#arg)},
|
"err_contains" => quote! {error_msg.contains(#arg)},
|
||||||
"cmd_contains" => quote! {last_command.contains(#arg)},
|
"cmd_contains" => quote! {last_command.contains(#arg)},
|
||||||
"min_length" => quote! {(split.len() >= #arg.parse::<usize>().unwrap())},
|
"min_length" => quote! {(split.len() >= #arg.parse::<usize>().unwrap())},
|
||||||
|
|
@ -215,6 +215,6 @@ fn eval_suggest(suggest: &str) -> TokenStream2 {
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#(#opt_list)*
|
#(#opt_list)*
|
||||||
data.add_candidate(&format!{#suggest, #(#replace_list),*});
|
candidates.push(format!{#suggest, #(#replace_list),*});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
pay-respects-utils/Cargo.toml
Normal file
8
pay-respects-utils/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "pay-respects-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
regex-lite = "0.1"
|
||||||
|
|
||||||
155
pay-respects-utils/src/evals.rs
Normal file
155
pay-respects-utils/src/evals.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
use regex_lite::Regex;
|
||||||
|
use crate::files::*;
|
||||||
|
|
||||||
|
pub fn opt_regex(regex: &str, command: &mut String) -> String {
|
||||||
|
let regex = Regex::new(regex).unwrap();
|
||||||
|
|
||||||
|
let mut opts = Vec::new();
|
||||||
|
for captures in regex.captures_iter(command) {
|
||||||
|
for cap in captures.iter().skip(1).flatten() {
|
||||||
|
opts.push(cap.as_str().to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt in opts.clone() {
|
||||||
|
*command = command.replace(&opt, "");
|
||||||
|
}
|
||||||
|
opts.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn err_regex(regex: &str, error_msg: &str) -> String {
|
||||||
|
let regex = Regex::new(regex).unwrap();
|
||||||
|
|
||||||
|
let mut err = Vec::new();
|
||||||
|
for captures in regex.captures_iter(error_msg) {
|
||||||
|
for cap in captures.iter().skip(1).flatten() {
|
||||||
|
err.push(cap.as_str().to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cmd_regex(regex: &str, command: &str) -> String {
|
||||||
|
let regex = Regex::new(regex).unwrap();
|
||||||
|
|
||||||
|
let mut cmd = Vec::new();
|
||||||
|
for captures in regex.captures_iter(command) {
|
||||||
|
for cap in captures.iter().skip(1).flatten() {
|
||||||
|
cmd.push(cap.as_str().to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_shell_command(shell: &str, command: &str) -> Vec<String> {
|
||||||
|
let output = std::process::Command::new(shell)
|
||||||
|
.arg("-c")
|
||||||
|
.arg(command)
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
let output = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let split_output = output.split('\n').collect::<Vec<&str>>();
|
||||||
|
split_output
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_command(command: &str) -> Vec<String> {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
eprintln!("command: {command}")
|
||||||
|
}
|
||||||
|
// this regex splits the command separated by spaces, except when the space
|
||||||
|
// is escaped by a backslash or surrounded by quotes
|
||||||
|
let regex = r#"([^\s"'\\]+|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\\ )+|\\|\n"#;
|
||||||
|
let regex = Regex::new(regex).unwrap();
|
||||||
|
let split_command = regex
|
||||||
|
.find_iter(command)
|
||||||
|
.map(|cap| cap.as_str().to_owned())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
split_command
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn suggest_typo(typos: &[String], candidates: Vec<String>, executables: &[String]) -> String {
|
||||||
|
let mut suggestions = Vec::new();
|
||||||
|
for typo in typos {
|
||||||
|
let typo = typo.as_str();
|
||||||
|
if candidates.len() == 1 {
|
||||||
|
match candidates[0].as_str() {
|
||||||
|
"path" => {
|
||||||
|
if let Some(suggest) = find_similar(typo, executables, Some(2)) {
|
||||||
|
suggestions.push(suggest);
|
||||||
|
} else {
|
||||||
|
suggestions.push(typo.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"file" => {
|
||||||
|
if let Some(suggest) = get_best_match_file(typo) {
|
||||||
|
suggestions.push(suggest);
|
||||||
|
} else {
|
||||||
|
suggestions.push(typo.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else if let Some(suggest) = find_similar(typo, &candidates, Some(2)) {
|
||||||
|
suggestions.push(suggest);
|
||||||
|
} else {
|
||||||
|
suggestions.push(typo.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suggestions.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn best_match_path(typo: &str, executables: &[String]) -> Option<String> {
|
||||||
|
find_similar(typo, executables, Some(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
// higher the threshold, the stricter the comparison
|
||||||
|
// 1: anything
|
||||||
|
// 2: 50%
|
||||||
|
// 3: 33%
|
||||||
|
// ... etc
|
||||||
|
pub fn find_similar(typo: &str, candidates: &[String], threshold: Option<usize>) -> Option<String> {
|
||||||
|
let threshold = threshold.unwrap_or(2);
|
||||||
|
let mut min_distance = typo.chars().count() / threshold + 1;
|
||||||
|
let mut min_distance_index = None;
|
||||||
|
for (i, candidate) in candidates.iter().enumerate() {
|
||||||
|
if candidate.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let distance = compare_string(typo, candidate);
|
||||||
|
if distance < min_distance {
|
||||||
|
min_distance = distance;
|
||||||
|
min_distance_index = Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(min_distance_index) = min_distance_index {
|
||||||
|
return Some(candidates[min_distance_index].to_string());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
|
pub fn compare_string(a: &str, b: &str) -> usize {
|
||||||
|
let mut matrix = vec![vec![0; b.chars().count() + 1]; a.chars().count() + 1];
|
||||||
|
|
||||||
|
for i in 0..a.chars().count() + 1 {
|
||||||
|
matrix[i][0] = i;
|
||||||
|
}
|
||||||
|
for j in 0..b.chars().count() + 1 {
|
||||||
|
matrix[0][j] = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, ca) in a.chars().enumerate() {
|
||||||
|
for (j, cb) in b.chars().enumerate() {
|
||||||
|
let cost = if ca == cb { 0 } else { 1 };
|
||||||
|
matrix[i + 1][j + 1] = std::cmp::min(
|
||||||
|
std::cmp::min(matrix[i][j + 1] + 1, matrix[i + 1][j] + 1),
|
||||||
|
matrix[i][j] + cost,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matrix[a.chars().count()][b.chars().count()]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::suggestions::find_similar;
|
use crate::evals::find_similar;
|
||||||
|
|
||||||
pub fn get_path_files() -> Vec<String> {
|
pub fn get_path_files() -> Vec<String> {
|
||||||
let path_env = path_env();
|
let path_env = path_env();
|
||||||
|
|
@ -164,3 +164,4 @@ fn path_env() -> String {
|
||||||
fn path_env_sep() -> &'static str {
|
fn path_env_sep() -> &'static str {
|
||||||
":"
|
":"
|
||||||
}
|
}
|
||||||
|
|
||||||
2
pay-respects-utils/src/lib.rs
Normal file
2
pay-respects-utils/src/lib.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod evals;
|
||||||
|
pub mod files;
|
||||||
|
|
@ -28,6 +28,7 @@ textwrap = { version = "0.16", features = ["terminal_size"], optional = true }
|
||||||
inquire = "0.7.5"
|
inquire = "0.7.5"
|
||||||
|
|
||||||
pay-respects-parser = { version = "0.3.2", path = "../pay-respects-parser" }
|
pay-respects-parser = { version = "0.3.2", path = "../pay-respects-parser" }
|
||||||
|
pay-respects-utils = {version = "0.1.0", path = "../pay-respects-utils"}
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
runtime-rules = ["dep:serde", "dep:toml"]
|
runtime-rules = ["dep:serde", "dep:toml"]
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
use sys_locale::get_locale;
|
use sys_locale::get_locale;
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
mod files;
|
|
||||||
mod modes;
|
mod modes;
|
||||||
mod rules;
|
mod rules;
|
||||||
mod shell;
|
mod shell;
|
||||||
|
|
@ -25,11 +24,6 @@ mod style;
|
||||||
mod suggestions;
|
mod suggestions;
|
||||||
mod system;
|
mod system;
|
||||||
|
|
||||||
#[cfg(feature = "runtime-rules")]
|
|
||||||
mod replaces;
|
|
||||||
#[cfg(feature = "runtime-rules")]
|
|
||||||
mod runtime_rules;
|
|
||||||
|
|
||||||
#[cfg(feature = "request-ai")]
|
#[cfg(feature = "request-ai")]
|
||||||
mod requests;
|
mod requests;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
use crate::shell::Data;
|
use crate::shell::Data;
|
||||||
use crate::suggestions::{best_match_path, suggest_candidates};
|
use crate::suggestions::suggest_candidates;
|
||||||
use crate::system;
|
use crate::system;
|
||||||
use crate::{shell, suggestions};
|
use crate::{shell, suggestions};
|
||||||
|
use pay_respects_utils::evals::best_match_path;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use inquire::*;
|
use inquire::*;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
||||||
pub fn suggestion(data: &mut Data) {
|
pub fn suggestion(data: &mut Data) {
|
||||||
let shell = data.shell.clone();
|
let shell = data.shell.clone();
|
||||||
let mut last_command;
|
let mut last_command;
|
||||||
|
|
@ -65,7 +67,7 @@ pub fn cnf(data: &mut Data) {
|
||||||
executable
|
executable
|
||||||
);
|
);
|
||||||
|
|
||||||
let best_match = best_match_path(executable);
|
let best_match = best_match_path(executable, &data.executables);
|
||||||
if best_match.is_some() {
|
if best_match.is_some() {
|
||||||
let best_match = best_match.unwrap();
|
let best_match = best_match.unwrap();
|
||||||
split_command[0] = best_match;
|
split_command[0] = best_match;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::suggestions::*;
|
use pay_respects_utils::evals::*;
|
||||||
|
|
||||||
fn tag(name: &str, x: i32) -> String {
|
fn tag(name: &str, x: i32) -> String {
|
||||||
format!("{{{}{}}}", name, x)
|
format!("{{{}{}}}", name, x)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::shell::Data;
|
use crate::shell::Data;
|
||||||
use crate::suggestions::*;
|
use pay_respects_utils::evals::*;
|
||||||
use pay_respects_parser::parse_rules;
|
use pay_respects_parser::parse_rules;
|
||||||
|
|
||||||
pub fn match_pattern(executable: &str, data: &mut Data) {
|
pub fn match_pattern(executable: &str, data: &mut Data) {
|
||||||
let error_msg = &data.error.clone();
|
let error_msg = &data.error;
|
||||||
let shell = &data.shell.clone();
|
let shell = &data.shell;
|
||||||
let last_command = &data.command.clone();
|
let last_command = &data.command;
|
||||||
let executables = &data.get_executables().clone();
|
let executables = &data.executables;
|
||||||
|
let candidates = &mut data.candidates;
|
||||||
parse_rules!("rules");
|
parse_rules!("rules");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use pay_respects_utils::evals::split_command;
|
||||||
|
use pay_respects_utils::files::get_path_files;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -5,10 +7,6 @@ use std::sync::mpsc::channel;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use regex_lite::Regex;
|
|
||||||
|
|
||||||
use crate::files::get_path_files;
|
|
||||||
|
|
||||||
pub const PRIVILEGE_LIST: [&str; 2] = ["sudo", "doas"];
|
pub const PRIVILEGE_LIST: [&str; 2] = ["sudo", "doas"];
|
||||||
|
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
|
|
@ -45,6 +43,8 @@ pub struct Data {
|
||||||
pub privilege: Option<String>,
|
pub privilege: Option<String>,
|
||||||
pub error: String,
|
pub error: String,
|
||||||
pub executables: Vec<String>,
|
pub executables: Vec<String>,
|
||||||
|
pub modules: Vec<String>,
|
||||||
|
pub fallbacks: Vec<String>,
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +54,32 @@ impl Data {
|
||||||
let command = last_command(&shell).trim().to_string();
|
let command = last_command(&shell).trim().to_string();
|
||||||
let alias = alias_map(&shell);
|
let alias = alias_map(&shell);
|
||||||
let mode = run_mode();
|
let mode = run_mode();
|
||||||
|
let (executables, modules, fallbacks) = {
|
||||||
|
let path_executables = get_path_files();
|
||||||
|
let mut executables = vec![];
|
||||||
|
let mut modules = vec![];
|
||||||
|
let mut fallbacks = vec![];
|
||||||
|
for exe in path_executables {
|
||||||
|
if exe.starts_with("pay-respects-module-") {
|
||||||
|
modules.push(exe.to_string());
|
||||||
|
} else if exe.starts_with("pay-respects-fallback-") {
|
||||||
|
fallbacks.push(exe.to_string());
|
||||||
|
} else {
|
||||||
|
executables.push(exe.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if alias.is_some() {
|
||||||
|
let alias = alias.as_ref().unwrap();
|
||||||
|
for command in alias.keys() {
|
||||||
|
if executables.contains(command) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
executables.push(command.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(executables, modules, fallbacks)
|
||||||
|
};
|
||||||
|
|
||||||
let mut init = Data {
|
let mut init = Data {
|
||||||
shell,
|
shell,
|
||||||
|
|
@ -64,7 +90,9 @@ impl Data {
|
||||||
split: vec![],
|
split: vec![],
|
||||||
privilege: None,
|
privilege: None,
|
||||||
error: "".to_string(),
|
error: "".to_string(),
|
||||||
executables: vec![],
|
executables,
|
||||||
|
modules,
|
||||||
|
fallbacks,
|
||||||
mode,
|
mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -135,50 +163,19 @@ impl Data {
|
||||||
self.candidates.push(candidate.to_string());
|
self.candidates.push(candidate.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn add_candidates(&mut self, candidates: &Vec<String>) {
|
||||||
pub fn get_executables(&mut self) -> &Vec<String> {
|
for candidate in candidates {
|
||||||
if self.executables.is_empty() {
|
let candidate = candidate.trim();
|
||||||
self.executables = get_path_files();
|
if candidate != self.command {
|
||||||
if self.alias.is_some() {
|
self.candidates.push(candidate.to_string());
|
||||||
let alias = self.alias.as_ref().unwrap();
|
|
||||||
for command in alias.keys() {
|
|
||||||
if self.executables.contains(command) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
self.executables.push(command.to_string());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&self.executables
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_executable(&mut self, executable: &str) -> bool {
|
|
||||||
if self.executables.is_empty() {
|
|
||||||
self.executables = get_path_files();
|
|
||||||
}
|
|
||||||
self.executables.contains(&executable.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn split_command(command: &str) -> Vec<String> {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
eprintln!("command: {command}");
|
|
||||||
// this regex splits the command separated by spaces, except when the space
|
|
||||||
// is escaped by a backslash or surrounded by quotes
|
|
||||||
let regex = r#"([^\s"'\\]+|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\\ )+|\\|\n"#;
|
|
||||||
let regex = Regex::new(regex).unwrap();
|
|
||||||
let split_command = regex
|
|
||||||
.find_iter(command)
|
|
||||||
.map(|cap| cap.as_str().to_owned())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
eprintln!("split_command: {:?}", split_command);
|
|
||||||
split_command
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn elevate(data: &mut Data, command: &mut String) {
|
pub fn elevate(data: &mut Data, command: &mut String) {
|
||||||
for privilege in PRIVILEGE_LIST.iter() {
|
for privilege in PRIVILEGE_LIST.iter() {
|
||||||
if data.has_executable(privilege) {
|
if data.executables.contains(&privilege.to_string()) {
|
||||||
*command = format!("{} {}", privilege, command);
|
*command = format!("{} {}", privilege, command);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -241,6 +238,28 @@ pub fn command_output(shell: &str, command: &str) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn module_output(data: &Data, module: &str) -> Vec<String> {
|
||||||
|
let shell = &data.shell;
|
||||||
|
let executable = &data.split[0];
|
||||||
|
let last_command = &data.command;
|
||||||
|
let error_msg = &data.error;
|
||||||
|
let executables = data.executables.clone().join(",");
|
||||||
|
let output = std::process::Command::new(shell)
|
||||||
|
.arg("-c")
|
||||||
|
.arg(module)
|
||||||
|
.env("_PR_COMMAND", executable)
|
||||||
|
.env("_PR_LAST_COMMAND", last_command)
|
||||||
|
.env("_PR_ERROR_MSG", error_msg)
|
||||||
|
.env("_PR_EXECUTABLES", executables)
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
.split("<_PR_BR>")
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn last_command(shell: &str) -> String {
|
pub fn last_command(shell: &str) -> String {
|
||||||
let last_command = match std::env::var("_PR_LAST_COMMAND") {
|
let last_command = match std::env::var("_PR_LAST_COMMAND") {
|
||||||
Ok(command) => command,
|
Ok(command) => command,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
use pay_respects_utils::evals::split_command;
|
||||||
use crate::shell::PRIVILEGE_LIST;
|
use crate::shell::PRIVILEGE_LIST;
|
||||||
use crate::suggestions::split_command;
|
|
||||||
use colored::*;
|
use colored::*;
|
||||||
|
|
||||||
// to_string() is necessary here, otherwise there won't be color in the output
|
// to_string() is necessary here, otherwise there won't be color in the output
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,9 @@ use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use inquire::*;
|
use inquire::*;
|
||||||
use regex_lite::Regex;
|
|
||||||
|
|
||||||
use crate::files::{get_best_match_file, get_path_files};
|
|
||||||
use crate::rules::match_pattern;
|
use crate::rules::match_pattern;
|
||||||
use crate::shell::{shell_evaluated_commands, Data};
|
use crate::shell::{shell_evaluated_commands, Data, module_output};
|
||||||
use crate::style::highlight_difference;
|
use crate::style::highlight_difference;
|
||||||
|
|
||||||
pub fn suggest_candidates(data: &mut Data) {
|
pub fn suggest_candidates(data: &mut Data) {
|
||||||
|
|
@ -21,10 +19,12 @@ pub fn suggest_candidates(data: &mut Data) {
|
||||||
match_pattern(executable, data);
|
match_pattern(executable, data);
|
||||||
match_pattern("_PR_general", data);
|
match_pattern("_PR_general", data);
|
||||||
|
|
||||||
#[cfg(feature = "runtime-rules")]
|
let modules = &data.modules.clone();
|
||||||
{
|
for module in modules {
|
||||||
use crate::runtime_rules::runtime_match;
|
let candidates = module_output(data, module);
|
||||||
runtime_match(executable, data);
|
if !candidates.is_empty() {
|
||||||
|
data.add_candidates(&candidates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "request-ai")]
|
#[cfg(feature = "request-ai")]
|
||||||
|
|
@ -119,159 +119,6 @@ pub fn select_candidate(data: &mut Data) {
|
||||||
data.candidates.clear();
|
data.candidates.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn opt_regex(regex: &str, command: &mut String) -> String {
|
|
||||||
let regex = Regex::new(regex).unwrap();
|
|
||||||
|
|
||||||
let mut opts = Vec::new();
|
|
||||||
for captures in regex.captures_iter(command) {
|
|
||||||
for cap in captures.iter().skip(1).flatten() {
|
|
||||||
opts.push(cap.as_str().to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for opt in opts.clone() {
|
|
||||||
*command = command.replace(&opt, "");
|
|
||||||
}
|
|
||||||
opts.join(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn err_regex(regex: &str, error_msg: &str) -> String {
|
|
||||||
let regex = Regex::new(regex).unwrap();
|
|
||||||
|
|
||||||
let mut err = Vec::new();
|
|
||||||
for captures in regex.captures_iter(error_msg) {
|
|
||||||
for cap in captures.iter().skip(1).flatten() {
|
|
||||||
err.push(cap.as_str().to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err.join(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmd_regex(regex: &str, command: &str) -> String {
|
|
||||||
let regex = Regex::new(regex).unwrap();
|
|
||||||
|
|
||||||
let mut cmd = Vec::new();
|
|
||||||
for captures in regex.captures_iter(command) {
|
|
||||||
for cap in captures.iter().skip(1).flatten() {
|
|
||||||
cmd.push(cap.as_str().to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd.join(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval_shell_command(shell: &str, command: &str) -> Vec<String> {
|
|
||||||
let output = std::process::Command::new(shell)
|
|
||||||
.arg("-c")
|
|
||||||
.arg(command)
|
|
||||||
.output()
|
|
||||||
.expect("failed to execute process");
|
|
||||||
let output = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let split_output = output.split('\n').collect::<Vec<&str>>();
|
|
||||||
split_output
|
|
||||||
.iter()
|
|
||||||
.map(|s| s.trim().to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn split_command(command: &str) -> Vec<String> {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
eprintln!("command: {command}")
|
|
||||||
}
|
|
||||||
// this regex splits the command separated by spaces, except when the space
|
|
||||||
// is escaped by a backslash or surrounded by quotes
|
|
||||||
let regex = r#"([^\s"'\\]+|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\\ )+|\\|\n"#;
|
|
||||||
let regex = Regex::new(regex).unwrap();
|
|
||||||
let split_command = regex
|
|
||||||
.find_iter(command)
|
|
||||||
.map(|cap| cap.as_str().to_owned())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
split_command
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn suggest_typo(typos: &[String], candidates: Vec<String>, executables: &[String]) -> String {
|
|
||||||
let mut suggestions = Vec::new();
|
|
||||||
for typo in typos {
|
|
||||||
let typo = typo.as_str();
|
|
||||||
if candidates.len() == 1 {
|
|
||||||
match candidates[0].as_str() {
|
|
||||||
"path" => {
|
|
||||||
if let Some(suggest) = find_similar(typo, executables, Some(2)) {
|
|
||||||
suggestions.push(suggest);
|
|
||||||
} else {
|
|
||||||
suggestions.push(typo.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"file" => {
|
|
||||||
if let Some(suggest) = get_best_match_file(typo) {
|
|
||||||
suggestions.push(suggest);
|
|
||||||
} else {
|
|
||||||
suggestions.push(typo.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else if let Some(suggest) = find_similar(typo, &candidates, Some(2)) {
|
|
||||||
suggestions.push(suggest);
|
|
||||||
} else {
|
|
||||||
suggestions.push(typo.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
suggestions.join(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn best_match_path(typo: &str) -> Option<String> {
|
|
||||||
let path_files = get_path_files();
|
|
||||||
find_similar(typo, &path_files, Some(3))
|
|
||||||
}
|
|
||||||
|
|
||||||
// higher the threshold, the stricter the comparison
|
|
||||||
// 1: anything
|
|
||||||
// 2: 50%
|
|
||||||
// 3: 33%
|
|
||||||
// ... etc
|
|
||||||
pub fn find_similar(typo: &str, candidates: &[String], threshold: Option<usize>) -> Option<String> {
|
|
||||||
let threshold = threshold.unwrap_or(2);
|
|
||||||
let mut min_distance = typo.chars().count() / threshold + 1;
|
|
||||||
let mut min_distance_index = None;
|
|
||||||
for (i, candidate) in candidates.iter().enumerate() {
|
|
||||||
if candidate.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let distance = compare_string(typo, candidate);
|
|
||||||
if distance < min_distance {
|
|
||||||
min_distance = distance;
|
|
||||||
min_distance_index = Some(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(min_distance_index) = min_distance_index {
|
|
||||||
return Some(candidates[min_distance_index].to_string());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
pub fn compare_string(a: &str, b: &str) -> usize {
|
|
||||||
let mut matrix = vec![vec![0; b.chars().count() + 1]; a.chars().count() + 1];
|
|
||||||
|
|
||||||
for i in 0..a.chars().count() + 1 {
|
|
||||||
matrix[i][0] = i;
|
|
||||||
}
|
|
||||||
for j in 0..b.chars().count() + 1 {
|
|
||||||
matrix[0][j] = j;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i, ca) in a.chars().enumerate() {
|
|
||||||
for (j, cb) in b.chars().enumerate() {
|
|
||||||
let cost = if ca == cb { 0 } else { 1 };
|
|
||||||
matrix[i + 1][j + 1] = std::cmp::min(
|
|
||||||
std::cmp::min(matrix[i][j + 1] + 1, matrix[i + 1][j] + 1),
|
|
||||||
matrix[i][j] + cost,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matrix[a.chars().count()][b.chars().count()]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn confirm_suggestion(data: &Data) -> Result<(), String> {
|
pub fn confirm_suggestion(data: &Data) -> Result<(), String> {
|
||||||
let shell = &data.shell;
|
let shell = &data.shell;
|
||||||
let command = &data.suggest.clone().unwrap();
|
let command = &data.suggest.clone().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub fn get_package_manager(data: &mut Data) -> Option<String> {
|
||||||
];
|
];
|
||||||
|
|
||||||
for package_manager in package_managers {
|
for package_manager in package_managers {
|
||||||
if data.has_executable(package_manager) {
|
if data.executables.contains(&package_manager.to_string()) {
|
||||||
return Some(package_manager.to_string());
|
return Some(package_manager.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ pub fn get_packages(
|
||||||
let shell = &data.shell.clone();
|
let shell = &data.shell.clone();
|
||||||
match package_manager {
|
match package_manager {
|
||||||
"apt" => {
|
"apt" => {
|
||||||
if !data.has_executable("apt-file") {
|
if !data.executables.contains(&"apt-file".to_string()) {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}: apt-file is required to find packages",
|
"{}: apt-file is required to find packages",
|
||||||
"pay-respects".yellow()
|
"pay-respects".yellow()
|
||||||
|
|
@ -67,7 +67,7 @@ pub fn get_packages(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"emerge" => {
|
"emerge" => {
|
||||||
if !data.has_executable("e-file") {
|
if !data.executables.contains(&"e-file".to_string()) {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}: pfl is required to find packages",
|
"{}: pfl is required to find packages",
|
||||||
"pay-respects".yellow()
|
"pay-respects".yellow()
|
||||||
|
|
@ -91,7 +91,7 @@ pub fn get_packages(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"nix" => {
|
"nix" => {
|
||||||
if !data.has_executable("nix-locate") {
|
if !data.executables.contains(&"nix-locate".to_string()) {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}: nix-index is required to find packages",
|
"{}: nix-index is required to find packages",
|
||||||
"pay-respects".yellow()
|
"pay-respects".yellow()
|
||||||
|
|
@ -121,7 +121,7 @@ pub fn get_packages(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"pacman" => {
|
"pacman" => {
|
||||||
let result = if data.has_executable("pkgfile") {
|
let result = if data.executables.contains(&"pkgfile".to_string()) {
|
||||||
command_output(shell, &format!("pkgfile -b {}", executable))
|
command_output(shell, &format!("pkgfile -b {}", executable))
|
||||||
} else {
|
} else {
|
||||||
command_output(shell, &format!("pacman -Fq /usr/bin/{}", executable))
|
command_output(shell, &format!("pacman -Fq /usr/bin/{}", executable))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue