vtcode_core/
markdown_storage.rs

1//! Simple markdown-based storage system
2//!
3//! This module provides simple storage capabilities using markdown files
4//! instead of complex databases. Perfect for storing project metadata,
5//! search results, and other simple data structures.
6
7use anyhow::{Context, Result};
8use indexmap::IndexMap;
9use serde::{Deserialize, Serialize};
10use std::fs;
11use std::path::PathBuf;
12
13/// Simple markdown storage manager
14#[derive(Clone)]
15pub struct MarkdownStorage {
16    storage_dir: PathBuf,
17}
18
19impl MarkdownStorage {
20    /// Create a new markdown storage instance
21    pub fn new(storage_dir: PathBuf) -> Self {
22        Self { storage_dir }
23    }
24
25    /// Initialize storage directory
26    pub fn init(&self) -> Result<()> {
27        fs::create_dir_all(&self.storage_dir)?;
28        Ok(())
29    }
30
31    /// Store data as markdown
32    pub fn store<T: Serialize>(&self, key: &str, data: &T, title: &str) -> Result<()> {
33        let file_path = self.storage_dir.join(format!("{}.md", key));
34        let markdown = self.serialize_to_markdown(data, title)?;
35        fs::write(file_path, markdown)?;
36        Ok(())
37    }
38
39    /// Load data from markdown
40    pub fn load<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Result<T> {
41        let file_path = self.storage_dir.join(format!("{}.md", key));
42        let content = fs::read_to_string(file_path)?;
43        self.deserialize_from_markdown(&content)
44    }
45
46    /// List all stored items
47    pub fn list(&self) -> Result<Vec<String>> {
48        let mut items = Vec::new();
49
50        for entry in fs::read_dir(&self.storage_dir)? {
51            let entry = entry?;
52            if let Some(file_name) = entry.path().file_stem() {
53                if let Some(name) = file_name.to_str() {
54                    items.push(name.to_string());
55                }
56            }
57        }
58
59        Ok(items)
60    }
61
62    /// Delete stored item
63    pub fn delete(&self, key: &str) -> Result<()> {
64        let file_path = self.storage_dir.join(format!("{}.md", key));
65        if file_path.exists() {
66            fs::remove_file(file_path)?;
67        }
68        Ok(())
69    }
70
71    /// Check if item exists
72    pub fn exists(&self, key: &str) -> bool {
73        let file_path = self.storage_dir.join(format!("{}.md", key));
74        file_path.exists()
75    }
76
77    // Helper methods
78
79    fn serialize_to_markdown<T: Serialize>(&self, data: &T, title: &str) -> Result<String> {
80        let json = serde_json::to_string_pretty(data)?;
81        let yaml = serde_yaml::to_string(data)?;
82
83        let markdown = format!(
84            "# {}\n\n\
85            ## JSON\n\n\
86            ```json\n\
87            {}\n\
88            ```\n\n\
89            ## YAML\n\n\
90            ```yaml\n\
91            {}\n\
92            ```\n\n\
93            ## Raw Data\n\n\
94            {}\n",
95            title,
96            json,
97            yaml,
98            self.format_raw_data(data)
99        );
100
101        Ok(markdown)
102    }
103
104    fn deserialize_from_markdown<T: for<'de> Deserialize<'de>>(&self, content: &str) -> Result<T> {
105        // Try to extract JSON from markdown code blocks
106        if let Some(json_block) = self.extract_code_block(content, "json") {
107            return serde_json::from_str(json_block).context("Failed to parse JSON from markdown");
108        }
109
110        // Try to extract YAML from markdown code blocks
111        if let Some(yaml_block) = self.extract_code_block(content, "yaml") {
112            return serde_yaml::from_str(yaml_block).context("Failed to parse YAML from markdown");
113        }
114
115        Err(anyhow::anyhow!("No valid JSON or YAML found in markdown"))
116    }
117
118    fn extract_code_block<'a>(&self, content: &'a str, language: &str) -> Option<&'a str> {
119        let start_pattern = format!("```{}", language);
120        let end_pattern = "```";
121
122        if let Some(start_idx) = content.find(&start_pattern) {
123            let code_start = start_idx + start_pattern.len();
124            if let Some(end_idx) = content[code_start..].find(end_pattern) {
125                let code_end = code_start + end_idx;
126                return Some(content[code_start..code_end].trim());
127            }
128        }
129
130        None
131    }
132
133    fn format_raw_data<T: Serialize>(&self, data: &T) -> String {
134        match serde_json::to_value(data) {
135            Ok(serde_json::Value::Object(map)) => {
136                let mut lines = Vec::new();
137                for (key, value) in map {
138                    lines.push(format!("- **{}**: {}", key, self.format_value(&value)));
139                }
140                lines.join("\n")
141            }
142            _ => "Complex data structure".to_string(),
143        }
144    }
145
146    fn format_value(&self, value: &serde_json::Value) -> String {
147        match value {
148            serde_json::Value::String(s) => format!("\"{}\"", s),
149            serde_json::Value::Number(n) => n.to_string(),
150            serde_json::Value::Bool(b) => b.to_string(),
151            serde_json::Value::Array(arr) => format!("[{} items]", arr.len()),
152            serde_json::Value::Object(obj) => format!("{{{} fields}}", obj.len()),
153            serde_json::Value::Null => "null".to_string(),
154        }
155    }
156}
157
158/// Simple key-value storage using markdown
159pub struct SimpleKVStorage {
160    storage: MarkdownStorage,
161}
162
163impl SimpleKVStorage {
164    pub fn new(storage_dir: PathBuf) -> Self {
165        Self {
166            storage: MarkdownStorage::new(storage_dir),
167        }
168    }
169
170    pub fn init(&self) -> Result<()> {
171        self.storage.init()
172    }
173
174    pub fn put(&self, key: &str, value: &str) -> Result<()> {
175        let data = IndexMap::from([("value".to_string(), value.to_string())]);
176        self.storage
177            .store(key, &data, &format!("Key-Value: {}", key))
178    }
179
180    pub fn get(&self, key: &str) -> Result<String> {
181        let data: IndexMap<String, String> = self.storage.load(key)?;
182        data.get("value")
183            .cloned()
184            .ok_or_else(|| anyhow::anyhow!("Value not found for key: {}", key))
185    }
186
187    pub fn delete(&self, key: &str) -> Result<()> {
188        self.storage.delete(key)
189    }
190
191    pub fn list_keys(&self) -> Result<Vec<String>> {
192        self.storage.list()
193    }
194}
195
196/// Simple project metadata storage
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct ProjectData {
199    pub name: String,
200    pub description: Option<String>,
201    pub version: String,
202    pub tags: Vec<String>,
203    pub metadata: IndexMap<String, String>,
204}
205
206impl ProjectData {
207    pub fn new(name: &str) -> Self {
208        Self {
209            name: name.to_string(),
210            description: None,
211            version: "1.0.0".to_string(),
212            tags: vec![],
213            metadata: IndexMap::new(),
214        }
215    }
216}
217
218/// Project storage using markdown
219#[derive(Clone)]
220pub struct ProjectStorage {
221    storage: MarkdownStorage,
222}
223
224impl ProjectStorage {
225    pub fn new(storage_dir: PathBuf) -> Self {
226        Self {
227            storage: MarkdownStorage::new(storage_dir),
228        }
229    }
230
231    pub fn init(&self) -> Result<()> {
232        self.storage.init()
233    }
234
235    pub fn save_project(&self, project: &ProjectData) -> Result<()> {
236        self.storage.store(
237            &project.name,
238            project,
239            &format!("Project: {}", project.name),
240        )
241    }
242
243    pub fn load_project(&self, name: &str) -> Result<ProjectData> {
244        self.storage.load(name)
245    }
246
247    pub fn list_projects(&self) -> Result<Vec<String>> {
248        self.storage.list()
249    }
250
251    pub fn delete_project(&self, name: &str) -> Result<()> {
252        self.storage.delete(name)
253    }
254}