From 2a5fa8a950e78331d8b4eaed7c31f3e11bcd8074 Mon Sep 17 00:00:00 2001 From: iff Date: Fri, 11 Aug 2023 00:41:16 +0200 Subject: [PATCH] feat: range substitution for typos --- rule_parser/src/replaces.rs | 102 ++++++++++++++++++++++++++---------- rules/rm.toml | 2 +- src/files.rs | 2 +- src/suggestions.rs | 49 +++++++++-------- 4 files changed, 102 insertions(+), 53 deletions(-) diff --git a/rule_parser/src/replaces.rs b/rule_parser/src/replaces.rs index 8280441..55f12d7 100644 --- a/rule_parser/src/replaces.rs +++ b/rule_parser/src/replaces.rs @@ -1,24 +1,26 @@ use proc_macro2::TokenStream as TokenStream2; 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: TokenStream2 = tag.parse().unwrap(); tag } -fn tag (name: &str, x: i32) -> String { +fn tag(name: &str, x: i32) -> String { let tag = format!("{{{}{}}}", name, x); let tag = tag.as_str(); let tag = tag.to_owned(); tag } -fn eval_placeholder(string: &str, start: &str, end: &str) -> (std::ops::Range, std::ops::Range) { +fn eval_placeholder( + string: &str, + start: &str, + end: &str, +) -> (std::ops::Range, std::ops::Range) { let start_index = string.find(start).unwrap(); - let end_index = string[start_index..].find(end).unwrap() - + start_index - + end.len(); + let end_index = string[start_index..].find(end).unwrap() + start_index + end.len(); let placeholder = start_index..end_index; @@ -27,7 +29,11 @@ fn eval_placeholder(string: &str, start: &str, end: &str) -> (std::ops::Range, opt_list: &mut Vec) { +pub fn opts( + suggest: &mut String, + replace_list: &mut Vec, + opt_list: &mut Vec, +) { let mut replace_tag = 0; let tag_name = "opts"; while suggest.contains("{{opt::") { @@ -82,13 +88,12 @@ pub fn err(suggest: &mut String, replace_list: &mut Vec) { } } - pub fn command(suggest: &mut String, replace_list: &mut Vec) { let mut replace_tag = 0; let tag_name = "command"; 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_string = start.to_string(); @@ -109,19 +114,18 @@ pub fn command(suggest: &mut String, replace_list: &mut Vec) { } }; - 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)); suggest.replace_range(placeholder, &tag(tag_name, replace_tag)); - replace_tag += 1; } else { let range = range.parse::().unwrap_or(0); let command = format!("split_command[{}]", range); replace_list.push(rtag(tag_name, replace_tag, command)); 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) { while suggest.contains("{{typo") { let (placeholder, args) = eval_placeholder(suggest, "{{typo", "}}"); - let string_index; - if suggest.contains('[') { + let string_index = if suggest.contains('[') { let split = suggest[args.to_owned()] .split(&['[', ']']) .collect::>(); - let command_index = split[1].parse::().unwrap(); - if command_index < 0 { - string_index = format!("split_command.len() {}", command_index); + let command_index = split[1]; + if !command_index.contains(':') { + let command_index = command_index.parse::().unwrap(); + + let index = if command_index < 0 { + format!("split_command.len() {}", command_index) + } else { + command_index.to_string() + }; + format!("{}..{} + 1", index, index) } else { - string_index = command_index.to_string(); + let (start, end) = command_index.split_once(':').unwrap(); + let start = start.parse::().unwrap_or(0); + let start_string = if start < 0 { + format!("split_command.len() {}", start) + } else { + start.to_string() + }; + let end = end.parse::(); + 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 { 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_once("(") + .unwrap() + .1 + .rsplit_once(")") + .unwrap() + .0; match_list = split.split(',').collect::>(); } else { unreachable!("Typo suggestion must have a match list"); @@ -161,13 +195,19 @@ pub fn typo(suggest: &mut String, replace_list: &mut Vec) { .collect::>(); 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(","); // 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.len() - 1], "\")"); - command = format!("suggest_typo(&split_command[{}], {})", string_index, function); + &function[function.find(',').unwrap() + 1..function.len() - 1], + "\")" + ); + command = format!( + "suggest_typo(&split_command[{}], &{})", + string_index, function + ); } else { let match_list = match_list .iter() @@ -175,15 +215,16 @@ pub fn typo(suggest: &mut String, replace_list: &mut Vec) { .collect::>(); let string_match_list = match_list.join("\".to_string(), \""); 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)); suggest.replace_range(placeholder, &tag(tag_name, replace_tag)); replace_tag += 1; } - } pub fn shell(suggest: &mut String, cmd_list: &mut Vec) { @@ -198,13 +239,16 @@ pub fn shell(suggest: &mut String, cmd_list: &mut Vec) { } } -pub fn shell_tag(suggest: &mut String, replace_list: &mut Vec, cmd_list: Vec) { +pub fn shell_tag( + suggest: &mut String, + replace_list: &mut Vec, + cmd_list: Vec, +) { let mut replace_tag = 0; let tag_name = "shell"; for command in cmd_list { if suggest.contains(&command) { - *suggest = suggest.replace(&command, &tag(tag_name, replace_tag)); let split = command.split_once(',').unwrap(); diff --git a/rules/rm.toml b/rules/rm.toml index f91c8a1..6d52ab6 100644 --- a/rules/rm.toml +++ b/rules/rm.toml @@ -11,6 +11,6 @@ suggest = [ pattern = [ "no such file or directory" ] suggest = [ ''' -{{command[0}} {{opt::(?:\s)-[\w]+}} {{typo[-1](file)}} ''' +{{command[0}} {{opt::(?:\s)-[\w]+}} {{typo[1:](file)}} ''' ] diff --git a/src/files.rs b/src/files.rs index 99c35f9..59b6c84 100644 --- a/src/files.rs +++ b/src/files.rs @@ -46,7 +46,7 @@ pub fn get_best_match_file(input: &str) -> Option { }) .collect::>(); - let best_match = find_similar(&exit_dir, dir_files); + let best_match = find_similar(&exit_dir, &dir_files); best_match.as_ref()?; input = format!("{}/{}", input, best_match.unwrap()); diff --git a/src/suggestions.rs b/src/suggestions.rs index 05e7cdb..c7ceaf2 100644 --- a/src/suggestions.rs +++ b/src/suggestions.rs @@ -1,5 +1,5 @@ -use std::process::{Stdio, exit}; -use std::time::{Instant, Duration}; +use std::process::{exit, Stdio}; +use std::time::{Duration, Instant}; 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()) { true => &last_command[split_command[0].len() + 1..], - false => &last_command, + false => last_command, }; 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); } - 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 PRIVILEGE_LIST.contains(&split_command[0].as_str()) { return Some(format!("{} {}", split_command[0], suggest)); @@ -132,31 +132,36 @@ pub fn split_command(command: &str) -> Vec { split_command } -fn suggest_typo(typo: &str, candidates: Vec) -> String { - let mut suggestion = typo.to_owned(); - - if candidates.len() == 1 { - match candidates[0].as_str() { - "path" => { - let path_files = get_path_files(); - if let Some(suggest) = find_similar(typo, path_files) { - suggestion = suggest; +fn suggest_typo(typos: &[String], candidates: &[String]) -> String { + let mut path_files = Vec::new(); + 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 path_files.is_empty() { + path_files = get_path_files(); + }; + if let Some(suggest) = find_similar(typo, &path_files) { + suggestions.push(suggest); + }; } - } - "file" => { - if let Some(suggest) = get_best_match_file(typo) { - suggestion = suggest; + "file" => { + if let Some(suggest) = get_best_match_file(typo) { + suggestions.push(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) -> Option { +pub fn find_similar(typo: &str, candidates: &[String]) -> Option { let mut min_distance = 10; let mut min_distance_index = None; for (i, candidate) in candidates.iter().enumerate() {