use std::sync::Arc;
use super::typed::{parse_structured_selector, AliasError, DataFormat};
use crate::predicate::{
DetectFileType, EnumMatcher, EnumPredicate, MetadataPredicate, Predicate,
StructuredDataPredicate,
};
pub fn resolve_alias(word: &str) -> Result<Predicate, AliasError> {
match parse_structured_selector(word) {
Ok(Some((format, components))) => {
let predicate = match format {
DataFormat::Yaml => StructuredDataPredicate::YamlExists { path: components },
DataFormat::Json => StructuredDataPredicate::JsonExists { path: components },
DataFormat::Toml => StructuredDataPredicate::TomlExists { path: components },
};
return Ok(Predicate::Structured(predicate));
}
Ok(None) => {
}
Err(e) => {
return Err(AliasError::Structured(e));
}
}
match DetectFileType::from_str(word) {
Ok(file_type) => Ok(Predicate::Metadata(Arc::new(MetadataPredicate::Type(
EnumMatcher::Equals(file_type),
)))),
Err(_) => Err(AliasError::UnknownAlias(word.to_string())),
}
}
pub fn suggest_aliases(word: &str) -> Vec<String> {
let all_aliases = DetectFileType::all_valid_strings();
all_aliases
.iter()
.filter(|&&alias| levenshtein_distance(word, alias) <= 2)
.map(|&s| s.to_string())
.collect()
}
fn levenshtein_distance(a: &str, b: &str) -> usize {
let a_chars: Vec<char> = a.chars().collect();
let b_chars: Vec<char> = b.chars().collect();
let a_len = a_chars.len();
let b_len = b_chars.len();
if a_len == 0 {
return b_len;
}
if b_len == 0 {
return a_len;
}
let mut prev_row: Vec<usize> = (0..=b_len).collect();
let mut curr_row = vec![0; b_len + 1];
for (i, a_char) in a_chars.iter().enumerate() {
curr_row[0] = i + 1;
for (j, b_char) in b_chars.iter().enumerate() {
let cost = usize::from(a_char != b_char);
curr_row[j + 1] = (curr_row[j] + 1) .min(prev_row[j + 1] + 1) .min(prev_row[j] + cost); }
std::mem::swap(&mut prev_row, &mut curr_row);
}
prev_row[b_len]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_type_aliases() {
assert!(resolve_alias("file").is_ok());
assert!(resolve_alias("dir").is_ok());
assert!(resolve_alias("directory").is_ok());
assert!(resolve_alias("symlink").is_ok());
assert!(resolve_alias("link").is_ok());
assert!(resolve_alias("socket").is_ok());
assert!(resolve_alias("sock").is_ok());
assert!(resolve_alias("fifo").is_ok());
assert!(resolve_alias("pipe").is_ok());
assert!(resolve_alias("block").is_ok());
assert!(resolve_alias("blockdev").is_ok());
assert!(resolve_alias("char").is_ok());
assert!(resolve_alias("chardev").is_ok());
}
#[test]
fn test_unknown_alias() {
let result = resolve_alias("unknown");
assert!(matches!(result, Err(AliasError::UnknownAlias(_))));
}
#[test]
fn test_case_insensitive() {
assert!(resolve_alias("FILE").is_ok());
assert!(resolve_alias("Dir").is_ok());
assert!(resolve_alias("SYMLINK").is_ok());
}
#[test]
fn test_suggestions() {
let suggestions = suggest_aliases("fil");
assert!(suggestions.contains(&"file".to_string()));
let suggestions = suggest_aliases("direktory");
assert!(suggestions.contains(&"directory".to_string()));
}
#[test]
fn test_levenshtein_distance() {
assert_eq!(levenshtein_distance("file", "file"), 0);
assert_eq!(levenshtein_distance("file", "fil"), 1);
assert_eq!(levenshtein_distance("directory", "dir"), 6);
assert_eq!(levenshtein_distance("", "test"), 4);
assert_eq!(levenshtein_distance("test", ""), 4);
}
}