Skip to main content

microclaw_engine/internal/tool_runtime/
todo_store.rs

1use 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, // "pending", "in_progress", "completed"
8}
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}