Skip to content

Commit 57a9c52

Browse files
committed
feat(writer): add rewrite_note with frontmatter preservation
1 parent 606b7aa commit 57a9c52

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

src/writer.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ pub struct EditResult {
6464
pub mode: String,
6565
}
6666

67+
#[derive(Debug, Clone)]
68+
pub struct RewriteInput {
69+
pub file: String,
70+
pub content: String,
71+
pub preserve_frontmatter: bool,
72+
pub modified_by: String,
73+
}
74+
6775
#[derive(Debug, Clone, serde::Serialize)]
6876
pub struct WriteResult {
6977
pub path: String,
@@ -802,6 +810,50 @@ pub fn edit_note(
802810
})
803811
}
804812

813+
/// Rewrite the body of an existing note, optionally preserving existing frontmatter.
814+
///
815+
/// If `preserve_frontmatter` is true and the note has frontmatter, the existing
816+
/// YAML block is kept intact and only the body is replaced with `input.content`.
817+
/// If false, the file is replaced entirely with `input.content`.
818+
///
819+
/// Does NOT re-index — the MCP layer handles that.
820+
pub fn rewrite_note(store: &Store, vault_path: &Path, input: &RewriteInput) -> Result<EditResult> {
821+
// Step 1: Resolve file via store
822+
let file_record = store
823+
.resolve_file(&input.file)?
824+
.ok_or_else(|| anyhow::anyhow!("file not found: {}", input.file))?;
825+
826+
let full_path = vault_path.join(&file_record.path);
827+
828+
// Step 2: Read current content from disk
829+
let existing_content = std::fs::read_to_string(&full_path)?;
830+
831+
// Step 3: Split frontmatter using crate::markdown::split_frontmatter
832+
let (maybe_frontmatter, _old_body) = crate::markdown::split_frontmatter(&existing_content);
833+
834+
// Step 4: Reconstruct content
835+
let new_content = if input.preserve_frontmatter {
836+
if let Some(frontmatter) = maybe_frontmatter {
837+
format!("---\n{}\n---\n\n{}", frontmatter, input.content)
838+
} else {
839+
// No existing frontmatter — just use new content as-is
840+
input.content.clone()
841+
}
842+
} else {
843+
input.content.clone()
844+
};
845+
846+
// Step 5: Write atomically (overwrite = true)
847+
atomic_write(&full_path, &new_content, true)?;
848+
849+
// Step 6: Return EditResult (reusing existing result type)
850+
Ok(EditResult {
851+
path: file_record.path,
852+
heading: String::new(),
853+
mode: "Rewrite".to_string(),
854+
})
855+
}
856+
805857
/// Move a note to a new folder.
806858
pub fn move_note(
807859
file: &str,
@@ -1419,4 +1471,26 @@ mod tests {
14191471
assert!(result.is_err());
14201472
assert!(result.unwrap_err().to_string().contains("file not found"));
14211473
}
1474+
1475+
#[test]
1476+
fn test_rewrite_preserves_frontmatter() {
1477+
let (tmp, store, root) = setup_vault();
1478+
let content = "---\ntags:\n - project\nstatus: active\n---\n\n# Old Content\n\nOld body\n";
1479+
std::fs::write(root.join("note.md"), content).unwrap();
1480+
store.insert_file("note.md", "hash", 100, &["project".to_string()], "rew123", None).unwrap();
1481+
1482+
let input = RewriteInput {
1483+
file: "note.md".into(),
1484+
content: "# New Content\n\nNew body\n".into(),
1485+
preserve_frontmatter: true,
1486+
modified_by: "test".into(),
1487+
};
1488+
rewrite_note(&store, &root, &input).unwrap();
1489+
1490+
let updated = std::fs::read_to_string(root.join("note.md")).unwrap();
1491+
assert!(updated.contains("status: active"));
1492+
assert!(updated.contains("# New Content"));
1493+
assert!(!updated.contains("Old body"));
1494+
drop(tmp);
1495+
}
14221496
}

0 commit comments

Comments
 (0)