Tree Processor Extension

A TreeProcessor extension is run on the Document instance after the blocks in the source have been parsed into a document structure, as represented by the Document object and its child AbstractNode objects (e.g., Section, Block, List, ListItem).

Asciidoctor invokes the Processor#process method on an instance of each registered TreeProcessor extension.

A TreeProcessor extension is run before any inline markup is interpreted. At the time the process method on the TreeProcessor extension is invoked, the block nodes in the document have unprocessed, unconverted text. Conversion of inline markup is done when the document is being converted. Thus, it’s possible for the TreeProcessor extension to replace the source text on the nodes, but it must consider the fact that the text is still going to be parsed (according to the content model of the block).

Tree Processor Extension Example

Purpose

Detect literal blocks that contain shell commands, strip the prompt character and style the command using CSS in such a way that the prompt character cannot be selected (as seen on help.github.com).

sample-with-shell-session.adoc

 $ echo "Hello, World!"
 > Hello, World!

 $ gem install asciidoctor

ShellSessionTreeProcessor

class ShellSessionTreeProcessor < Asciidoctor::Extensions::TreeProcessor
  LF = ?\n

  def process document
    (document.find_by(context: :literal) {|candidate| candidate.lines[0].start_with? '$ ', '> ' }).each do |block|
      (children = block.parent.blocks)[children.index block] = convert_to_terminal_listing block
    end
    nil
  end

  def convert_to_terminal_listing block
    attrs = block.attributes
    attrs['role'] = 'terminal'
    prompt_attr = (attrs.key? 'prompt') ? %( data-prompt="#{block.sub_specialchars attrs['prompt']}") : nil
    lines = (block.content.split LF).map do |line|
      if line.start_with? '$ '
        %(<span class="command"#{prompt_attr}>#{line[2..-1]}</span>)
      elsif line.start_with? '&gt; '
        %(<span class="output">#{line[5..-1]}</span>)
        #%(<span class="output"><span class="comment-prefix"># </span>#{line[5..-1]}</span>)
      else
        line
      end
    end
    create_listing_block block.parent, lines * LF, attrs, subs: nil
  end
end

Usage

Asciidoctor::Extensions.register do
  tree_processor ShellSessionTreeProcessor
end

Asciidoctor.convert_file 'sample-with-shell-session.adoc', safe: :safe