diff --git a/Cargo.lock b/Cargo.lock index 5318ea72..659f2671 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -459,6 +459,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "libc" version = "0.2.139" @@ -730,6 +736,7 @@ dependencies = [ "indoc", "lasso", "lazy_static", + "levenshtein", "libflate", "md5", "paste", diff --git a/Cargo.toml b/Cargo.toml index 02977610..27957956 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ clap = { version = "4.1.4", features = ["derive"] } hex-serde = "0.1.0" home = "0.5.4" rand = "0.8.5" +levenshtein = "1.0.5" quick-xml = "0.27.1" libflate = "1.1.0" diff --git a/src/gd/levelstring.rs b/src/gd/levelstring.rs index 03d0a632..f3843d34 100644 --- a/src/gd/levelstring.rs +++ b/src/gd/levelstring.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; use base64::engine::general_purpose; use base64::Engine; use colored::Colorize; +use levenshtein::levenshtein; use libflate::{gzip, zlib}; use quick_xml::events::{BytesText, Event}; use quick_xml::{Reader, Writer}; @@ -60,6 +61,11 @@ fn decrypt_savefile(mut sf: Vec) -> Result, Box> { } } +pub struct Name { + pub name: String, + pub distance: usize +} + pub fn get_level_string( ls: Vec, level_name: Option<&String>, @@ -77,6 +83,8 @@ pub fn get_level_string( let mut k2_detected = false; let mut level_detected = false; + let mut names: Vec = vec![]; + let mut level_name_out = String::new(); loop { @@ -104,6 +112,8 @@ pub fn get_level_string( level_name_out = text.clone(); + names.push(level_name_out.clone()); + k2_detected = false } if level_detected && text == "k4" { @@ -133,7 +143,40 @@ pub fn get_level_string( ).into()); } else if !k4_detected { if let Some(level_name) = level_name { - return Err(BasicError(format!("Level named \"{level_name}\" was not found")).into()); + let mut close: Vec = vec![]; + + for name in &names { + let lev = levenshtein(level_name, name); + if lev <= 3 { + close.push(Name {name: name.to_string(), distance: lev}); + } + } + + close.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap()); + + let mut suggestions = close.iter() + .map(|n| n.name.clone()) + .collect::>(); + + let last_suggestion = suggestions.pop(); + + let message = match close.len() { + 0 => String::new(), + 1 => format!("Did you mean: \"{}\"?", match last_suggestion { + Some(suggestion) => format!("{}", suggestion), + None => String::new() + }), + _ => format!( + "Did you mean: \"{}{}", + suggestions.join("\", \""), + match last_suggestion { + Some(suggestion) => format!("\" or \"{}\"?", suggestion), + None => String::new(), + } + ) + }; + + return Err(BasicError(format!("Level named \"{level_name}\" was not found. {message}")).into()); } else { return Err(BasicError( "No level found! Please create a level for SPWN to operate on".to_string(),