Class: Ace::Support::Markdown::Models::MarkdownDocument

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/support/markdown/models/markdown_document.rb

Overview

Immutable representation of a markdown document Contains frontmatter and sections for safe transformations

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(frontmatter:, raw_body:, sections: nil, file_path: nil) ⇒ MarkdownDocument

Create a new MarkdownDocument

Parameters:

  • frontmatter (Hash)

    The YAML frontmatter data

  • raw_body (String)

    The raw body content (without frontmatter)

  • sections (Array<Section>) (defaults to: nil)

    Optional parsed sections

  • file_path (String, nil) (defaults to: nil)

    Optional source file path



17
18
19
20
21
22
23
24
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 17

def initialize(frontmatter:, raw_body:, sections: nil, file_path: nil)
  @frontmatter = frontmatter.freeze
  @raw_body = raw_body.freeze
  @sections = sections&.freeze
  @file_path = file_path&.freeze

  validate!
end

Instance Attribute Details

#file_pathObject (readonly)

Returns the value of attribute file_path.



10
11
12
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 10

def file_path
  @file_path
end

#frontmatterObject (readonly)

Returns the value of attribute frontmatter.



10
11
12
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 10

def frontmatter
  @frontmatter
end

#raw_bodyObject (readonly)

Returns the value of attribute raw_body.



10
11
12
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 10

def raw_body
  @raw_body
end

#sectionsObject (readonly)

Returns the value of attribute sections.



10
11
12
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 10

def sections
  @sections
end

Class Method Details

.parse(content, file_path: nil) ⇒ MarkdownDocument

Parse document from markdown string

Parameters:

  • content (String)

    The complete markdown content

  • file_path (String, nil) (defaults to: nil)

    Optional source file path

Returns:

Raises:



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 137

def self.parse(content, file_path: nil)
  result = Atoms::FrontmatterExtractor.extract(content)

  raise ValidationError, result[:errors].join(", ") unless result[:valid]

  new(
    frontmatter: result[:frontmatter],
    raw_body: result[:body],
    file_path: file_path
  )
end

.parse_with_sections(content, file_path: nil) ⇒ MarkdownDocument

Parse document with sections

Parameters:

  • content (String)

    The complete markdown content

  • file_path (String, nil) (defaults to: nil)

    Optional source file path

Returns:



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 153

def self.parse_with_sections(content, file_path: nil)
  doc = parse(content, file_path: file_path)

  # Extract all sections
  section_data = Atoms::SectionExtractor.extract_all(doc.raw_body)

  sections = section_data.map do |s|
    Section.new(
      heading: s[:heading],
      level: s[:level],
      content: s[:content] || ""
    )
  end

  doc.with_sections(sections)
end

Instance Method Details

#==(other) ⇒ Boolean

Compare documents for equality

Parameters:

Returns:

  • (Boolean)


115
116
117
118
119
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 115

def ==(other)
  other.is_a?(MarkdownDocument) &&
    @frontmatter == other.frontmatter &&
    @raw_body == other.raw_body
end

#find_section(heading) ⇒ Section?

Find a section by heading text

Parameters:

  • heading (String)

    The heading to find

Returns:



77
78
79
80
81
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 77

def find_section(heading)
  return nil unless @sections

  @sections.find { |s| s.heading == heading }
end

#get_frontmatter(key) ⇒ Object?

Get a specific frontmatter field

Parameters:

  • key (String, Symbol)

    The field key

Returns:

  • (Object, nil)

    The field value



70
71
72
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 70

def get_frontmatter(key)
  @frontmatter[key.to_s] || @frontmatter[key.to_sym]
end

#has_frontmatter?Boolean

Check if document has frontmatter

Returns:

  • (Boolean)


91
92
93
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 91

def has_frontmatter?
  !@frontmatter.empty?
end

#has_sections?Boolean

Check if document has sections parsed

Returns:

  • (Boolean)


97
98
99
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 97

def has_sections?
  !@sections.nil? && !@sections.empty?
end

#statsHash

Get document statistics

Returns:

  • (Hash)


103
104
105
106
107
108
109
110
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 103

def stats
  {
    frontmatter_fields: @frontmatter.keys.length,
    body_length: @raw_body.length,
    sections_count: @sections&.length || 0,
    word_count: @raw_body.split(/\s+/).length
  }
end

#to_hHash

Hash representation

Returns:

  • (Hash)


123
124
125
126
127
128
129
130
131
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 123

def to_h
  {
    frontmatter: @frontmatter,
    raw_body: @raw_body,
    sections: @sections&.map(&:to_h),
    file_path: @file_path,
    stats: stats
  }
end

#to_markdownString

Convert document to complete markdown string

Returns:

  • (String)

    The complete markdown document



85
86
87
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 85

def to_markdown
  Atoms::FrontmatterSerializer.rebuild_document(@frontmatter, @raw_body)
end

#with_body(new_body) ⇒ MarkdownDocument

Create a new document with updated body

Parameters:

  • new_body (String)

    The new body content

Returns:



43
44
45
46
47
48
49
50
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 43

def with_body(new_body)
  MarkdownDocument.new(
    frontmatter: @frontmatter,
    raw_body: new_body,
    sections: nil, # Invalidate sections cache
    file_path: @file_path
  )
end

#with_frontmatter(updates) ⇒ MarkdownDocument

Create a new document with updated frontmatter

Parameters:

  • updates (Hash)

    Frontmatter updates to merge

Returns:



29
30
31
32
33
34
35
36
37
38
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 29

def with_frontmatter(updates)
  new_frontmatter = @frontmatter.merge(updates)

  MarkdownDocument.new(
    frontmatter: new_frontmatter,
    raw_body: @raw_body,
    sections: @sections,
    file_path: @file_path
  )
end

#with_sections(new_sections) ⇒ MarkdownDocument

Create a new document with updated sections

Parameters:

  • new_sections (Array<Section>)

    The new sections

Returns:



55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ace/support/markdown/models/markdown_document.rb', line 55

def with_sections(new_sections)
  # Rebuild raw_body from sections
  new_body = new_sections.map(&:to_markdown).join("\n\n")

  MarkdownDocument.new(
    frontmatter: @frontmatter,
    raw_body: new_body,
    sections: new_sections,
    file_path: @file_path
  )
end