From 53c7e1fd7b5774b44fa62f391ae83f8a4ac40d88 Mon Sep 17 00:00:00 2001 From: iff Date: Thu, 3 Aug 2023 21:26:41 +0200 Subject: [PATCH] feat: all parse in compilation --- rule_parser/Cargo.toml | 1 + rule_parser/src/lib.rs | 314 +++++++++++++++++++++++++++++++++++++---- rules/git.toml | 2 +- src/files.rs | 41 ++++++ src/main.rs | 1 + src/shell.rs | 114 +++++++-------- src/style.rs | 2 + src/suggestions.rs | 256 +++------------------------------ 8 files changed, 410 insertions(+), 321 deletions(-) create mode 100644 src/files.rs diff --git a/rule_parser/Cargo.toml b/rule_parser/Cargo.toml index f44f0d4..a4dbc97 100644 --- a/rule_parser/Cargo.toml +++ b/rule_parser/Cargo.toml @@ -11,5 +11,6 @@ proc-macro = true [dependencies] syn = "1.0" quote = "1.0" +proc-macro2 = "1.0" toml = "0.7" serde = { version = "1.0", features = ["derive"] } diff --git a/rule_parser/src/lib.rs b/rule_parser/src/lib.rs index a6f4fad..ee0d2e6 100644 --- a/rule_parser/src/lib.rs +++ b/rule_parser/src/lib.rs @@ -1,14 +1,15 @@ use std::path::Path; use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; #[proc_macro] pub fn parse_rules(input: TokenStream) -> TokenStream { let directory = input.to_string().trim_matches('"').to_owned(); let rules = get_rules(directory); - let string_hashmap = gen_string_hashmap(rules); - string_hashmap.parse().unwrap() + gen_match_rules(rules) } #[derive(serde::Deserialize)] @@ -38,35 +39,298 @@ fn get_rules(directory: String) -> Vec { rules } -fn gen_string_hashmap(rules: Vec) -> String { - let mut string_hashmap = String::from("HashMap::from(["); - for rule in rules { - let command = rule.command.to_owned(); - string_hashmap.push_str(&format!("(\"{}\", vec![", command)); - for match_err in rule.match_err { - let pattern = match_err - .pattern +fn gen_match_rules(rules: Vec) -> TokenStream { + let command = rules + .iter() + .map(|x| x.command.to_owned()) + .collect::>(); + let command_matches = rules + .iter() + .map(|x| { + x.match_err .iter() - .map(|x| x.to_lowercase()) - .collect::>(); - let suggest = match_err. - suggest - .iter() - .map(|x| x.to_lowercase()) - .collect::>(); - string_hashmap.push_str(&format!( - "(vec![\"{}\"], vec![r###\"{}\"###]),", - pattern.join("\", \""), - suggest.join("\"###, r###\"") - )); + .map(|x| { + let pattern = x + .pattern + .iter() + .map(|x| x.to_lowercase()) + .collect::>(); + let suggests = x + .suggest + .iter() + .map(|x| x.to_lowercase()) + .collect::>(); + (pattern, suggests) + }) + .collect::, Vec)>>() + }) + .collect::, Vec)>>>(); + + let mut matches_tokens = Vec::new(); + + for match_err in command_matches { + let mut suggestion_tokens = Vec::new(); + let mut patterns_tokens = Vec::new(); + for (pattern, suggests) in match_err { + // let mut match_condition = Vec::new(); + let mut pattern_suggestions = Vec::new(); + for suggest in suggests { + let (suggestion_no_condition, conditions) = parse_conditions(&suggest); + let suggest = eval_suggest(&suggestion_no_condition); + let suggestion = quote! { + if #(#conditions)&&* { + #suggest; + }; + }; + pattern_suggestions.push(suggestion); + }; + let match_tokens = quote! { + #(#pattern_suggestions)* + }; + + suggestion_tokens.push(match_tokens); + + let string_patterns = pattern.join("\", \""); + let string_patterns: TokenStream2 = format!("vec![\"{}\"]", string_patterns).parse().unwrap(); + patterns_tokens.push(string_patterns); } - string_hashmap.push_str("]),"); + + matches_tokens.push(quote!{ + #( + for pattern in #patterns_tokens { + if error_msg.contains(pattern) { + #suggestion_tokens; + }; + })* + }) } - string_hashmap.push_str("])"); - string_hashmap + quote! { + let mut last_command = last_command.to_string(); + match executable { + #( + #command => { + #matches_tokens + return None; + } + )* + _ => { return None; } + }; + }.into() } fn parse_file(file: &Path) -> Rule { let file = std::fs::read_to_string(file).expect("Failed to read file."); toml::from_str(&file).expect("Failed to parse toml.") } + +fn parse_conditions(suggest: &str) -> (String, Vec) { + let mut eval_conditions = Vec::new(); + if suggest.starts_with('#') { + let mut lines = suggest.lines().collect::>(); + 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::>(); + + 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, + }; + let evaluated_condition = eval_condition(condition, arg); + + eval_conditions.push(quote!{#evaluated_condition == !#reverse}); + } + let suggest = lines.join("\n"); + return (suggest, eval_conditions); + } + (suggest.to_owned(), vec![quote!{true}]) +} + +fn eval_condition(condition: &str, arg: &str) -> TokenStream2 { + match condition { + "executable" => { + quote!{ + std::process::Command::new("which") + .arg(#arg) + .output() + .expect("failed to execute process") + .status + .success() + } + }, + "err_contains" => quote!{error_msg.contains(#arg)}, + "cmd_contains" => quote!{command.contains(#arg)}, + _ => unreachable!("Unknown condition when evaluation condition: {}", condition), + } +} + +fn eval_suggest(suggest: &str) -> TokenStream2 { + let mut suggest = suggest.to_owned(); + let rtag = |x: i32, y: String| { + let tag = format!("tag{} = {}", x, y); + let tag: TokenStream2 = tag.parse().unwrap(); + tag + }; + + let tag = |x: i32| { + let tag = format!("{{tag{}}}", x); + let tag = tag.as_str(); + let tag = tag.to_owned(); + tag + }; + let mut replace_tag = 0; + let mut replace_list = Vec::new(); + + if suggest.contains("{{command}}") { + let command = "last_command".to_string(); + + replace_list.push(rtag(replace_tag, command)); + suggest = suggest.replace("{{command}}", &tag(replace_tag)); + replace_tag += 1; + } + + let mut opt_lists = Vec::new(); + while suggest.contains("{{opt::") { + let placeholder_start = "{{opt::"; + let placeholder_end = "}}"; + + let start_index = suggest.find(placeholder_start).unwrap(); + let end_index = suggest[start_index..].find(placeholder_end).unwrap() + + start_index + + placeholder_end.len(); + + let placeholder = start_index..end_index; + + let args = start_index + placeholder_start.len()..end_index - placeholder_end.len(); + let opt = &suggest[args.to_owned()]; + let regex = opt.trim(); + let current_tag = tag(replace_tag); + let token_tag: TokenStream2 = format!("tag{}", replace_tag).parse().unwrap(); + let command = quote! { + let #token_tag = opt_regex(#regex, &mut last_command); + }; + opt_lists.push(command); + + replace_list.push(rtag(replace_tag, current_tag.to_owned())); + suggest.replace_range(placeholder, ¤t_tag); + replace_tag += 1; + } + + while suggest.contains("{{command") { + let placeholder_start = "{{command"; + let placeholder_end = "}}"; + + let start_index = suggest.find(placeholder_start).unwrap(); + let end_index = suggest[start_index..].find(placeholder_end).unwrap() + + start_index + + placeholder_end.len(); + + let placeholder = start_index..end_index; + + let args = start_index + placeholder_start.len()..end_index - placeholder_end.len(); + 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(); + let start = start.parse::().unwrap_or(0); + if start < 0 { + start_string = format!("split_command.len() {}", start); + }; + let end_string; + let parsed_end = end.parse::(); + if parsed_end.is_err() { + end_string = String::from("split_command.len()"); + } else { + let end = parsed_end.clone().unwrap(); + if end < 0 { + end_string = format!("split_command.len() {}", end + 1); + } else { + end_string = (end + 1).to_string(); + } + }; + + let command = format!{r#"split_command[{}..{}].join(" ")"#, start_string, end_string}; + + replace_list.push(rtag(replace_tag, command)); + suggest.replace_range(placeholder, &tag(replace_tag)); + replace_tag += 1; + } else { + let range = range.parse::().unwrap_or(0); + let command = format!("split_command[{}]", range); + + replace_list.push(rtag(replace_tag, command)); + suggest.replace_range(placeholder, &tag(replace_tag)); + replace_tag += 1; + } + } + + while suggest.contains("{{typo") { + let placeholder_start = "{{typo"; + let placeholder_end = "}}"; + + let start_index = suggest.find(placeholder_start).unwrap(); + let end_index = suggest[start_index..].find(placeholder_end).unwrap() + + start_index + + placeholder_end.len(); + + let placeholder = start_index..end_index; + let args = start_index + placeholder_start.len()..end_index - placeholder_end.len(); + + 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 { + // command_index += split_command.len() as i32; + string_index = format!("split_command.len() {}", command_index); + } else { + string_index = command_index.to_string(); + } + } else { + unreachable!("Typo suggestion must have a command index"); + } + let mut match_list = Vec::new(); + if suggest.contains('(') { + let split = suggest[args.to_owned()] + .split(&['(', ')']) + .collect::>(); + match_list = split[1].trim().split(',').collect::>(); + } + + let match_list = match_list + .iter() + .map(|s| s.trim().to_string()) + .collect::>(); + let string_match_list = match_list.join(r#"".to_string(), ""#); + let string_match_list = format!(r#""{}".to_string()"#, string_match_list); + + let command = format!("suggest_typo(&split_command[{}], vec![{}])", string_index, string_match_list); + + replace_list.push(rtag(replace_tag, command)); + suggest.replace_range(placeholder, &tag(replace_tag)); + replace_tag += 1; + } + + quote! { + #(#opt_lists)* + let split_command = split_command(&last_command); + return Some(format!{#suggest, #(#replace_list),*}); + } +} diff --git a/rules/git.toml b/rules/git.toml index a2eb84b..f60c745 100644 --- a/rules/git.toml +++ b/rules/git.toml @@ -39,7 +39,7 @@ range-diff, rebase, reset, restore, -revert +revert, rm, scalar, shortlog, diff --git a/src/files.rs b/src/files.rs new file mode 100644 index 0000000..e3facfa --- /dev/null +++ b/src/files.rs @@ -0,0 +1,41 @@ +pub fn get_path_files() -> Vec { + let path = std::env::var("PATH").unwrap(); + let path = path.split(':').collect::>(); + let mut all_executable = vec![]; + for p in path { + let files = match std::fs::read_dir(p) { + Ok(files) => files, + Err(_) => continue, + }; + for file in files { + let file = file.unwrap(); + let file_name = file.file_name().into_string().unwrap(); + all_executable.push(file_name); + } + } + all_executable +} + +pub fn get_directory_files(input: &str) -> Vec { + let mut input = input.trim_matches(|c| c == '\'' || c == '"').to_owned(); + let files = loop { + match std::fs::read_dir(&input) { + Ok(files) => break files, + Err(_) => { + if let Some((dirs, _)) = input.rsplit_once('/') { + input = dirs.to_owned(); + } else { + break std::fs::read_dir("./").unwrap(); + } + } + } + }; + + let mut all_files = vec![]; + for file in files { + let file = file.unwrap(); + let file_name = file.path().to_str().unwrap().to_owned(); + all_files.push(file_name); + } + all_files +} diff --git a/src/main.rs b/src/main.rs index 56a540e..971bbc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use colored::Colorize; mod args; +mod files; mod shell; mod style; mod suggestions; diff --git a/src/shell.rs b/src/shell.rs index 7239a33..d6aed82 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -1,5 +1,5 @@ -use std::process::exit; use std::io::prelude::*; +use std::process::exit; pub const PRIVILEGE_LIST: [&str; 2] = ["sudo", "doas"]; @@ -95,71 +95,71 @@ pub fn last_command_expanded_alias(shell: &str) -> String { } pub fn print_command_with_env(shell: &str, binary_path: &str) { - let last_command; - let alias; + let last_command; + let alias; - match shell { - "bash" => { - last_command = "$(history 2)"; - alias = "$(alias)" - } - "zsh" => { - last_command = "$(fc -ln -1)"; - alias = "$(alias)" - } - "fish" => { - last_command = "$(history | head -n 1)"; - alias = "$(alias)"; - } - "nu" | "nush" | "nushell" => { - last_command = "(history | last).command"; - alias = "\"\""; - let command = format!( - "with-env {{ _PR_LAST_COMMAND : {},\ + match shell { + "bash" => { + last_command = "$(history 2)"; + alias = "$(alias)" + } + "zsh" => { + last_command = "$(fc -ln -1)"; + alias = "$(alias)" + } + "fish" => { + last_command = "$(history | head -n 1)"; + alias = "$(alias)"; + } + "nu" | "nush" | "nushell" => { + last_command = "(history | last).command"; + alias = "\"\""; + let command = format!( + "with-env {{ _PR_LAST_COMMAND : {},\ _PR_ALIAS : {},\ _PR_SHELL : nu }} \ {{ {} }}", - last_command, alias, binary_path - ); - println!("{}\n", command); - println!("Add following to your config file? (Y/n)"); - let alias = format!("alias f = {}", command); - println!("{}", alias); - let mut input = String::new(); - std::io::stdin().read_line(&mut input).unwrap(); - match input.trim() { - "Y" | "y" | "" => { - let output = std::process::Command::new("nu") - .arg("-c") - .arg("echo $nu.config-path") - .output() - .expect("Failed to execute process"); - let config_path = String::from_utf8_lossy(&output.stdout); - let mut file = std::fs::OpenOptions::new() - .write(true) - .append(true) - .open(config_path.trim()) - .expect("Failed to open config file"); + last_command, alias, binary_path + ); + println!("{}\n", command); + println!("Add following to your config file? (Y/n)"); + let alias = format!("alias f = {}", command); + println!("{}", alias); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + match input.trim() { + "Y" | "y" | "" => { + let output = std::process::Command::new("nu") + .arg("-c") + .arg("echo $nu.config-path") + .output() + .expect("Failed to execute process"); + let config_path = String::from_utf8_lossy(&output.stdout); + let mut file = std::fs::OpenOptions::new() + .write(true) + .append(true) + .open(config_path.trim()) + .expect("Failed to open config file"); - writeln!(file, "{}", alias).expect("Failed to write to config file"); - } - _ => std::process::exit(0), - }; - std::process::exit(0); - } - _ => { - println!("Unknown shell: {}", shell); - std::process::exit(1); - } + writeln!(file, "{}", alias).expect("Failed to write to config file"); + } + _ => std::process::exit(0), + }; + std::process::exit(0); } + _ => { + println!("Unknown shell: {}", shell); + std::process::exit(1); + } + } - println!( - "\ + println!( + "\ _PR_LAST_COMMAND=\"{}\" \ _PR_ALIAS=\"{}\" \ _PR_SHELL=\"{}\" \ \"{}\"", - last_command, alias, shell, binary_path - ); - std::process::exit(0); + last_command, alias, shell, binary_path + ); + std::process::exit(0); } diff --git a/src/style.rs b/src/style.rs index 2ee9883..43b1b48 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,6 +1,8 @@ use crate::suggestions::split_command; use colored::*; +// to_string() is necessary here, otherwise there won't be color in the output +#[warn(clippy::unnecessary_to_owned)] pub fn highlight_difference(suggested_command: &str, last_command: &str) -> String { let split_suggested_command = split_command(suggested_command); let split_last_command = split_command(last_command); diff --git a/src/suggestions.rs b/src/suggestions.rs index beb8a7e..9110f5b 100644 --- a/src/suggestions.rs +++ b/src/suggestions.rs @@ -1,8 +1,8 @@ use regex_lite::Regex; -use std::collections::HashMap; use rule_parser::parse_rules; +use crate::files::{get_directory_files, get_path_files}; use crate::shell::{command_output, PRIVILEGE_LIST}; use crate::style::highlight_difference; @@ -17,14 +17,12 @@ pub fn suggest_command(shell: &str, last_command: &str) -> Option { if !PRIVILEGE_LIST.contains(&executable) { let suggest = match_pattern("privilege", last_command, &err); - if let Some(suggest) = suggest { - let suggest = eval_suggest(&suggest, last_command); - return Some(suggest); + if suggest.is_some() { + return suggest; } } let suggest = match_pattern(executable, last_command, &err); if let Some(suggest) = suggest { - let suggest = eval_suggest(&suggest, last_command); if PRIVILEGE_LIST.contains(&executable) { return Some(format!("{} {}", split_command[0], suggest)); } @@ -33,201 +31,25 @@ pub fn suggest_command(shell: &str, last_command: &str) -> Option { let suggest = match_pattern("general", last_command, &err); if let Some(suggest) = suggest { - let suggest = eval_suggest(&suggest, last_command); return Some(suggest); } None } -fn match_pattern(executable: &str, command: &str, error_msg: &str) -> Option { - let rules = parse_rules!("rules"); - if rules.contains_key(executable) { - let suggest = rules.get(executable).unwrap(); - for (pattern, suggest) in suggest { - for pattern in pattern { - if error_msg.contains(pattern) { - for suggest in suggest { - if let Some(suggest) = check_condition(suggest, command, error_msg) { - return Some(suggest); - } - } - } - } - } - None - } else { - None - } +fn match_pattern(executable: &str, last_command: &str, error_msg: &str) -> Option { + parse_rules!("rules"); } -fn check_condition(suggest: &str, command: &str, error_msg: &str) -> Option { - if !suggest.starts_with('#') { - return Some(suggest.to_owned()); +fn opt_regex(regex: &str, command: &mut String) -> String { + let regex = Regex::new(regex).unwrap(); + let opts = regex + .find_iter(command) + .map(|cap| cap.as_str().to_owned()) + .collect::>(); + for opt in opts.clone() { + *command = command.replace(&opt, ""); } - let mut lines = suggest.lines().collect::>(); - let mut conditions = String::new(); - for (i, line) in lines[0..].iter().enumerate() { - conditions.push_str(line); - if line.ends_with(']') { - lines = lines[i..].to_vec(); - break; - } - } - let conditions = conditions - .trim_start_matches(['#', '[']) - .trim_end_matches(']') - .split(',') - .collect::>(); - - 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, command, error_msg) == reverse { - return None; - } - } - Some(lines[1..].join("\n")) -} - -fn eval_condition(condition: &str, arg: &str, command: &str, error_msg: &str) -> bool { - match condition { - "executable" => { - let output = std::process::Command::new("which") - .arg(arg) - .output() - .expect("failed to execute process"); - output.status.success() - } - "err_contains" => error_msg.contains(arg), - "cmd_contains" => command.contains(arg), - _ => unreachable!("Unknown condition when evaluation condition: {}", condition), - } -} - -fn eval_suggest(suggest: &str, last_command: &str) -> String { - let mut suggest = suggest.to_owned(); - let mut last_command = last_command.to_owned(); - if suggest.contains("{{command}}") { - suggest = suggest.replace("{{command}}", &last_command); - } - - while suggest.contains("{{opt::") { - let placeholder_start = "{{opt::"; - let placeholder_end = "}}"; - - let start_index = suggest.find(placeholder_start).unwrap(); - let end_index = suggest[start_index..].find(placeholder_end).unwrap() - + start_index - + placeholder_end.len(); - - let placeholder = start_index..end_index; - - let args = start_index + placeholder_start.len()..end_index - placeholder_end.len(); - let opt = &suggest[args.to_owned()]; - let regex = opt.trim(); - let regex = Regex::new(regex).unwrap(); - let opts = regex - .find_iter(&last_command) - .map(|cap| cap.as_str().to_owned()) - .collect::>(); - - suggest.replace_range(placeholder, &opts.join(" ")); - for opt in opts { - last_command = last_command.replace(&opt, ""); - } - } - - let split_command = split_command(&last_command); - - while suggest.contains("{{command") { - let placeholder_start = "{{command"; - let placeholder_end = "}}"; - - let start_index = suggest.find(placeholder_start).unwrap(); - let end_index = suggest[start_index..].find(placeholder_end).unwrap() - + start_index - + placeholder_end.len(); - - let placeholder = start_index..end_index; - - let args = start_index + placeholder_start.len()..end_index - placeholder_end.len(); - let range = suggest[args.to_owned()].trim_matches(|c| c == '[' || c == ']'); - if let Some((start, end)) = range.split_once(':') { - let start = { - let mut start = start.parse::().unwrap_or(0); - if start < 0 { - start += split_command.len() as i32; - } - start as usize - }; - let end = { - let mut end = end.parse::().unwrap_or(split_command.len() as i32 - 1) + 1; - if end < 0 { - end += split_command.len() as i32; - } - end as usize - }; - let command = split_command[start..end].join(" "); - - suggest.replace_range(placeholder, &command); - } else { - let range = range.parse::().unwrap_or(0); - let command = split_command[range].to_owned(); - suggest.replace_range(placeholder, &command); - } - } - - while suggest.contains("{{typo") { - let placeholder_start = "{{typo"; - let placeholder_end = "}}"; - - let start_index = suggest.find(placeholder_start).unwrap(); - let end_index = suggest[start_index..].find(placeholder_end).unwrap() - + start_index - + placeholder_end.len(); - - let placeholder = start_index..end_index; - let args = start_index + placeholder_start.len()..end_index - placeholder_end.len(); - - let mut command_index; - let mut match_list = vec![]; - if suggest.contains('[') { - let split = suggest[args.to_owned()] - .split(&['[', ']']) - .collect::>(); - command_index = split[1].parse::().unwrap(); - if command_index < 0 { - command_index += split_command.len() as i32; - } - } else { - unreachable!("Typo suggestion must have a command index"); - } - if suggest.contains('(') { - let split = suggest[args.to_owned()] - .split(&['(', ')']) - .collect::>(); - match_list = split[1].split(',').collect::>(); - } - - let match_list = match_list - .iter() - .map(|s| s.trim().to_string()) - .collect::>(); - let command_index = command_index as usize; - let suggestion = suggest_typo(&split_command[command_index], match_list.clone()); - - suggest.replace_range(placeholder, &suggestion); - } - - suggest + opts.join(" ") } pub fn split_command(command: &str) -> Vec { @@ -247,68 +69,26 @@ fn suggest_typo(typo: &str, candidates: Vec) -> String { match candidates[0].as_str() { "path" => { let path_files = get_path_files(); - if let Some(suggest) = find_fimilar(typo, path_files) { + if let Some(suggest) = find_similar(typo, path_files) { suggestion = suggest; } } "file" => { let files = get_directory_files(typo); - if let Some(suggest) = find_fimilar(typo, files) { + if let Some(suggest) = find_similar(typo, files) { suggestion = suggest; } } _ => {} } - } else if let Some(suggest) = find_fimilar(typo, candidates) { + } else if let Some(suggest) = find_similar(typo, candidates) { suggestion = suggest; } suggestion } -fn get_path_files() -> Vec { - let path = std::env::var("PATH").unwrap(); - let path = path.split(':').collect::>(); - let mut all_executable = vec![]; - for p in path { - let files = match std::fs::read_dir(p) { - Ok(files) => files, - Err(_) => continue, - }; - for file in files { - let file = file.unwrap(); - let file_name = file.file_name().into_string().unwrap(); - all_executable.push(file_name); - } - } - all_executable -} - -fn get_directory_files(input: &str) -> Vec { - let mut input = input.trim_matches(|c| c == '\'' || c == '"').to_owned(); - let files = loop { - match std::fs::read_dir(&input) { - Ok(files) => break files, - Err(_) => { - if let Some((dirs, _)) = input.rsplit_once('/') { - input = dirs.to_owned(); - } else { - break std::fs::read_dir("./").unwrap(); - } - } - } - }; - - let mut all_files = vec![]; - for file in files { - let file = file.unwrap(); - let file_name = file.path().to_str().unwrap().to_owned(); - all_files.push(file_name); - } - all_files -} - -fn find_fimilar(typo: &str, candidates: Vec) -> Option { +fn find_similar(typo: &str, candidates: Vec) -> Option { let mut min_distance = 10; let mut min_distance_index = None; for (i, candidate) in candidates.iter().enumerate() {