microclaw_engine/internal/tool_runtime/
todo_store.rs1use serde::{Deserialize, Serialize};
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct TodoItem {
6 pub task: String,
7 pub status: String, }
9
10pub fn todo_path(groups_dir: &Path, channel: &str, chat_id: i64) -> PathBuf {
11 groups_dir
12 .join(channel.trim())
13 .join(chat_id.to_string())
14 .join("TODO.json")
15}
16
17pub fn read_todos(groups_dir: &Path, channel: &str, chat_id: i64) -> Vec<TodoItem> {
18 let path = todo_path(groups_dir, channel, chat_id);
19 match std::fs::read_to_string(&path) {
20 Ok(content) => serde_json::from_str(&content).unwrap_or_default(),
21 Err(_) => Vec::new(),
22 }
23}
24
25pub fn write_todos(
26 groups_dir: &Path,
27 channel: &str,
28 chat_id: i64,
29 todos: &[TodoItem],
30) -> std::io::Result<()> {
31 let path = todo_path(groups_dir, channel, chat_id);
32 if let Some(parent) = path.parent() {
33 std::fs::create_dir_all(parent)?;
34 }
35 let json = serde_json::to_string_pretty(todos).map_err(std::io::Error::other)?;
36 std::fs::write(path, json)
37}
38
39pub fn clear_todos(groups_dir: &Path, channel: &str, chat_id: i64) -> std::io::Result<bool> {
40 let path = todo_path(groups_dir, channel, chat_id);
41 match std::fs::remove_file(path) {
42 Ok(()) => Ok(true),
43 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
44 Err(e) => Err(e),
45 }
46}
47
48pub fn format_todos(todos: &[TodoItem]) -> String {
49 if todos.is_empty() {
50 return "No tasks in the todo list.".into();
51 }
52 let mut out = String::new();
53 for (i, item) in todos.iter().enumerate() {
54 let icon = match item.status.as_str() {
55 "completed" => "[x]",
56 "in_progress" => "[~]",
57 _ => "[ ]",
58 };
59 out.push_str(&format!("{}. {} {}\n", i + 1, icon, item.task));
60 }
61 out
62}
63
64#[cfg(test)]
65mod tests {
66 use super::{clear_todos, read_todos, write_todos, TodoItem};
67 use std::path::{Path, PathBuf};
68
69 fn test_dir() -> PathBuf {
70 std::env::temp_dir().join(format!(
71 "microclaw_todo_store_test_{}",
72 uuid::Uuid::new_v4()
73 ))
74 }
75
76 fn cleanup(dir: &Path) {
77 let _ = std::fs::remove_dir_all(dir);
78 }
79
80 #[test]
81 fn test_clear_todos_removes_file() {
82 let dir = test_dir();
83 let groups_dir = dir.join("groups");
84 write_todos(
85 &groups_dir,
86 "telegram",
87 123,
88 &[TodoItem {
89 task: "task".into(),
90 status: "pending".into(),
91 }],
92 )
93 .unwrap();
94
95 let cleared = clear_todos(&groups_dir, "telegram", 123).unwrap();
96 assert!(cleared);
97 assert!(read_todos(&groups_dir, "telegram", 123).is_empty());
98 cleanup(&dir);
99 }
100
101 #[test]
102 fn test_clear_todos_not_found_is_ok() {
103 let dir = test_dir();
104 let groups_dir = dir.join("groups");
105 let cleared = clear_todos(&groups_dir, "telegram", 999).unwrap();
106 assert!(!cleared);
107 cleanup(&dir);
108 }
109}