feat: range substitution for typos

This commit is contained in:
iff 2023-08-11 00:41:16 +02:00
parent b991214729
commit 2a5fa8a950
4 changed files with 102 additions and 53 deletions

View file

@ -1,24 +1,26 @@
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::quote;
fn rtag (name: &str, x: i32, y: String) -> TokenStream2 { fn rtag(name: &str, x: i32, y: String) -> TokenStream2 {
let tag = format!("{}{} = {}", name, x, y); let tag = format!("{}{} = {}", name, x, y);
let tag: TokenStream2 = tag.parse().unwrap(); let tag: TokenStream2 = tag.parse().unwrap();
tag tag
} }
fn tag (name: &str, x: i32) -> String { fn tag(name: &str, x: i32) -> String {
let tag = format!("{{{}{}}}", name, x); let tag = format!("{{{}{}}}", name, x);
let tag = tag.as_str(); let tag = tag.as_str();
let tag = tag.to_owned(); let tag = tag.to_owned();
tag tag
} }
fn eval_placeholder(string: &str, start: &str, end: &str) -> (std::ops::Range<usize>, std::ops::Range<usize>) { 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 start_index = string.find(start).unwrap();
let end_index = string[start_index..].find(end).unwrap() let end_index = string[start_index..].find(end).unwrap() + start_index + end.len();
+ start_index
+ end.len();
let placeholder = start_index..end_index; let placeholder = start_index..end_index;
@ -27,7 +29,11 @@ fn eval_placeholder(string: &str, start: &str, end: &str) -> (std::ops::Range<us
(placeholder, args) (placeholder, args)
} }
pub fn opts(suggest: &mut String, replace_list: &mut Vec<TokenStream2>, opt_list: &mut Vec<TokenStream2>) { pub fn opts(
suggest: &mut String,
replace_list: &mut Vec<TokenStream2>,
opt_list: &mut Vec<TokenStream2>,
) {
let mut replace_tag = 0; let mut replace_tag = 0;
let tag_name = "opts"; let tag_name = "opts";
while suggest.contains("{{opt::") { while suggest.contains("{{opt::") {
@ -82,7 +88,6 @@ pub fn err(suggest: &mut String, replace_list: &mut Vec<TokenStream2>) {
} }
} }
pub fn command(suggest: &mut String, replace_list: &mut Vec<TokenStream2>) { pub fn command(suggest: &mut String, replace_list: &mut Vec<TokenStream2>) {
let mut replace_tag = 0; let mut replace_tag = 0;
let tag_name = "command"; let tag_name = "command";
@ -109,19 +114,18 @@ pub fn command(suggest: &mut String, replace_list: &mut Vec<TokenStream2>) {
} }
}; };
let command = format!{r#"split_command[{}..{}].join(" ")"#, start_string, end_string}; let command = format! {r#"split_command[{}..{}].join(" ")"#, start_string, end_string};
replace_list.push(rtag(tag_name, replace_tag, command)); replace_list.push(rtag(tag_name, replace_tag, command));
suggest.replace_range(placeholder, &tag(tag_name, replace_tag)); suggest.replace_range(placeholder, &tag(tag_name, replace_tag));
replace_tag += 1;
} else { } else {
let range = range.parse::<i32>().unwrap_or(0); let range = range.parse::<i32>().unwrap_or(0);
let command = format!("split_command[{}]", range); let command = format!("split_command[{}]", range);
replace_list.push(rtag(tag_name, replace_tag, command)); replace_list.push(rtag(tag_name, replace_tag, command));
suggest.replace_range(placeholder, &tag(tag_name, replace_tag)); suggest.replace_range(placeholder, &tag(tag_name, replace_tag));
replace_tag += 1;
} }
replace_tag += 1;
} }
} }
@ -132,24 +136,54 @@ pub fn typo(suggest: &mut String, replace_list: &mut Vec<TokenStream2>) {
while suggest.contains("{{typo") { while suggest.contains("{{typo") {
let (placeholder, args) = eval_placeholder(suggest, "{{typo", "}}"); let (placeholder, args) = eval_placeholder(suggest, "{{typo", "}}");
let string_index; let string_index = if suggest.contains('[') {
if suggest.contains('[') {
let split = suggest[args.to_owned()] let split = suggest[args.to_owned()]
.split(&['[', ']']) .split(&['[', ']'])
.collect::<Vec<&str>>(); .collect::<Vec<&str>>();
let command_index = split[1].parse::<i32>().unwrap(); let command_index = split[1];
if command_index < 0 { if !command_index.contains(':') {
string_index = format!("split_command.len() {}", command_index); let command_index = command_index.parse::<i32>().unwrap();
let index = if command_index < 0 {
format!("split_command.len() {}", command_index)
} else {
command_index.to_string()
};
format!("{}..{} + 1", index, index)
} else { } else {
string_index = command_index.to_string(); let (start, end) = command_index.split_once(':').unwrap();
let start = start.parse::<i32>().unwrap_or(0);
let start_string = if start < 0 {
format!("split_command.len() {}", start)
} else {
start.to_string()
};
let end = end.parse::<i32>();
let end_string = if end.is_err() {
String::from("split_command.len()")
} else {
let end = end.unwrap();
if end < 0 {
format!("split_command.len() {}", end + 1)
} else {
(end + 1).to_string()
}
};
format!("{}..{}", start_string, end_string)
} }
} else { } else {
unreachable!("Typo suggestion must have a command index"); unreachable!("Typo suggestion must have a command index");
} };
let match_list; let match_list;
if suggest.contains('(') { if suggest.contains('(') {
let split = suggest[args.to_owned()] let split = suggest[args.to_owned()]
.split_once("(").unwrap().1.rsplit_once(")").unwrap().0; .split_once("(")
.unwrap()
.1
.rsplit_once(")")
.unwrap()
.0;
match_list = split.split(',').collect::<Vec<&str>>(); match_list = split.split(',').collect::<Vec<&str>>();
} else { } else {
unreachable!("Typo suggestion must have a match list"); unreachable!("Typo suggestion must have a match list");
@ -161,13 +195,19 @@ pub fn typo(suggest: &mut String, replace_list: &mut Vec<TokenStream2>) {
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let command; let command;
if match_list[0].starts_with("eval_shell_command("){ if match_list[0].starts_with("eval_shell_command(") {
let function = match_list.join(","); let function = match_list.join(",");
// add a " after first comma, and a " before last ) // add a " after first comma, and a " before last )
let function = format!("{}\"{}{}", let function = format!(
"{}\"{}{}",
&function[..function.find(',').unwrap() + 1], &function[..function.find(',').unwrap() + 1],
&function[function.find(',').unwrap() + 1..function.len() - 1], "\")"); &function[function.find(',').unwrap() + 1..function.len() - 1],
command = format!("suggest_typo(&split_command[{}], {})", string_index, function); "\")"
);
command = format!(
"suggest_typo(&split_command[{}], &{})",
string_index, function
);
} else { } else {
let match_list = match_list let match_list = match_list
.iter() .iter()
@ -175,15 +215,16 @@ pub fn typo(suggest: &mut String, replace_list: &mut Vec<TokenStream2>) {
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let string_match_list = match_list.join("\".to_string(), \""); let string_match_list = match_list.join("\".to_string(), \"");
let string_match_list = format!("\"{}\".to_string()", string_match_list); let string_match_list = format!("\"{}\".to_string()", string_match_list);
command = format!("suggest_typo(&split_command[{}], vec![{}])", string_index, string_match_list); command = format!(
"suggest_typo(&split_command[{}], &[{}])",
string_index, string_match_list
);
} }
replace_list.push(rtag(tag_name, replace_tag, command)); replace_list.push(rtag(tag_name, replace_tag, command));
suggest.replace_range(placeholder, &tag(tag_name, replace_tag)); suggest.replace_range(placeholder, &tag(tag_name, replace_tag));
replace_tag += 1; replace_tag += 1;
} }
} }
pub fn shell(suggest: &mut String, cmd_list: &mut Vec<String>) { pub fn shell(suggest: &mut String, cmd_list: &mut Vec<String>) {
@ -198,13 +239,16 @@ pub fn shell(suggest: &mut String, cmd_list: &mut Vec<String>) {
} }
} }
pub fn shell_tag(suggest: &mut String, replace_list: &mut Vec<TokenStream2>, cmd_list: Vec<String>) { pub fn shell_tag(
suggest: &mut String,
replace_list: &mut Vec<TokenStream2>,
cmd_list: Vec<String>,
) {
let mut replace_tag = 0; let mut replace_tag = 0;
let tag_name = "shell"; let tag_name = "shell";
for command in cmd_list { for command in cmd_list {
if suggest.contains(&command) { if suggest.contains(&command) {
*suggest = suggest.replace(&command, &tag(tag_name, replace_tag)); *suggest = suggest.replace(&command, &tag(tag_name, replace_tag));
let split = command.split_once(',').unwrap(); let split = command.split_once(',').unwrap();

View file

@ -11,6 +11,6 @@ suggest = [
pattern = [ "no such file or directory" ] pattern = [ "no such file or directory" ]
suggest = [ suggest = [
''' '''
{{command[0}} {{opt::(?:\s)-[\w]+}} {{typo[-1](file)}} ''' {{command[0}} {{opt::(?:\s)-[\w]+}} {{typo[1:](file)}} '''
] ]

View file

@ -46,7 +46,7 @@ pub fn get_best_match_file(input: &str) -> Option<String> {
}) })
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let best_match = find_similar(&exit_dir, dir_files); let best_match = find_similar(&exit_dir, &dir_files);
best_match.as_ref()?; best_match.as_ref()?;
input = format!("{}/{}", input, best_match.unwrap()); input = format!("{}/{}", input, best_match.unwrap());

View file

@ -1,5 +1,5 @@
use std::process::{Stdio, exit}; use std::process::{exit, Stdio};
use std::time::{Instant, Duration}; use std::time::{Duration, Instant};
use regex_lite::Regex; use regex_lite::Regex;
@ -24,7 +24,7 @@ pub fn suggest_command(shell: &str, last_command: &str, error_msg: &str) -> Opti
let last_command = match PRIVILEGE_LIST.contains(&split_command[0].as_str()) { let last_command = match PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
true => &last_command[split_command[0].len() + 1..], true => &last_command[split_command[0].len() + 1..],
false => &last_command, false => last_command,
}; };
let suggest = match_pattern(executable, last_command, error_msg, shell); let suggest = match_pattern(executable, last_command, error_msg, shell);
@ -35,7 +35,7 @@ pub fn suggest_command(shell: &str, last_command: &str, error_msg: &str) -> Opti
return Some(suggest); return Some(suggest);
} }
let suggest = match_pattern("general", &last_command, error_msg, shell); let suggest = match_pattern("general", last_command, error_msg, shell);
if let Some(suggest) = suggest { if let Some(suggest) = suggest {
if PRIVILEGE_LIST.contains(&split_command[0].as_str()) { if PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
return Some(format!("{} {}", split_command[0], suggest)); return Some(format!("{} {}", split_command[0], suggest));
@ -132,31 +132,36 @@ pub fn split_command(command: &str) -> Vec<String> {
split_command split_command
} }
fn suggest_typo(typo: &str, candidates: Vec<String>) -> String { fn suggest_typo(typos: &[String], candidates: &[String]) -> String {
let mut suggestion = typo.to_owned(); let mut path_files = Vec::new();
let mut suggestions = Vec::new();
if candidates.len() == 1 { for typo in typos {
match candidates[0].as_str() { let typo = typo.as_str();
"path" => { if candidates.len() == 1 {
let path_files = get_path_files(); match candidates[0].as_str() {
if let Some(suggest) = find_similar(typo, path_files) { "path" => {
suggestion = suggest; if path_files.is_empty() {
path_files = get_path_files();
};
if let Some(suggest) = find_similar(typo, &path_files) {
suggestions.push(suggest);
};
} }
} "file" => {
"file" => { if let Some(suggest) = get_best_match_file(typo) {
if let Some(suggest) = get_best_match_file(typo) { suggestions.push(suggest);
suggestion = suggest; }
} }
_ => {}
} }
_ => {} } else if let Some(suggest) = find_similar(typo, candidates) {
suggestions.push(suggest);
} }
} else if let Some(suggest) = find_similar(typo, candidates) {
suggestion = suggest;
} }
suggestion suggestions.join(" ")
} }
pub fn find_similar(typo: &str, candidates: Vec<String>) -> Option<String> { pub fn find_similar(typo: &str, candidates: &[String]) -> Option<String> {
let mut min_distance = 10; let mut min_distance = 10;
let mut min_distance_index = None; let mut min_distance_index = None;
for (i, candidate) in candidates.iter().enumerate() { for (i, candidate) in candidates.iter().enumerate() {