use std::collections::BTreeMap;
use std::fmt;
use super::task::TaskId;
use super::task_path::TaskPath;
#[derive(Debug)]
struct TrieNode {
task: Option<TaskId>,
children: BTreeMap<String, TrieNode>,
}
impl TrieNode {
fn new() -> Self {
Self {
task: None,
children: BTreeMap::new(),
}
}
fn is_empty(&self) -> bool {
self.task.is_none() && self.children.is_empty()
}
fn collect_all(
&self,
prefix: &str,
result: &mut Vec<(TaskPath, TaskId)>,
) {
if let Some(id) = self.task {
if let Ok(path) = TaskPath::new(prefix) {
result.push((path, id));
}
}
for (component, child) in &self.children {
let child_path = if prefix == "/" {
format!("/{}", component)
} else {
format!("{}/{}", prefix, component)
};
child.collect_all(&child_path, result);
}
}
fn glob_walk(
&self,
prefix: &str,
pattern: &[&str],
result: &mut Vec<(TaskPath, TaskId)>,
) {
if pattern.is_empty() {
if let Some(id) = self.task {
if let Ok(path) = TaskPath::new(prefix) {
result.push((path, id));
}
}
return;
}
let pat = pattern[0];
let rest = &pattern[1..];
if pat == "**" {
self.glob_walk(prefix, rest, result);
for (component, child) in &self.children {
let child_path = if prefix == "/" {
format!("/{}", component)
} else {
format!("{}/{}", prefix, component)
};
child.glob_walk(&child_path, pattern, result);
}
} else if pat == "*" {
for (component, child) in &self.children {
let child_path = if prefix == "/" {
format!("/{}", component)
} else {
format!("{}/{}", prefix, component)
};
child.glob_walk(&child_path, rest, result);
}
} else {
if let Some(child) = self.children.get(pat) {
let child_path = if prefix == "/" {
format!("/{}", pat)
} else {
format!("{}/{}", prefix, pat)
};
child.glob_walk(&child_path, rest, result);
}
}
}
}
#[derive(Debug)]
pub struct PathConflictError {
pub path: TaskPath,
pub existing_id: TaskId,
}
impl fmt::Display for PathConflictError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"path {} already occupied by task {:?}",
self.path, self.existing_id
)
}
}
impl std::error::Error for PathConflictError {}
pub struct PathTrie {
root: TrieNode,
}
impl PathTrie {
pub fn new() -> Self {
Self {
root: TrieNode::new(),
}
}
pub fn insert(
&mut self,
path: &TaskPath,
id: TaskId,
) -> Result<(), PathConflictError> {
let mut node = &mut self.root;
for component in path.components() {
node = node
.children
.entry(component.to_string())
.or_insert_with(TrieNode::new);
}
if let Some(existing) = node.task {
return Err(PathConflictError {
path: path.clone(),
existing_id: existing,
});
}
node.task = Some(id);
Ok(())
}
pub fn remove(&mut self, path: &TaskPath) -> Option<TaskId> {
let components: Vec<&str> = path.components().collect();
Self::remove_recursive(&mut self.root, &components)
}
fn remove_recursive(
node: &mut TrieNode,
components: &[&str],
) -> Option<TaskId> {
if components.is_empty() {
return node.task.take();
}
let component = components[0];
let rest = &components[1..];
let result = if let Some(child) = node.children.get_mut(component) {
Self::remove_recursive(child, rest)
} else {
return None;
};
if let Some(child) = node.children.get(component) {
if child.is_empty() {
node.children.remove(component);
}
}
result
}
pub fn resolve(&self, path: &TaskPath) -> Option<TaskId> {
let mut node = &self.root;
for component in path.components() {
node = node.children.get(component)?;
}
node.task
}
pub fn children(
&self,
path: &TaskPath,
) -> Vec<(String, Option<TaskId>)> {
let node = self.walk_to(path);
let Some(node) = node else {
return Vec::new();
};
node
.children
.iter()
.map(|(name, child)| (name.clone(), child.task))
.collect()
}
pub fn descendants(
&self,
path: &TaskPath,
) -> Vec<(TaskPath, TaskId)> {
let node = self.walk_to(path);
let Some(node) = node else {
return Vec::new();
};
let mut result = Vec::new();
for (component, child) in &node.children {
let child_path = if path.as_str() == "/" {
format!("/{}", component)
} else {
format!("{}/{}", path.as_str(), component)
};
child.collect_all(&child_path, &mut result);
}
result
}
pub fn glob(&self, pattern: &str) -> Vec<(TaskPath, TaskId)> {
if !pattern.starts_with('/') {
return Vec::new();
}
let parts: Vec<&str> =
pattern[1..].split('/').filter(|c| !c.is_empty()).collect();
let mut result = Vec::new();
self.root.glob_walk("/", &parts, &mut result);
result
}
pub fn iter(&self) -> Vec<(TaskPath, TaskId)> {
let mut result = Vec::new();
self.root.collect_all("/", &mut result);
result
}
fn walk_to(&self, path: &TaskPath) -> Option<&TrieNode> {
let mut node = &self.root;
for component in path.components() {
node = node.children.get(component)?;
}
Some(node)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn path(s: &str) -> TaskPath {
TaskPath::new(s).unwrap()
}
#[test]
fn test_insert_and_resolve() {
let mut trie = PathTrie::new();
trie.insert(&path("/web"), TaskId(1)).unwrap();
trie.insert(&path("/services/api"), TaskId(2)).unwrap();
trie.insert(&path("/services/worker"), TaskId(3)).unwrap();
assert_eq!(trie.resolve(&path("/web")), Some(TaskId(1)));
assert_eq!(trie.resolve(&path("/services/api")), Some(TaskId(2)));
assert_eq!(trie.resolve(&path("/services/worker")), Some(TaskId(3)));
assert_eq!(trie.resolve(&path("/missing")), None);
assert_eq!(trie.resolve(&path("/services")), None); }
#[test]
fn test_insert_conflict() {
let mut trie = PathTrie::new();
trie.insert(&path("/web"), TaskId(1)).unwrap();
let err = trie.insert(&path("/web"), TaskId(2)).unwrap_err();
assert_eq!(err.existing_id, TaskId(1));
}
#[test]
fn test_remove() {
let mut trie = PathTrie::new();
trie.insert(&path("/a/b"), TaskId(1)).unwrap();
trie.insert(&path("/a/c"), TaskId(2)).unwrap();
assert_eq!(trie.remove(&path("/a/b")), Some(TaskId(1)));
assert_eq!(trie.resolve(&path("/a/b")), None);
assert_eq!(trie.resolve(&path("/a/c")), Some(TaskId(2)));
assert_eq!(trie.remove(&path("/a/c")), Some(TaskId(2)));
assert_eq!(trie.resolve(&path("/a/c")), None);
}
#[test]
fn test_remove_nonexistent() {
let mut trie = PathTrie::new();
assert_eq!(trie.remove(&path("/nope")), None);
}
#[test]
fn test_children() {
let mut trie = PathTrie::new();
trie.insert(&path("/services/api"), TaskId(1)).unwrap();
trie.insert(&path("/services/web"), TaskId(2)).unwrap();
trie.insert(&path("/services/web/v2"), TaskId(3)).unwrap();
trie.insert(&path("/tools/lint"), TaskId(4)).unwrap();
let root_children = trie.children(&path("/"));
assert_eq!(root_children.len(), 2); assert_eq!(root_children[0].0, "services");
assert_eq!(root_children[0].1, None); assert_eq!(root_children[1].0, "tools");
assert_eq!(root_children[1].1, None);
let svc_children = trie.children(&path("/services"));
assert_eq!(svc_children.len(), 2); assert_eq!(svc_children[0], ("api".to_string(), Some(TaskId(1))));
assert_eq!(svc_children[1], ("web".to_string(), Some(TaskId(2))));
}
#[test]
fn test_descendants() {
let mut trie = PathTrie::new();
trie.insert(&path("/services/api"), TaskId(1)).unwrap();
trie.insert(&path("/services/web"), TaskId(2)).unwrap();
trie.insert(&path("/services/web/v2"), TaskId(3)).unwrap();
trie.insert(&path("/tools/lint"), TaskId(4)).unwrap();
let desc = trie.descendants(&path("/services"));
assert_eq!(desc.len(), 3);
assert_eq!(desc[0], (path("/services/api"), TaskId(1)));
assert_eq!(desc[1], (path("/services/web"), TaskId(2)));
assert_eq!(desc[2], (path("/services/web/v2"), TaskId(3)));
}
#[test]
fn test_glob_star() {
let mut trie = PathTrie::new();
trie.insert(&path("/services/api"), TaskId(1)).unwrap();
trie.insert(&path("/services/web"), TaskId(2)).unwrap();
trie.insert(&path("/services/web/v2"), TaskId(3)).unwrap();
trie.insert(&path("/tools/lint"), TaskId(4)).unwrap();
let results = trie.glob("/services/*");
assert_eq!(results.len(), 2);
assert_eq!(results[0], (path("/services/api"), TaskId(1)));
assert_eq!(results[1], (path("/services/web"), TaskId(2)));
}
#[test]
fn test_glob_double_star() {
let mut trie = PathTrie::new();
trie.insert(&path("/services/api"), TaskId(1)).unwrap();
trie.insert(&path("/services/web"), TaskId(2)).unwrap();
trie.insert(&path("/services/web/v2"), TaskId(3)).unwrap();
trie.insert(&path("/tools/lint"), TaskId(4)).unwrap();
let results = trie.glob("/services/**");
assert_eq!(results.len(), 3);
let results = trie.glob("/**");
assert_eq!(results.len(), 4);
}
#[test]
fn test_glob_mixed() {
let mut trie = PathTrie::new();
trie.insert(&path("/a/b/c"), TaskId(1)).unwrap();
trie.insert(&path("/a/x/c"), TaskId(2)).unwrap();
trie.insert(&path("/a/b/d"), TaskId(3)).unwrap();
let results = trie.glob("/a/*/c");
assert_eq!(results.len(), 2);
assert_eq!(results[0], (path("/a/b/c"), TaskId(1)));
assert_eq!(results[1], (path("/a/x/c"), TaskId(2)));
}
#[test]
fn test_iter_sorted() {
let mut trie = PathTrie::new();
trie.insert(&path("/z"), TaskId(1)).unwrap();
trie.insert(&path("/a/b"), TaskId(2)).unwrap();
trie.insert(&path("/a/a"), TaskId(3)).unwrap();
trie.insert(&path("/m"), TaskId(4)).unwrap();
let items = trie.iter();
let paths: Vec<&str> =
items.iter().map(|(p, _)| p.as_str()).collect();
assert_eq!(paths, vec!["/a/a", "/a/b", "/m", "/z"]);
}
}