walrus_daemon/hook/system/memory/
entry.rs1use crate::hook::system::memory::storage::Storage;
8use anyhow::{Result, bail};
9use std::path::{Path, PathBuf};
10
11pub struct MemoryEntry {
13 pub name: String,
14 pub description: String,
15 pub content: String,
16 pub path: PathBuf,
17}
18
19impl MemoryEntry {
20 pub fn new(name: String, description: String, content: String, entries_dir: &Path) -> Self {
22 let slug = slugify(&name);
23 let path = entries_dir.join(format!("{slug}.md"));
24 Self {
25 name,
26 description,
27 content,
28 path,
29 }
30 }
31
32 pub fn parse(path: PathBuf, raw: &str) -> Result<Self> {
34 let raw = raw.replace("\r\n", "\n");
36 let raw = raw.trim();
37 if !raw.starts_with("---") {
38 bail!("missing frontmatter opening ---");
39 }
40
41 let after_open = &raw[3..];
42 let Some(close_pos) = after_open.find("\n---") else {
43 bail!("missing frontmatter closing ---");
44 };
45
46 let frontmatter = &after_open[..close_pos];
47 let content = after_open[close_pos + 4..].trim().to_owned();
48
49 let mut name = None;
50 let mut description = None;
51
52 for line in frontmatter.lines() {
53 let line = line.trim();
54 if let Some(val) = line.strip_prefix("name:") {
55 name = Some(val.trim().to_owned());
56 } else if let Some(val) = line.strip_prefix("description:") {
57 description = Some(val.trim().to_owned());
58 }
59 }
60
61 let Some(name) = name else {
62 bail!("missing 'name' in frontmatter");
63 };
64 let description = description.unwrap_or_default();
65
66 Ok(Self {
67 name,
68 description,
69 content,
70 path,
71 })
72 }
73
74 pub fn serialize(&self) -> String {
76 let mut out = String::new();
77 out.push_str("---\n");
78 out.push_str(&format!("name: {}\n", self.name));
79 out.push_str(&format!("description: {}\n", self.description));
80 out.push_str("---\n\n");
81 out.push_str(&self.content);
82 out.push('\n');
83 out
84 }
85
86 pub fn save(&self, storage: &dyn Storage) -> Result<()> {
88 storage.write(&self.path, &self.serialize())
89 }
90
91 pub fn delete(&self, storage: &dyn Storage) -> Result<()> {
93 storage.delete(&self.path)
94 }
95
96 pub fn search_text(&self) -> String {
98 format!("{} {}", self.description, self.content)
99 }
100}
101
102pub fn slugify(name: &str) -> String {
107 let mut slug = String::with_capacity(name.len());
108 let mut prev_dash = true; for ch in name.chars() {
111 if ch.is_alphanumeric() {
112 for lc in ch.to_lowercase() {
113 slug.push(lc);
114 }
115 prev_dash = false;
116 } else if !prev_dash {
117 slug.push('-');
118 prev_dash = true;
119 }
120 }
121
122 if slug.ends_with('-') {
124 slug.pop();
125 }
126
127 if slug.is_empty() {
128 slug.push_str("entry");
129 }
130
131 slug
132}