codelens_engine/
memory.rs1use std::path::{Path, PathBuf};
4
5use anyhow::{Result, bail};
6
7pub fn list_memory_names(memories_dir: &Path, topic: Option<&str>) -> Vec<String> {
10 if !memories_dir.is_dir() {
11 return Vec::new();
12 }
13 let mut names = Vec::new();
14 collect_memory_files(memories_dir, memories_dir, &mut names);
15 names.sort();
16 if let Some(t) = topic {
17 let t = t.trim().trim_matches('/');
18 if !t.is_empty() {
19 names.retain(|n| n == t || n.starts_with(&format!("{t}/")));
20 }
21 }
22 names
23}
24
25fn collect_memory_files(base: &Path, dir: &Path, names: &mut Vec<String>) {
26 let entries = match std::fs::read_dir(dir) {
27 Ok(e) => e,
28 Err(_) => return,
29 };
30 for entry in entries.flatten() {
31 let path = entry.path();
32 if path.is_dir() {
33 collect_memory_files(base, &path, names);
34 } else if path.extension().and_then(|e| e.to_str()) == Some("md")
35 && let Ok(rel) = path.strip_prefix(base)
36 {
37 let name = rel
38 .to_string_lossy()
39 .replace('\\', "/")
40 .trim_end_matches(".md")
41 .to_string();
42 names.push(name);
43 }
44 }
45}
46
47pub fn resolve_memory_path(memories_dir: &Path, name: &str) -> Result<PathBuf> {
49 let normalized = name
50 .trim()
51 .replace('\\', "/")
52 .trim_matches('/')
53 .trim_end_matches(".md")
54 .to_string();
55 if normalized.is_empty() {
56 bail!("memory name must not be empty");
57 }
58 if normalized.contains("..") {
59 bail!("memory path must not contain '..': {name}");
60 }
61 Ok(memories_dir.join(format!("{normalized}.md")))
62}
63
64pub fn read_memory(memories_dir: &Path, name: &str) -> Result<String> {
66 let path = resolve_memory_path(memories_dir, name)?;
67 std::fs::read_to_string(&path).map_err(|_| anyhow::anyhow!("memory not found: {name}"))
68}
69
70pub fn write_memory(memories_dir: &Path, name: &str, content: &str) -> Result<()> {
72 let path = resolve_memory_path(memories_dir, name)?;
73 if let Some(parent) = path.parent() {
74 std::fs::create_dir_all(parent)?;
75 }
76 std::fs::write(&path, content)?;
77 Ok(())
78}
79
80pub fn delete_memory(memories_dir: &Path, name: &str) -> Result<()> {
82 let path = resolve_memory_path(memories_dir, name)?;
83 if !path.is_file() {
84 bail!("memory not found: {name}");
85 }
86 std::fs::remove_file(&path)?;
87 Ok(())
88}
89
90pub fn rename_memory(memories_dir: &Path, old_name: &str, new_name: &str) -> Result<()> {
92 let old_path = resolve_memory_path(memories_dir, old_name)?;
93 let new_path = resolve_memory_path(memories_dir, new_name)?;
94 if !old_path.is_file() {
95 bail!("memory not found: {old_name}");
96 }
97 if new_path.exists() {
98 bail!("target already exists: {new_name}");
99 }
100 if let Some(parent) = new_path.parent() {
101 std::fs::create_dir_all(parent)?;
102 }
103 std::fs::rename(&old_path, &new_path)?;
104 Ok(())
105}