class MdTransformer::Markdown

Class representing a parsed markdown file

Constants

LOWEST_PRECEDENCE

The lowest valid precedence of a header, allows for up to H6 (###### Header)

Attributes

children[R]

@return [Array] the array of child objects

content[R]

@return [String] the content of the Markdown object

parent[R]

@return [MdTransformer::Markdown, nil] nil or the parent of the current Markdown object

title[RW]

@return [String] the title of the Markdown object

Public Class Methods

new(source = '', options = {}) click to toggle source

Creates a new Markdown object @param source [String] the markdown content or path to a markdown file @param options [Hash] the options hash @option options [Boolean] :file whether to treat the passed source as a file path @option options [MdTransformer::Markdown] :parent the parent of the new object @option options [String] :title the title of the new object @return [MdTransformer::Markdown] the new Markdown object

# File lib/md_transformer/markdown.rb, line 32
def initialize(source = '', options = {})
  @parent = options[:parent]
  @title = options[:title] || ''
  if options[:file]
    raise InvalidMarkdownPath, "Could not find markdown file at #{source}" unless File.exist?(source)

    source = File.read(source)
    @title ||= options[:file]
  end
  source = translate(source)
  parse!(source)
end

Public Instance Methods

<=>(other) click to toggle source

Compares Markdown objects with other objects through string conversion @param other [Object] object to compare @return [Integer] the result of the <=> operator on the string values of both objects

# File lib/md_transformer/markdown.rb, line 164
def <=>(other)
  to_s <=> other.to_s
end
[](key) click to toggle source

Retrieves the child object at a given key if it exists @param key [String] the key of the child object @return [MdTransformer::Markdown, nil] the found child object or nil

# File lib/md_transformer/markdown.rb, line 102
def [](key)
  @children.find { |c| c.title == key }
end
[]=(key, value) click to toggle source

Sets the value of the child object at a given key. If the key does not exist, a new child object is created. @param key [String] the key of the child object @param value [String] the new value of the child object @return [String] the newly assigned value

# File lib/md_transformer/markdown.rb, line 110
def []=(key, value)
  child = self[key] || Markdown.new('', title: key, parent: self)
  child.content = value.to_s
  @children.push(child) unless key?(key)
end
content=(value) click to toggle source

Updates the current object's Markdown content @param value [String] the new content @return [String] the newly added content

# File lib/md_transformer/markdown.rb, line 48
def content=(value)
  # Reflow the content headers based on the child's levels (raise exception if level exceeds 6)
  m = Markdown.new(value.to_s)

  # Reassign the parent of the children to the current object and ensure the new depth is valid
  @children = m.children
  @children.each do |c|
    c.instance_variable_set(:@parent, self)
    validate_levels(c)
  end

  @content = m.content
end
dig(key, *rest) click to toggle source

Digs through the hash for the child object at the given nested key(s) @param key [String] the first key to check @param rest [Array<String>] any number of nested string keys for which to find @return [MdTransformer::Markdown, nil] the found child object or nil

# File lib/md_transformer/markdown.rb, line 92
def dig(key, *rest)
  value = self[key]
  return value if value.nil? || rest.empty?

  value.dig(*rest)
end
each() { |title, child| ... } click to toggle source

For a block { |k, v| … } @yield [k, v] Gives the key and value of the object

# File lib/md_transformer/markdown.rb, line 153
def each
  return enum_for(__method__) unless block_given?

  @children.each do |child|
    yield child.title, child
  end
end
key?(key) click to toggle source

Checks for whether a child object has a given key @param key [String] the key of the child object @return [Boolean] whether the passed key exists

# File lib/md_transformer/markdown.rb, line 71
def key?(key)
  !self[key].nil?
end
keys() click to toggle source

Gets all child object keys @return [Array] the array of child keys

# File lib/md_transformer/markdown.rb, line 64
def keys
  @children.map(&:title)
end
level() click to toggle source

Calculates the current nesting level of the Markdown object @return [Integer] the nesting level of the object (0 for the root, +1 for each additional level)

# File lib/md_transformer/markdown.rb, line 145
def level
  return 0 if root?

  @parent.level + 1
end
root?() click to toggle source

Checks whether the current object is the root of the Markdown object @return [Boolean] whether the current object is the root

# File lib/md_transformer/markdown.rb, line 139
def root?
  @parent.nil?
end
to_s(options = { title: true }) click to toggle source

Creates a string representing the markdown document's content from current content and all child content @param options [Hash] the options hash @option options [Boolean] :title (true) whether to include the title of the current object in the output @return [String] the constructed Markdown string

# File lib/md_transformer/markdown.rb, line 120
def to_s(options = { title: true })
  title_str = root? ? '' : "#{'#' * level} #{@title}\n"
  md_string = "#{options[:title] ? title_str : ''}#{@content}#{@children.map(&:to_s).join}"
  md_string << "\n" unless md_string.end_with?("\n")
  md_string
end
value?(value) click to toggle source

Checks for whether a child object has a given value @param value [String] the value to check for @return [Boolean] whether the passed value exists

# File lib/md_transformer/markdown.rb, line 84
def value?(value)
  !@children.find { |c| c == value }.nil?
end
values() click to toggle source

Gets all child object values @return [Array] the array of child objects

# File lib/md_transformer/markdown.rb, line 77
def values
  @children
end
write(path, options: { create_dir: true }) click to toggle source

Writes the current markdown object to a file @param path [String] the path to the new file @param options [Hash] the options hash @option options [Boolean] :create_dir (true) whether to create the parent directories of the path @return [Integer] the length of the newly created file

# File lib/md_transformer/markdown.rb, line 132
def write(path, options: { create_dir: true })
  FileUtils.mkdir_p(File.dirname(path)) if options[:create_dir]
  File.write(path, to_s)
end

Private Instance Methods

parse!(content) click to toggle source

Parses the provided markdown string content into a the current object's content and children @param content [String] the string Markdown content to parse

# File lib/md_transformer/markdown.rb, line 191
def parse!(content)
  @children = []
  @content = ''

  # Parse all direct children and create new markdown objects
  sections = Section.generate_sections(content)

  # No children!
  if sections.empty?
    @content = content
    return
  end

  # Populate content prior to found headers
  @content = content[0..sections.first.header_location.begin - 1] if sections.first.header_location.begin > 0

  parse_children!(sections)
end
parse_children!(sections) click to toggle source

Parses all available sections into direct children of the current object @param sections [Array] the array of Markdown::Section objects to parse

# File lib/md_transformer/markdown.rb, line 212
def parse_children!(sections)
  # Go through the headers sequentially to find all direct children (base on header level vs. current level)
  last_child_level = LOWEST_PRECEDENCE + 1

  sections.each do |s|
    # Finish parsing if we encounter a sibling (same level) or aunt/uncle (higher level)
    break if s.level <= level

    if s.level <= last_child_level
      @children.push(Markdown.new(s.content, title: s.title, parent: self))
      last_child_level = s.level
    end
  end
end
translate(content) click to toggle source

Translates content to markdown prior to parsing @param content [String] the string Markdown content to translate @return [String] the translated content

# File lib/md_transformer/markdown.rb, line 185
def translate(content)
  content.gsub(%r{<h([1-6])>(.*?)</h[1-6]>}m) { '#' * Regexp.last_match[1].to_i + ' ' + Regexp.last_match[2] }
end
validate_levels(child) click to toggle source

Validates that all children have valid precedence levels and raises an exception if they are not @param child [MdTransformer::Markdown] child object ot validate @return [MdTransformer::Markdown] the validated child object

# File lib/md_transformer/markdown.rb, line 173
def validate_levels(child)
  if child.level >= LOWEST_PRECEDENCE
    raise HeaderTooDeep, "#{child.title} header level (h#{child.level}) is beyond h#{LOWEST_PRECEDENCE}"
  end

  child.children.each { |c| validate_levels(c) }
  child
end