class Asciidoctor::Converter::ManPageConverter - asciidoctor-1.5.6.2 Documentation

# class Asciidoctor::Converter::ManPageConverter

A built-in {Converter} implementation that generates the man page (troff) format.

The output follows the groff man page definition while also trying to be consistent with the output produced by the a2x tool from AsciiDoc Python.

See www.gnu.org/software/groff/manual/html_node/Man-usage.html#Man-usage

### Constants

ESC
ESC_BS
ESC_FS
ET
EllipsisCharRefRx
EmDashCharRefRx
EscapedMacroRx
LF
LeadingPeriodRx
LiteralBackslashRx
MockBoundaryRx
TAB
WHITESPACE

### Public Instance Methods

admonition(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 177
        def admonition node
          result = []
          result << %Q(.if n \\{\\
    .sp
    .\\}
    .RS 4
    .it 1 an-trap
    .nr an-no-space-flag 1
    .nr an-break-flag 1
    .br
    .ps +1
    .B #{node.attr 'textlabel'}#{node.title? ? "\\fP: #{manify node.title}" : nil}
    .ps -1
    .br
    #{resolve_content node}
    .sp .5v
    .RE)
          result * LF
        end

audio(node, name = nil)
Alias for: skip_with_warning
colist(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 199
        def colist node
          result = []
          result << %Q(.sp
    .B #{manify node.title}
    .br) if node.title?
          result << '.TS
    tab(:);
    r lw(\n(.lu*75u/100u).'

          num = 0
          node.items.each do |item|
            result << %Q(\\fB(#{num += 1})\\fP\\h'-2n':T{)
            result << (manify item.text)
            result << item.content if item.blocks?
            result << 'T}'
          end
          result << '.TE'
          result * LF
        end

dlist(node) click to toggle source

TODO implement title for dlist TODO implement horizontal (if it makes sense)

    # File lib/asciidoctor/converter/manpage.rb, line 221
        def dlist node
          result = []
          counter = 0
          node.items.each do |terms, dd|
            counter += 1
            case node.style
            when 'qanda'
              result << %Q(.sp
    #{counter}. #{manify([*terms].map {|dt| dt.text } * ' ')}
    .RS 4)
            else
              result << %Q(.sp
    #{manify([*terms].map {|dt| dt.text } * ', ')}
    .RS 4)
            end
            if dd
              result << (manify dd.text) if dd.text?
              result << dd.content if dd.blocks?
            end
            result << '.RE'
          end
          result * LF
        end

document(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 73
        def document node
          unless node.attr? 'mantitle'
            raise 'asciidoctor: ERROR: doctype must be set to manpage when using manpage backend'
          end
          mantitle = node.attr 'mantitle'
          manvolnum = node.attr 'manvolnum', '1'
          manname = node.attr 'manname', mantitle
          docdate = (node.attr? 'reproducible') ? nil : (node.attr 'docdate')
          # NOTE the first line enables the table (tbl) preprocessor, necessary for non-Linux systems
          result = [%Q('\\" t
    .\\"     Title: #{mantitle}
    .\\"    Author: #{(node.attr? 'authors') ? (node.attr 'authors') : '[see the "AUTHORS" section]'}
    .\\" Generator: Asciidoctor #{node.attr 'asciidoctor-version'})]
          result << %Q(.\\"      Date: #{docdate}) if docdate
          result << %Q(.\\"    Manual: #{(manual = node.attr 'manmanual') || '\ \&'}
    .\\"    Source: #{(source = node.attr 'mansource') || '\ \&'}
    .\\"  Language: English
    .\\")
          # TODO add document-level setting to disable capitalization of manname
          result << %Q(.TH "#{manify manname.upcase}" "#{manvolnum}" "#{docdate}" "#{source ? (manify source) : '\ \&'}" "#{manual ? (manify manual) : '\ \&'}")
          # define portability settings
          # see http://bugs.debian.org/507673
          # see http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
          result << '.ie \n(.g .ds Aq \(aq'
          result << '.el       .ds Aq \'
          # set sentence_space_size to 0 to prevent extra space between sentences separated by a newline
          # the alternative is to add \& at the end of the line
          result << '.ss \n[.ss] 0'
          # disable hyphenation
          result << '.nh'
          # disable justification (adjust text to left margin only)
          result << '.ad l'
          # define URL macro for portability
          # see http://web.archive.org/web/20060102165607/http://people.debian.org/~branden/talks/wtfm/wtfm.pdf
          #
          # Use: .URL "http://www.debian.org" "Debian" "."
          #
          # * First argument: the URL
          # * Second argument: text to be hyperlinked
          # * Third (optional) argument: text that needs to immediately trail
          #   the hyperlink without intervening whitespace
          result << '.de URL
    \\$2 \(laURL: \\$1 \(ra\\$3
    ..
    .if \n[.g] .mso www.tmac'
          result << %Q(.LINKSTYLE #{node.attr 'man-linkstyle', 'blue R < >'})

          unless node.noheader
            if node.attr? 'manpurpose'
              result << %Q(.SH "#{node.attr 'manname-title'}"
    #{manify mantitle} \\- #{manify node.attr 'manpurpose'})
            end
          end

          result << node.content

          # QUESTION should NOTES come after AUTHOR(S)?
          if node.footnotes? && !(node.attr? 'nofootnotes')
            result << '.SH "NOTES"'
            result.concat(node.footnotes.map {|fn| %Q(#{fn.index}. #{fn.text}) })
          end

          # FIXME detect single author and use appropriate heading; itemize the authors if multiple
          if node.attr? 'authors'
            result << %Q(.SH "AUTHOR(S)"
    .sp
    \\fB#{node.attr 'authors'}\\fP
    .RS 4
    Author(s).
    .RE)
          end

          result * LF
        end

embedded(node) click to toggle source

NOTE embedded doesn't really make sense in the manpage backend

    # File lib/asciidoctor/converter/manpage.rb, line 149
    def embedded node
      result = [node.content]

      if node.footnotes? && !(node.attr? 'nofootnotes')
        result << '.SH "NOTES"'
        result.concat(node.footnotes.map {|fn| %Q(#{fn.index}. #{fn.text}) })
      end

      # QUESTION should we add an AUTHOR(S) section?

      result * LF
    end

example(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 245
        def example node
          result = []
          result << %Q(.sp
    .B #{manify node.captioned_title}
    .br) if node.title?
          result << %Q(.RS 4
    #{resolve_content node}
    .RE)
          result * LF
        end

floating_title(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 256
    def floating_title node
      %Q(.SS "#{manify node.title}")
    end

image(node, name = nil)
Alias for: skip_with_warning
inline_anchor(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 587
    def inline_anchor node
      target = node.target
      case node.type
      when :link
        if (text = node.text) == target
          text = nil
        else
          text = text.gsub '"', %Q[#{ESC_BS}(dq]
        end
        if target.start_with? 'mailto:'
          macro = 'MTO'
          target = target[7..-1].sub '@', %Q[#{ESC_BS}(at]
        else
          macro = 'URL'
        end
        %Q(#{ESC_BS}c#{LF}#{ESC_FS}#{macro} "#{target}" "#{text}" )
      when :xref
        refid = (node.attr 'refid') || target
        node.text || (node.document.catalog[:ids][refid] || %Q([#{refid}]))
      when :ref, :bibref
        # These are anchor points, which shouldn't be visible
        ''
      else
        warn %Q(asciidoctor: WARNING: unknown anchor type: #{node.type.inspect})
      end
    end

inline_break(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 614
    def inline_break node
      %Q(#{node.text}#{LF}#{ESC_FS}br)
    end

inline_button(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 618
    def inline_button node
      %Q(#{ESC_BS}fB[#{ESC_BS}0#{node.text}#{ESC_BS}0]#{ESC_BS}fP)
    end

inline_callout(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 622
    def inline_callout node
      %Q(#{ESC_BS}fB(#{node.text})#{ESC_BS}fP)
    end

inline_footnote(node) click to toggle source

TODO supposedly groff has footnotes, but we're in search of an example

    # File lib/asciidoctor/converter/manpage.rb, line 627
    def inline_footnote node
      if (index = node.attr 'index')
        %Q([#{index}])
      elsif node.type == :xref
        %Q([#{node.text}])
      end
    end

inline_image(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 635
    def inline_image node
      (node.attr? 'link') ? %Q([#{node.alt}] <#{node.attr 'link'}>) : %Q([#{node.alt}])
    end

inline_indexterm(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 639
    def inline_indexterm node
      node.type == :visible ? node.text : ''
    end

inline_kbd(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 643
    def inline_kbd node
      if (keys = node.attr 'keys').size == 1
        keys[0]
      else
        keys * %Q(#{ESC_BS}0+#{ESC_BS}0)
      end
    end

inline_quoted(node) click to toggle source

NOTE use fake <BOUNDARY> element to prevent creating artificial word boundaries

    # File lib/asciidoctor/converter/manpage.rb, line 665
    def inline_quoted node
      case node.type
      when :emphasis
        %Q(#{ESC_BS}fI<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
      when :strong
        %Q(#{ESC_BS}fB<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
      when :monospaced
        %Q(#{ESC_BS}f[CR]<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
      when :single
        %Q[#{ESC_BS}(oq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(cq]
      when :double
        %Q[#{ESC_BS}(lq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(rq]
      else
        node.text
      end
    end

listing(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 262
        def listing node
          result = []
          result << %Q(.sp
    .B #{manify node.captioned_title}
    .br) if node.title?
          result << %Q(.sp
    .if n \\{\\
    .RS 4
    .\\}
    .nf
    #{manify node.content}
    .fi
    .if n \\{\\
    .RE
    .\\})
          result * LF
        end

literal(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 280
        def literal node
          result = []
          result << %Q(.sp
    .B #{manify node.title}
    .br) if node.title?
          result << %Q(.sp
    .if n \\{\\
    .RS 4
    .\\}
    .nf
    #{manify node.content}
    .fi
    .if n \\{\\
    .RE
    .\\})
          result * LF
        end

manify(str, opts = {}) click to toggle source

Converts HTML entity references back to their original form, escapes special man characters and strips trailing whitespace.

It's crucial that text only ever pass through manify once.

str - the String to convert opts - an Hash of options to control processing (default: {})

    * :preserve_space a Boolean that indicates whether to preserve spaces (only expanding tabs) if true
      or to collapse all adjacent whitespace to a single space if false (default: true)
    * :append_newline a Boolean that indicates whether to append an endline to the result (default: false)

    # File lib/asciidoctor/converter/manpage.rb, line 34
    def manify str, opts = {}
      str = ((opts.fetch :preserve_space, true) ? (str.gsub TAB, ET) : (str.tr_s WHITESPACE, ' ')).
        gsub(LiteralBackslashRx, '\&(rs'). # literal backslash (not a troff escape sequence)
        gsub(LeadingPeriodRx, '\\&.'). # leading . is used in troff for macro call or other formatting; replace with \&.
        # drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
        gsub(EscapedMacroRx) { (rest = $3.lstrip).empty? ? %Q(.#$1"#$2") : %Q(.#$1"#$2"#{LF}#{rest}) }.
        gsub('-', '\-').
        gsub('&lt;', '<').
        gsub('&gt;', '>').
        gsub('&#160;', '\~').     # non-breaking space
        gsub('&#169;', '\(co').   # copyright sign
        gsub('&#174;', '\(rg').   # registered sign
        gsub('&#8482;', '\(tm').  # trademark sign
        gsub('&#8201;', ' ').     # thin space
        gsub('&#8211;', '\(en').  # en dash
        gsub(EmDashCharRefRx, '\(em'). # em dash
        gsub('&#8216;', '\(oq').  # left single quotation mark
        gsub('&#8217;', '\(cq').  # right single quotation mark
        gsub('&#8220;', '\(lq').  # left double quotation mark
        gsub('&#8221;', '\(rq').  # right double quotation mark
        gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
        gsub('&#8592;', '\(<-').  # leftwards arrow
        gsub('&#8594;', '\(->').  # rightwards arrow
        gsub('&#8656;', '\(lA').  # leftwards double arrow
        gsub('&#8658;', '\(rA').  # rightwards double arrow
        gsub('&#8203;', '\:').    # zero width space
        gsub('\', '\(aq').       # apostrophe-quote
        gsub(MockBoundaryRx, ''). # mock boundary
        gsub(ESC_BS, '\').       # unescape troff backslash (NOTE update if more escapes are added)
        gsub(ESC_FS, '.').        # unescape full stop in troff commands (NOTE must take place after gsub(LeadingPeriodRx))
        rstrip                    # strip trailing space
      opts[:append_newline] ? %Q(#{str}#{LF}) : str
    end

olist(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 298
        def olist node
          result = []
          result << %Q(.sp
    .B #{manify node.title}
    .br) if node.title?

          node.items.each_with_index do |item, idx|
            result << %Q(.sp
    .RS 4
    .ie n \\{\\
    \\h'-04' #{idx + 1}.\\h'+01'\\c
    .\\}
    .el \\{\\
    .sp -1
    .IP " #{idx + 1}." 4.2
    .\\}
    #{manify item.text})
            result << item.content if item.blocks?
            result << '.RE'
          end
          result * LF
        end

open(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 321
    def open node
      case node.style
      when 'abstract', 'partintro'
        resolve_content node
      else
        node.content
      end
    end

paragraph(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 333
        def paragraph node
          if node.title?
            %Q(.sp
    .B #{manify node.title}
    .br
    #{manify node.content})
          else
            %Q(.sp
    #{manify node.content})
          end
        end

quote(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 347
        def quote node
          result = []
          if node.title?
            result << %Q(.sp
    .in +.3i
    .B #{manify node.title}
    .br
    .in)
          end
          attribution_line = (node.attr? 'citetitle') ? %Q(#{node.attr 'citetitle'} ) : nil
          attribution_line = (node.attr? 'attribution') ? %Q[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
          result << %Q(.in +.3i
    .ll -.3i
    .nf
    #{resolve_content node}
    .fi
    .br
    .in
    .ll)
          if attribution_line
            result << %Q(.in +.5i
    .ll -.5i
    #{attribution_line}
    .in
    .ll)
          end
          result * LF
        end

resolve_content(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 682
    def resolve_content node
      node.content_model == :compound ? node.content : %Q(.sp#{LF}#{manify node.content})
    end

section(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 162
        def section node
          result = []
          if node.level > 1
            macro = 'SS'
            # QUESTION why captioned title? why not when level == 1?
            stitle = node.captioned_title
          else
            macro = 'SH'
            stitle = node.title.upcase
          end
          result << %Q(.#{macro} "#{manify stitle}"
    #{node.content})
          result * LF
        end

skip_with_warning(node, name = nil) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 68
    def skip_with_warning node, name = nil
      warn %Q(asciidoctor: WARNING: converter missing for #{name || node.node_name} node in manpage backend)
      nil
    end

Also aliased as: audio, image, sidebar
stem(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 378
        def stem node
          title_element = node.title? ? %Q(.sp
    .B #{manify node.title}
    .br) : nil
          open, close = BLOCK_MATH_DELIMITERS[node.style.to_sym]

          unless ((equation = node.content).start_with? open) && (equation.end_with? close)
            equation = %Q(#{open}#{equation}#{close})
          end

          %Q(#{title_element}#{equation})
        end

table(node) click to toggle source

FIXME: The reason this method is so complicated is because we are not receiving empty(marked) cells when there are colspans or rowspans. This method has to create a map of all cells and in the case of rowspans create empty cells as placeholders of the span. To fix this, asciidoctor needs to provide an API to tell the user if a given cell is being used as a colspan or rowspan.

    # File lib/asciidoctor/converter/manpage.rb, line 397
        def table node
          result = []
          if node.title?
            result << %Q(.sp
    .it 1 an-trap
    .nr an-no-space-flag 1
    .nr an-break-flag 1
    .br
    .B #{manify node.captioned_title}
    )
          end
          result << '.TS
    allbox tab(:);'
          row_header = []
          row_text = []
          row_index = 0
          node.rows.by_section.each do |tsec, rows|
            rows.each do |row|
              row_header[row_index] ||= []
              row_text[row_index] ||= []
              # result << LF
              # l left-adjusted
              # r right-adjusted
              # c centered-adjusted
              # n numerical align
              # a alphabetic align
              # s spanned
              # ^ vertically spanned
              remaining_cells = row.size
              row.each_with_index do |cell, cell_index|
                remaining_cells -= 1
                row_header[row_index][cell_index] ||= []
                # Add an empty cell if this is a rowspan cell
                if row_header[row_index][cell_index] == ['^t']
                  row_text[row_index] << %Q(T{#{LF}.sp#{LF}T}:)
                end
                row_text[row_index] << %Q(T{#{LF}.sp#{LF})
                cell_halign = (cell.attr 'halign', 'left').chr
                if tsec == :head
                  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
                    row_header[row_index][cell_index] << %Q(#{cell_halign}tB)
                  else
                    row_header[row_index][cell_index + 1] ||= []
                    row_header[row_index][cell_index + 1] << %Q(#{cell_halign}tB)
                  end
                  row_text[row_index] << %Q(#{manify cell.text}#{LF})
                elsif tsec == :body
                  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
                    row_header[row_index][cell_index] << %Q(#{cell_halign}t)
                  else
                    row_header[row_index][cell_index + 1] ||= []
                    row_header[row_index][cell_index + 1] << %Q(#{cell_halign}t)
                  end
                  case cell.style
                  when :asciidoc
                    cell_content = cell.content
                  when :literal
                    cell_content = %Q(.nf#{LF}#{manify cell.text}#{LF}.fi)
                  when :verse
                    cell_content = %Q(.nf#{LF}#{manify cell.text}#{LF}.fi)
                  else
                    cell_content = manify cell.content.join
                  end
                  row_text[row_index] << %Q(#{cell_content}#{LF})
                elsif tsec == :foot
                  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
                    row_header[row_index][cell_index] << %Q(#{cell_halign}tB)
                  else
                    row_header[row_index][cell_index + 1] ||= []
                    row_header[row_index][cell_index + 1] << %Q(#{cell_halign}tB)
                  end
                  row_text[row_index] << %Q(#{manify cell.text}#{LF})
                end
                if cell.colspan && cell.colspan > 1
                  (cell.colspan - 1).times do |i|
                    if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
                      row_header[row_index][cell_index + i] << 'st'
                    else
                      row_header[row_index][cell_index + 1 + i] ||= []
                      row_header[row_index][cell_index + 1 + i] << 'st'
                    end
                  end
                end
                if cell.rowspan && cell.rowspan > 1
                  (cell.rowspan - 1).times do |i|
                    row_header[row_index + 1 + i] ||= []
                    if row_header[row_index + 1 + i].empty? || row_header[row_index + 1 + i][cell_index].empty?
                      row_header[row_index + 1 + i][cell_index] ||= []
                      row_header[row_index + 1 + i][cell_index] << '^t'
                    else
                      row_header[row_index + 1 + i][cell_index + 1] ||= []
                      row_header[row_index + 1 + i][cell_index + 1] << '^t'
                    end
                  end
                end
                if remaining_cells >= 1
                  row_text[row_index] << 'T}:'
                else
                  row_text[row_index] << %Q(T}#{LF})
                end
              end
              row_index += 1
            end unless rows.empty?
          end

          #row_header.each do |row|
          #  result << LF
          #  row.each_with_index do |cell, i|
          #    result << (cell * ' ')
          #    result << ' ' if row.size > i + 1
          #  end
          #end
          # FIXME temporary fix to get basic table to display
          result << LF
          result << row_header[0].map { 'lt' } * ' '

          result << %Q(.#{LF})
          row_text.each do |row|
            result << row.join
          end
          result << %Q(.TE#{LF}.sp)
          result.join
        end

thematic_break(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 521
        def thematic_break node
          '.sp
    .ce
    \l\\n(.lu*25u/100u\(ap\'
        end

ulist(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 529
        def ulist node
          result = []
          result << %Q(.sp
    .B #{manify node.title}
    .br) if node.title?
          node.items.map {|item|
            result << %Q[.sp
    .RS 4
    .ie n \\{\\
    \\h'-04'\\(bu\\h'+03'\\c
    .\\}
    .el \\{\\
    .sp -1
    .IP \\(bu 2.3
    .\\}
    #{manify item.text}]
            result << item.content if item.blocks?
            result << '.RE'
          }
          result * LF
        end

verse(node) click to toggle source

FIXME git uses [verse] for the synopsis; detect this special case

    # File lib/asciidoctor/converter/manpage.rb, line 552
        def verse node
          result = []
          if node.title?
            result << %Q(.sp
    .B #{manify node.title}
    .br)
          end
          attribution_line = (node.attr? 'citetitle') ? %Q(#{node.attr 'citetitle'} ) : nil
          attribution_line = (node.attr? 'attribution') ? %Q[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
          result << %Q(.sp
    .nf
    #{manify node.content}
    .fi
    .br)
          if attribution_line
            result << %Q(.in +.5i
    .ll -.5i
    #{attribution_line}
    .in
    .ll)
          end
          result * LF
        end

video(node) click to toggle source

    # File lib/asciidoctor/converter/manpage.rb, line 576
        def video node
          start_param = (node.attr? 'start', nil, false) ? %Q(&start=#{node.attr 'start'}) : nil
          end_param = (node.attr? 'end', nil, false) ? %Q(&end=#{node.attr 'end'}) : nil
          result = []
          result << %Q(.sp
    .B #{manify node.title}
    .br) if node.title?
          result << %Q(<#{node.media_uri(node.attr 'target')}#{start_param}#{end_param}> (video))
          result * LF
        end