pub struct MarkdownDocument {
pub metadata: Option<MarkdownMetadata>,
pub content: String,
pub raw: String,
}Expand description
A parsed Markdown document representing a Claude Code resource.
This is the core structure for handling Markdown files in AGPM. It provides a clean separation between structured metadata (from frontmatter) and the actual content, while preserving the original document format for roundtrip compatibility.
§Structure
A MarkdownDocument consists of three parts:
- Metadata: Structured data from frontmatter (YAML or TOML)
- Content: The main Markdown content without frontmatter
- Raw: The complete original document for faithful reproduction
§Frontmatter Support
The document can parse both YAML (--- delimiters) and TOML (+++ delimiters)
frontmatter formats. If no frontmatter is present, the entire file is treated
as content.
§Content Extraction
When explicit metadata is not available, the document can extract information
from the content itself using get_title and get_description methods.
§Thread Safety
This struct is Clone and can be safely passed between threads for
concurrent processing of multiple documents.
§Examples
§Reading from File
let doc = MarkdownDocument::read(Path::new("agent.md"))?;
if let Some(metadata) = &doc.metadata {
println!("Found metadata: {:?}", metadata.title);
}
println!("Content length: {} chars", doc.content.len());§Creating Programmatically
let metadata = MarkdownMetadata {
title: Some("Test Agent".to_string()),
version: Some("1.0.0".to_string()),
..Default::default()
};
let content = "# Test Agent\n\nThis agent helps with testing.";
let doc = MarkdownDocument::with_metadata(metadata, content.to_string());
// Raw contains formatted frontmatter + content
assert!(doc.raw.contains("title: Test Agent"));
assert!(doc.raw.contains("This agent helps with testing"));§Modifying Content
let mut doc = MarkdownDocument::new("# Original".to_string());
// Update content - raw is automatically regenerated
doc.set_content("# Updated Content\n\nNew description.".to_string());
assert_eq!(doc.content, "# Updated Content\n\nNew description.");
assert_eq!(doc.raw, doc.content); // No frontmatter, so raw == contentFields§
§metadata: Option<MarkdownMetadata>Parsed metadata extracted from frontmatter.
This will be Some if the document contained valid YAML or TOML
frontmatter, and None for plain Markdown files. The metadata
is used by AGPM for dependency resolution and resource management.
content: StringThe main Markdown content without frontmatter delimiters.
This contains only the actual content portion of the document, with frontmatter stripped away. This is what gets processed for content-based metadata extraction.
raw: StringThe complete original document including frontmatter.
This field preserves the exact original format for faithful reproduction when writing back to disk. When metadata or content is modified, this field is automatically regenerated to maintain consistency.
Implementations§
Source§impl MarkdownDocument
impl MarkdownDocument
Sourcepub fn new(content: String) -> Self
pub fn new(content: String) -> Self
Create a new markdown document without frontmatter.
This creates a plain Markdown document with no metadata. The content
becomes both the content and raw fields since there’s no frontmatter
to format.
§Arguments
content- The Markdown content as a string
§Examples
let doc = MarkdownDocument::new("# Hello\n\nWorld!".to_string());
assert!(doc.metadata.is_none());
assert_eq!(doc.content, "# Hello\n\nWorld!");
assert_eq!(doc.raw, doc.content);Sourcepub fn with_metadata(metadata: MarkdownMetadata, content: String) -> Self
pub fn with_metadata(metadata: MarkdownMetadata, content: String) -> Self
Create a markdown document with metadata and content.
This constructor creates a complete document with structured metadata
in YAML frontmatter format. The raw field will contain the formatted
frontmatter followed by the content.
§Arguments
metadata- The structured metadata for the documentcontent- The Markdown content (without frontmatter)
§Examples
let metadata = MarkdownMetadata {
title: Some("Example".to_string()),
version: Some("1.0.0".to_string()),
..Default::default()
};
let doc = MarkdownDocument::with_metadata(
metadata,
"# Example\n\nThis is an example.".to_string()
);
assert!(doc.metadata.is_some());
assert!(doc.raw.starts_with("---\n"));
assert!(doc.raw.contains("title: Example"));Sourcepub fn read(path: &Path) -> Result<Self>
pub fn read(path: &Path) -> Result<Self>
Read and parse a Markdown file from the filesystem.
This method reads the entire file into memory and parses it for frontmatter and content. It supports both YAML and TOML frontmatter formats and provides detailed error context on failure.
§Arguments
path- Path to the Markdown file to read
§Returns
Returns a Result containing the parsed document or an error with
context about what went wrong (file not found, parse error, etc.).
§Errors
This function will return an error if:
- The file cannot be read (doesn’t exist, permissions, etc.)
- The file contains invalid UTF-8
- The frontmatter is malformed YAML or TOML
§Examples
let doc = MarkdownDocument::read(Path::new("resources/agent.md"))?;
println!("Title: {:?}", doc.get_title());
println!("Content length: {}", doc.content.len());Sourcepub fn write(&self, path: &Path) -> Result<()>
pub fn write(&self, path: &Path) -> Result<()>
Write the document to a file on disk.
This method performs an atomic write operation, creating any necessary
parent directories automatically. The complete raw content (including
frontmatter if present) is written to the specified path.
§Arguments
path- Target path where the file should be written
§Returns
Returns Ok(()) on success, or an error with context on failure.
§Errors
This function will return an error if:
- Parent directories cannot be created (permissions, disk space, etc.)
- The file cannot be written (permissions, disk space, etc.)
- The path is invalid or inaccessible
§Safety
This operation creates parent directories as needed, which could potentially create unexpected directory structures if the path is not validated by the caller.
§Examples
let doc = MarkdownDocument::new("# Test\n\nContent".to_string());
// Writes to file, creating directories as needed
doc.write(Path::new("output/resources/test.md"))?;Sourcepub fn parse_with_context(input: &str, context: Option<&str>) -> Result<Self>
pub fn parse_with_context(input: &str, context: Option<&str>) -> Result<Self>
Parse a Markdown string that may contain frontmatter with context for warnings.
This is similar to parse but accepts an optional context string
that will be included in warning messages when preprocessing is required.
§Arguments
input- The complete Markdown document as a stringcontext- Optional context (e.g., file path) for warning messages
§Returns
Returns a parsed MarkdownDocument. If frontmatter parsing fails,
a warning is emitted and the entire document is treated as content.
Sourcepub fn parse(input: &str) -> Result<Self>
pub fn parse(input: &str) -> Result<Self>
Parse a Markdown string that may contain frontmatter.
This is the core parsing method that handles both YAML and TOML frontmatter formats. It attempts to detect and parse frontmatter, falling back to treating the entire input as content if no valid frontmatter is found.
§Supported Formats
§YAML Frontmatter (recommended)
---
title: "Example"
version: "1.0.0"
---
Content here...§TOML Frontmatter
+++
title = "Example"
version = "1.0.0"
+++
Content here...§Arguments
input- The complete Markdown document as a string
§Returns
Returns a parsed MarkdownDocument with metadata extracted if present.
§Errors
Returns an error if the frontmatter is present but malformed:
- Invalid YAML syntax in
---delimited frontmatter - Invalid TOML syntax in
+++delimited frontmatter - Frontmatter that doesn’t match the expected metadata schema
§Examples
// Parse document with YAML frontmatter
let input = "---\ntitle: Test\n---\n# Content";
let doc = MarkdownDocument::parse(input).unwrap();
assert!(doc.metadata.is_some());
// Parse plain Markdown
let input = "# Just Content";
let doc = MarkdownDocument::parse(input).unwrap();
assert!(doc.metadata.is_none());Sourcepub fn set_metadata(&mut self, metadata: MarkdownMetadata)
pub fn set_metadata(&mut self, metadata: MarkdownMetadata)
Update the document’s metadata and regenerate the raw content.
This method replaces the current metadata (if any) with new metadata
and automatically regenerates the raw field to include properly
formatted YAML frontmatter.
§Arguments
metadata- The new metadata to set for this document
§Effects
- Sets
self.metadatatoSome(metadata) - Regenerates
self.rawwith YAML frontmatter + content - Preserves the existing
contentfield unchanged
§Examples
let mut doc = MarkdownDocument::new("# Test\n\nContent".to_string());
assert!(doc.metadata.is_none());
let metadata = MarkdownMetadata {
title: Some("New Title".to_string()),
version: Some("2.0.0".to_string()),
..Default::default()
};
doc.set_metadata(metadata);
assert!(doc.metadata.is_some());
assert!(doc.raw.contains("title: New Title"));
assert!(doc.raw.contains("# Test"));Sourcepub fn set_content(&mut self, content: String)
pub fn set_content(&mut self, content: String)
Update the document’s content and regenerate the raw document.
This method replaces the current content with new content and
automatically regenerates the raw field. If metadata is present,
the raw content will include formatted frontmatter; otherwise it
will be just the new content.
§Arguments
content- The new Markdown content (without frontmatter)
§Effects
- Sets
self.contentto the new content - Regenerates
self.rawappropriately:- If metadata exists: frontmatter + new content
- If no metadata: just the new content
- Preserves existing metadata unchanged
§Examples
// Document with metadata
let metadata = MarkdownMetadata {
title: Some("Test".to_string()),
..Default::default()
};
let mut doc = MarkdownDocument::with_metadata(
metadata,
"Original content".to_string()
);
doc.set_content("# New Content\n\nUpdated!".to_string());
assert_eq!(doc.content, "# New Content\n\nUpdated!");
assert!(doc.raw.contains("title: Test"));
assert!(doc.raw.contains("# New Content"));Sourcepub fn get_title(&self) -> Option<String>
pub fn get_title(&self) -> Option<String>
Extract the document title from metadata or content.
This method provides a fallback mechanism for getting the document title:
- First, check if metadata contains an explicit title
- If not, scan the content for the first level-1 heading (
# Title) - Return
Noneif neither source provides a title
§Returns
Some(String)containing the title if foundNoneif no title is available from either source
§Title Extraction Rules
When extracting from content:
- Only level-1 headings (starting with
#) are considered - The first matching heading is used
- Leading/trailing whitespace is trimmed from the result
- Empty headings (just
#) are ignored
§Examples
// From metadata
let metadata = MarkdownMetadata {
title: Some("Metadata Title".to_string()),
..Default::default()
};
let doc = MarkdownDocument::with_metadata(
metadata,
"# Content Title\n\nSome text".to_string()
);
assert_eq!(doc.get_title(), Some("Metadata Title".to_string()));
// From content heading
let doc = MarkdownDocument::new("# Extracted Title\n\nContent".to_string());
assert_eq!(doc.get_title(), Some("Extracted Title".to_string()));
// No title available
let doc = MarkdownDocument::new("Just some content without headings".to_string());
assert_eq!(doc.get_title(), None);Sourcepub fn get_description(&self) -> Option<String>
pub fn get_description(&self) -> Option<String>
Extract the document description from metadata or content.
This method provides a fallback mechanism for getting the document description:
- First, check if metadata contains an explicit description
- If not, extract the first paragraph from the content (after any headings)
- Return
Noneif neither source provides a description
§Returns
Some(String)containing the description if foundNoneif no description is available from either source
§Description Extraction Rules
When extracting from content:
- All headings (lines starting with
#) are skipped - Empty lines before the first paragraph are ignored
- The first continuous block of non-empty lines becomes the description
- Multiple lines are joined with spaces
- Extraction stops at the first empty line after content starts
§Examples
// From metadata
let metadata = MarkdownMetadata {
description: Some("Metadata description".to_string()),
..Default::default()
};
let doc = MarkdownDocument::with_metadata(
metadata,
"# Title\n\nContent description".to_string()
);
assert_eq!(doc.get_description(), Some("Metadata description".to_string()));
// From content paragraph
let doc = MarkdownDocument::new(
"# Title\n\nThis is the first\nparagraph of content.\n\nSecond paragraph.".to_string()
);
assert_eq!(doc.get_description(), Some("This is the first paragraph of content.".to_string()));
// No description available
let doc = MarkdownDocument::new("# Just a title".to_string());
assert_eq!(doc.get_description(), None);Trait Implementations§
Source§impl Clone for MarkdownDocument
impl Clone for MarkdownDocument
Source§fn clone(&self) -> MarkdownDocument
fn clone(&self) -> MarkdownDocument
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more