Skip to main content

levi_core/
ids.rs

1//! Short task ids: display as `lv-` + a unique hex prefix (git-style),
2//! prefix matching on input.
3
4use crate::materialize::World;
5
6const MIN_SHORT: usize = 4;
7
8/// Shortest unique-prefix display id for `id` within `world` (min 4 hex
9/// chars). Falls back to lengthening on collision, git-style.
10pub fn short_id(world: &World, id: &str) -> String {
11    let mut len = MIN_SHORT;
12    while len < id.len() {
13        let prefix = &id[..len];
14        let collisions = world
15            .tasks
16            .keys()
17            .filter(|other| other.as_str() != id && other.starts_with(prefix))
18            .count();
19        if collisions == 0 {
20            return format!("lv-{prefix}");
21        }
22        len += 2;
23    }
24    format!("lv-{id}")
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum PrefixError {
29    NotFound(String),
30    Ambiguous(String, Vec<String>),
31}
32
33impl std::fmt::Display for PrefixError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            PrefixError::NotFound(p) => write!(f, "no task matches '{p}'"),
37            PrefixError::Ambiguous(p, ids) => {
38                write!(f, "'{p}' is ambiguous: matches {}", ids.join(", "))
39            }
40        }
41    }
42}
43
44impl std::error::Error for PrefixError {}
45
46/// Resolve user input (`lv-3f2a`, `3f2a`, or a full id) to a task id.
47pub fn resolve_prefix<'w>(world: &'w World, input: &str) -> Result<&'w str, PrefixError> {
48    let prefix = input.strip_prefix("lv-").unwrap_or(input);
49    let mut matches: Vec<&str> = world
50        .tasks
51        .keys()
52        .filter(|id| id.starts_with(prefix))
53        .map(String::as_str)
54        .collect();
55    match matches.len() {
56        0 => Err(PrefixError::NotFound(input.to_string())),
57        1 => Ok(matches.remove(0)),
58        _ => Err(PrefixError::Ambiguous(
59            input.to_string(),
60            matches.iter().map(|id| short_id_of(id)).collect(),
61        )),
62    }
63}
64
65fn short_id_of(id: &str) -> String {
66    format!("lv-{}", &id[..id.len().min(8)])
67}