"""Serialize an Obsidian note from a frontmatter mapping and a body.""" import yaml def format_obsidian_note(frontmatter: dict, body: str) -> str: """Serialize a complete Obsidian note from frontmatter and body. When `frontmatter` is a non-empty mapping, the result is: ---\n---\n\n where `` is produced by `yaml.safe_dump(frontmatter, sort_keys=False, allow_unicode=True)` (which already ends in a newline). When `frontmatter` is empty or None, only the body is returned. This is the inverse of `parse_obsidian_frontmatter` for a reasonable round-trip (key order preserved, unicode kept literal). Pure and deterministic: no I/O, no mutation of the inputs. Args: frontmatter: Mapping of YAML metadata for the note. Empty or None means no frontmatter block is emitted. body: The Markdown body of the note. Returns: The full note text as a string. """ safe_body = body if body is not None else "" if not frontmatter: return safe_body yaml_block = yaml.safe_dump(frontmatter, sort_keys=False, allow_unicode=True) # yaml.safe_dump already terminates with a trailing newline. return f"---\n{yaml_block}---\n\n{safe_body}" if __name__ == "__main__": out = format_obsidian_note({"title": "My Note", "tags": ["a", "b"]}, "Hello.") assert out == "---\ntitle: My Note\ntags:\n- a\n- b\n---\n\nHello.", repr(out) # Empty frontmatter -> body only. assert format_obsidian_note({}, "just a body") == "just a body" assert format_obsidian_note(None, "just a body") == "just a body" # Round-trip with parse_obsidian_frontmatter: the frontmatter mapping is # recovered exactly; the body is recovered modulo the blank separator line # that the format inserts between the frontmatter block and the body. from parse_obsidian_frontmatter import parse_obsidian_frontmatter fm = {"title": "Round Trip", "status": "open"} body = "Body with a [[link]]." note = format_obsidian_note(fm, body) parsed = parse_obsidian_frontmatter(note) assert parsed["frontmatter"] == fm, parsed assert parsed["body"].strip() == body, repr(parsed["body"]) print("OK")