use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Default)]
pub struct TitleIndex {
by_name: HashMap<String, Vec<PathBuf>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TitleMatch {
Unique(PathBuf),
Ambiguous(Vec<PathBuf>),
Unknown,
}
impl TitleIndex {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, name: impl Into<String>, path: impl Into<PathBuf>) {
let name = name.into();
if name.trim().is_empty() {
return;
}
let path = path.into();
let paths = self.by_name.entry(name).or_default();
if !paths.contains(&path) {
paths.push(path);
}
}
pub fn resolve(&self, name: &str) -> TitleMatch {
match self.by_name.get(name).map(Vec::as_slice) {
None | Some([]) => TitleMatch::Unknown,
Some([one]) => TitleMatch::Unique(one.clone()),
Some(many) => {
let mut paths = many.to_vec();
paths.sort();
TitleMatch::Ambiguous(paths)
}
}
}
pub fn is_empty(&self) -> bool {
self.by_name.is_empty()
}
pub fn len(&self) -> usize {
self.by_name.len()
}
}
pub fn is_alias_shaped(target: &str) -> bool {
!target.is_empty()
&& !target.contains('/')
&& !target.contains('\\')
&& Path::new(target).extension().is_none()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolves_unique_ambiguous_and_unknown() {
let mut ix = TitleIndex::new();
ix.insert("My File", "notes/a.md");
ix.insert("a", "notes/a.md");
ix.insert("Shared", "one.md");
ix.insert("Shared", "two.md");
assert_eq!(
ix.resolve("My File"),
TitleMatch::Unique(PathBuf::from("notes/a.md"))
);
assert_eq!(
ix.resolve("a"),
TitleMatch::Unique(PathBuf::from("notes/a.md"))
);
assert_eq!(
ix.resolve("Shared"),
TitleMatch::Ambiguous(vec![PathBuf::from("one.md"), PathBuf::from("two.md")])
);
assert_eq!(ix.resolve("nobody"), TitleMatch::Unknown);
}
#[test]
fn blank_names_are_ignored() {
let mut ix = TitleIndex::new();
ix.insert(" ", "a.md");
assert!(ix.is_empty());
}
#[test]
fn alias_shape_excludes_paths_and_extensions() {
assert!(is_alias_shaped("My File"));
assert!(is_alias_shaped("intro"));
assert!(!is_alias_shaped("notes/a.md"));
assert!(!is_alias_shaped("README.md"));
assert!(!is_alias_shaped(""));
}
}