archelon_core/entry_ref.rs
1use std::path::PathBuf;
2
3use crate::{error::Result, journal::Journal};
4
5/// A reference to a journal entry — either a filesystem path or a CarettaId prefix.
6///
7/// This is the canonical input type for commands that operate on a single entry
8/// (show, fix, remove, etc.). Parse raw user input with [`EntryRef::parse`], then
9/// resolve it to a concrete [`PathBuf`] with [`EntryRef::resolve`].
10#[derive(Debug, Clone)]
11pub enum EntryRef {
12 /// A filesystem path to the entry file.
13 Path(PathBuf),
14 /// A CarettaId prefix (1–7 characters).
15 Id(String),
16}
17
18impl EntryRef {
19 /// Classify a raw string as a path or an ID prefix.
20 ///
21 /// The string is treated as a **path** when it:
22 /// - contains a path separator (`/` or the platform separator), or
23 /// - starts with `.` or `~`, or
24 /// - ends with `.md`.
25 ///
26 /// Everything else is treated as a **CarettaId prefix**.
27 pub fn parse(s: &str) -> Self {
28 if s.contains('/')
29 || s.contains(std::path::MAIN_SEPARATOR)
30 || s.starts_with('.')
31 || s.ends_with(".md")
32 {
33 EntryRef::Path(PathBuf::from(s))
34 } else {
35 EntryRef::Id(s.to_owned())
36 }
37 }
38
39 /// Resolve this reference to a concrete file path.
40 ///
41 /// - `Path` variant: returns the stored path as-is.
42 /// - `Id` variant: delegates to [`Journal::find_entry_by_id`].
43 pub fn resolve(&self, journal: &Journal) -> Result<PathBuf> {
44 match self {
45 EntryRef::Path(p) => Ok(p.clone()),
46 EntryRef::Id(id) => journal.find_entry_by_id(id),
47 }
48 }
49}
50
51impl From<&str> for EntryRef {
52 fn from(s: &str) -> Self {
53 Self::parse(s)
54 }
55}
56
57impl From<String> for EntryRef {
58 fn from(s: String) -> Self {
59 Self::parse(&s)
60 }
61}