MarkdownDocument

Struct MarkdownDocument 

Source
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:

  1. Metadata: Structured data from frontmatter (YAML or TOML)
  2. Content: The main Markdown content without frontmatter
  3. 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 == content

Fields§

§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: String

The 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: String

The 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

Source

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);
Source

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 document
  • content - 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"));
Source

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());
Source

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"))?;
Source

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 string
  • context - 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.

Source

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
---
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());
Source

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.metadata to Some(metadata)
  • Regenerates self.raw with YAML frontmatter + content
  • Preserves the existing content field 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"));
Source

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.content to the new content
  • Regenerates self.raw appropriately:
    • 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"));
Source

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:

  1. First, check if metadata contains an explicit title
  2. If not, scan the content for the first level-1 heading (# Title)
  3. Return None if neither source provides a title
§Returns
  • Some(String) containing the title if found
  • None if 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);
Source

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:

  1. First, check if metadata contains an explicit description
  2. If not, extract the first paragraph from the content (after any headings)
  3. Return None if neither source provides a description
§Returns
  • Some(String) containing the description if found
  • None if 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

Source§

fn clone(&self) -> MarkdownDocument

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MarkdownDocument

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,