1use crate::store::memory::{Importance, Memory, MemoryType, Scope};
2use chrono::Utc;
3
4#[derive(Debug)]
6pub struct ImportedMemory {
7 pub title: String,
8 pub content: String,
9 pub memory_type: MemoryType,
10 pub importance: Importance,
11 pub tags: Vec<String>,
12 pub scope: Scope,
13 pub what: Option<String>,
14 pub why: Option<String>,
15 pub context: Option<String>,
16 pub learned: Option<String>,
17}
18
19pub fn export_to_markdown(memories: &[Memory], project: &str) -> String {
21 let mut out = String::new();
22 let now = Utc::now().to_rfc3339();
23
24 out.push_str(&format!("# mneme export — proyecto: {}\n", project));
25 out.push_str(&format!("Exportado el: {}\n", now));
26 out.push_str(&format!("Total: {} memorias\n", memories.len()));
27
28 for memory in memories {
29 out.push_str("\n---\n\n");
30 out.push_str(&memory_to_markdown_section(memory));
31 }
32 out
33}
34
35fn memory_to_markdown_section(memory: &Memory) -> String {
36 let mut s = String::new();
37 s.push_str(&format!("## {}\n", memory.title));
38 s.push_str(&format!("- **ID**: `{}`\n", memory.id));
39 s.push_str(&format!("- **Tipo**: {}\n", memory.memory_type));
40 s.push_str(&format!("- **Importancia**: {}\n", memory.importance));
41 s.push_str(&format!("- **Scope**: {}\n", memory.scope));
42
43 if !memory.tags.is_empty() {
44 let tags = memory
45 .tags
46 .iter()
47 .map(|t| format!("`{}`", t))
48 .collect::<Vec<_>>()
49 .join(" ");
50 s.push_str(&format!("- **Tags**: {}\n", tags));
51 }
52
53 s.push_str(&format!(
54 "- **Creado**: {}\n",
55 memory.created_at.to_rfc3339()
56 ));
57 s.push_str(&format!(
58 "- **Actualizado**: {}\n",
59 memory.updated_at.to_rfc3339()
60 ));
61
62 if memory.is_encrypted {
63 let for_whom = memory.encrypted_for.as_deref().unwrap_or("unknown");
64 s.push_str(&format!("- **🔒 Encriptado**: sà ({})\n", for_whom));
65 s.push_str("\n*[contenido encriptado — usar `mneme decrypt <id>` para ver]*\n");
66 return s;
67 }
68
69 s.push('\n');
70
71 if !memory.content.is_empty() {
72 s.push_str(&memory.content);
73 s.push('\n');
74 }
75
76 if let Some(what) = &memory.what {
77 s.push_str(&format!("\n**What**: {}\n", what));
78 }
79 if let Some(why) = &memory.why {
80 s.push_str(&format!("**Why**: {}\n", why));
81 }
82 if let Some(context) = &memory.context {
83 s.push_str(&format!("**Context**: {}\n", context));
84 }
85 if let Some(learned) = &memory.learned {
86 s.push_str(&format!("**Learned**: {}\n", learned));
87 }
88
89 s.push('\n');
90 s
91}
92
93pub fn import_from_markdown(content: &str) -> crate::error::Result<Vec<ImportedMemory>> {
97 let mut memories = Vec::new();
98
99 let sections: Vec<&str> = content.split("\n---\n").collect();
100
101 for section in sections.iter().skip(1) {
102 let section = section.trim();
103 if section.is_empty() {
104 continue;
105 }
106
107 let title = section
108 .lines()
109 .find(|l| l.starts_with("## "))
110 .map(|l| l.trim_start_matches("## ").trim().to_string())
111 .unwrap_or_default();
112
113 if title.is_empty() {
114 continue;
115 }
116
117 let mut memory_type = MemoryType::Note;
118 let mut importance = Importance::Medium;
119 let mut tags = Vec::new();
120 let mut scope = Scope::Project;
121
122 for line in section.lines() {
123 if let Some(rest) = line.strip_prefix("- **Tipo**: ") {
124 let v = rest.trim();
125 memory_type = v.parse().unwrap_or(MemoryType::Note);
126 } else if let Some(rest) = line.strip_prefix("- **Importancia**: ") {
127 let v = rest.trim();
128 importance = v.parse().unwrap_or(Importance::Medium);
129 } else if let Some(rest) = line.strip_prefix("- **Tags**: ") {
130 let v = rest.trim();
131 tags = v
132 .split_whitespace()
133 .map(|t| t.trim_matches('`').to_string())
134 .filter(|t| !t.is_empty())
135 .collect();
136 } else if let Some(rest) = line.strip_prefix("- **Scope**: ") {
137 let v = rest.trim();
138 scope = v.parse().unwrap_or(Scope::Project);
139 }
140 }
141
142 let mut in_meta = true;
143 let mut content_lines = Vec::new();
144 let mut what = None;
145 let mut why = None;
146 let mut context_field = None;
147 let mut learned = None;
148
149 for line in section.lines().skip(1) {
150 if line.starts_with("- **") {
152 in_meta = true;
153 continue;
154 }
155 if in_meta && line.is_empty() {
156 in_meta = false;
157 continue;
158 }
159 if in_meta {
160 continue;
161 }
162
163 if line.starts_with("**What**: ") {
164 what = Some(line.trim_start_matches("**What**: ").to_string());
165 } else if line.starts_with("**Why**: ") {
166 why = Some(line.trim_start_matches("**Why**: ").to_string());
167 } else if line.starts_with("**Context**: ") {
168 context_field = Some(line.trim_start_matches("**Context**: ").to_string());
169 } else if line.starts_with("**Learned**: ") {
170 learned = Some(line.trim_start_matches("**Learned**: ").to_string());
171 } else if !line.starts_with("*[contenido encriptado") {
172 content_lines.push(line);
173 }
174 }
175
176 let content = content_lines.join("\n").trim().to_string();
177
178 memories.push(ImportedMemory {
179 title,
180 content,
181 memory_type,
182 importance,
183 tags,
184 scope,
185 what,
186 why,
187 context: context_field,
188 learned,
189 });
190 }
191
192 Ok(memories)
193}