class Md2conf::ConfluenceUtil

ConfluenceUtil class contains various helpers for processing XHTML generated by redcarpet gem.

Public Class Methods

new(html, max_toc_level, config_file) click to toggle source

@param [String] html XHTML rendered by redcarpet gem (must have fenced code blocks). @param [Integer] max_toc_level Table of Contents maximum header depth.

# File lib/md2conf.rb, line 15
def initialize(html, max_toc_level, config_file)
  @html          = html
  @max_toc_level = max_toc_level
  return unless File.file?(File.expand_path(config_file))
  @config = YAML.load_file(File.expand_path(config_file))
  return unless @config.key? 'macros'
  @macros = @config['macros']
end

Public Instance Methods

add_toc() click to toggle source

Add Table of Contents Confluence tag at the beginning.

Use @max_toc_level class variable to specify maximum header depth.

# File lib/md2conf.rb, line 157
    def add_toc
      return if @max_toc_level == 0
      @html = <<~HTML
        <ac:structured-macro ac:name="toc">
          <ac:parameter ac:name="maxLevel">#{@max_toc_level}</ac:parameter>
        </ac:structured-macro>
        #{@html}
      HTML
    end
convert_info_macros() click to toggle source

Convert Info macros to Confluence-friendly format:

@example Regular informational message

> My info message

@example A Note (`Note: ` will be removed from the message)

> Note: An important note

@example A Warning (`Warning: ` will be removed from the message)

> Warning: An extremely important warning
# File lib/md2conf.rb, line 102
    def convert_info_macros
      confluence_code = <<~HTML
        <ac:structured-macro ac:name="%<macro_name>s">
          <ac:rich-text-body>
            %<quote>s
          </ac:rich-text-body>
        </ac:structured-macro>
      HTML

      @html.scan(%r{<blockquote>(.*?)</blockquote>}m).each do |quote|
        quote = quote.first
        if quote.include? 'Note: '
          quote_new  = quote.strip.sub 'Note: ', ''
          macro_name = 'note'
        elsif quote.include? 'Warning: '
          quote_new  = quote.strip.sub 'Warning: ', ''
          macro_name = 'warning'
        else
          quote_new  = quote.strip
          macro_name = 'info'
        end
        @html.sub! "<blockquote>#{quote}</blockquote>", format(confluence_code, macro_name: macro_name, quote: quote_new)
      end
    end
parse() click to toggle source

Launch all internal parsers.

# File lib/md2conf.rb, line 25
def parse
  process_macros if @macros
  process_mentions
  convert_info_macros
  process_code_blocks
  add_toc

  @html
end
process_code_blocks() click to toggle source

Convert regular code blocks to Confluence code blocks. Language type is also supported.

`puppet` is currently replaced by `ruby` language type.

# File lib/md2conf.rb, line 131
    def process_code_blocks
      @html.scan(%r{<pre><code.*?>.*?</code></pre>}m).each do |codeblock|
        content = codeblock.match(%r{<pre><code.*?>(.*?)</code></pre>}m)[1]
        lang    = codeblock.match(/code class="(.*)"/)
        lang    = if lang.nil?
          'none'
        else
          lang[1].sub('puppet', 'ruby')
        end

        confluence_code = <<~HTML
          <ac:structured-macro ac:name="code">
            <ac:parameter ac:name="theme">RDark</ac:parameter>
            <ac:parameter ac:name="linenumbers">true</ac:parameter>
            <ac:parameter ac:name="language">#{lang}</ac:parameter>
            <ac:plain-text-body><![CDATA[#{CGI.unescape_html content}]]></ac:plain-text-body>
          </ac:structured-macro>
        HTML

        @html.sub! codeblock, confluence_code
      end
    end
process_macros() click to toggle source

Process custom macros. Macro definitions should be placed in `~/.md2conf.yaml`. Format is described in the README.

Macros are blocks that are contained in curly braces like this: `{JIRA:52837}`.

# File lib/md2conf.rb, line 39
def process_macros
  html_new      = ''
  last_position = 0
  @html.scan(/{(.*?)}/) do |macro|
    next if inside_code_block Regexp.last_match.pre_match
    macro_name = macro.first.split(':')[0]
    macro_arg  = macro.first.split(':')[1]

    confluence_code = if @macros.include? macro_name
      @macros[macro_name] % { arg: macro_arg }
    else
      "<code>#{macro.first}</code>"
    end

    since_last_match = @html[last_position..Regexp.last_match.begin(0) - 1]
    html_new << "#{since_last_match}#{confluence_code}"
    last_position = Regexp.last_match.end(0)
  end

  # Did we have at least one match?
  return unless Regexp.last_match
  @html = html_new << if inside_code_block Regexp.last_match.pre_match
    @html[last_position..-1]
  else
    Regexp.last_match.post_match
  end
end
process_mentions() click to toggle source

Process username mentions.

Everything that starts with an `@` and is not enclosed in a code block will be converted to a valid Confluence username.

# File lib/md2conf.rb, line 71
def process_mentions
  html_new      = ''
  last_position = 0
  @html.scan(/@(\w+)/) do |mention|
    next if inside_code_block Regexp.last_match.pre_match

    confluence_code  = "<ac:link><ri:user ri:username=\"#{mention.first}\"/></ac:link>"
    since_last_match = @html[last_position..Regexp.last_match.begin(0) - 1]
    html_new << "#{since_last_match}#{confluence_code}"
    last_position = Regexp.last_match.end(1)
  end

  # Did we have at least one match?
  return unless Regexp.last_match
  @html = html_new << if inside_code_block Regexp.last_match.pre_match
    @html[last_position..-1]
  else
    Regexp.last_match.post_match
  end
end

Private Instance Methods

inside_code_block(pre_match) click to toggle source

Check whether we're inside a code block based on pre_match variable.

# File lib/md2conf.rb, line 170
def inside_code_block(pre_match)
  # *
  return false unless pre_match.include? '<code'

  # <code> *
  return true unless pre_match.include? '</code>'

  # <code></code> *
  # <code></code><code> *
  pre_match.rindex('<code') > pre_match.rindex('</code>')
end