use std::path::Path;
use plotnik_langs::Lang;
pub fn resolve_lang(explicit: Option<&str>, query_path: Option<&Path>) -> Option<Lang> {
if let Some(name) = explicit {
return plotnik_langs::from_name(name);
}
if let Some(path) = query_path
&& path.is_dir()
&& let Some(name) = path.file_name().and_then(|n| n.to_str())
&& let Some((_, ext)) = name.rsplit_once('.')
{
return plotnik_langs::from_ext(ext);
}
None
}
pub fn resolve_lang_required(lang_name: &str) -> Result<Lang, String> {
plotnik_langs::from_name(lang_name).ok_or_else(|| format!("unknown language: '{}'", lang_name))
}
pub fn require_lang(
explicit: Option<&str>,
query_path: Option<&std::path::Path>,
command: &str,
) -> Lang {
if let Some(lang_name) = explicit {
match resolve_lang_required(lang_name) {
Ok(l) => return l,
Err(msg) => {
eprintln!("error: {}", msg);
if let Some(suggestion) = suggest_language(lang_name) {
eprintln!();
eprintln!("Did you mean '{}'?", suggestion);
}
eprintln!();
eprintln!("Run 'plotnik langs' for the full list.");
std::process::exit(1);
}
}
}
if let Some(l) = resolve_lang(None, query_path) {
return l;
}
eprintln!("error: language is required for {}", command);
eprintln!();
eprintln!("hint: use -l <language> to specify the target language");
std::process::exit(1);
}
pub fn suggest_language(input: &str) -> Option<String> {
let input_lower = input.to_lowercase();
plotnik_langs::all()
.into_iter()
.filter(|lang| levenshtein(lang.name(), &input_lower) <= 2)
.min_by_key(|lang| levenshtein(lang.name(), &input_lower))
.map(|lang| lang.name().to_string())
}
fn levenshtein(a: &str, b: &str) -> usize {
let a_chars: Vec<char> = a.chars().collect();
let b_chars: Vec<char> = b.chars().collect();
let m = a_chars.len();
let n = b_chars.len();
if m == 0 {
return n;
}
if n == 0 {
return m;
}
let mut prev = (0..=n).collect::<Vec<_>>();
let mut curr = vec![0; n + 1];
for i in 1..=m {
curr[0] = i;
for j in 1..=n {
let cost = usize::from(a_chars[i - 1] != b_chars[j - 1]);
curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
}
std::mem::swap(&mut prev, &mut curr);
}
prev[n]
}