lib/asciidoctor/converter/manpage.rb in asciidoctor-1.5.7.1 vs lib/asciidoctor/converter/manpage.rb in asciidoctor-1.5.8

- old
+ new

@@ -18,23 +18,34 @@ LeadingPeriodRx = /^\./ EscapedMacroRx = /^(?:#{ESC}\\c\n)?#{ESC}\.((?:URL|MTO) ".*?" ".*?" )( |[^\s]*)(.*?)(?: *#{ESC}\\c)?$/ MockBoundaryRx = /<\/?BOUNDARY>/ EmDashCharRefRx = /&#8212;(?:&#8203;)?/ EllipsisCharRefRx = /&#8230;(?:&#8203;)?/ + WrappedIndentRx = /#{CG_BLANK}*#{LF}#{CG_BLANK}*/ # 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) + # * :whitespace an enum that indicates how to handle whitespace; supported options are: + # :preserve - preserve spaces (only expanding tabs); :normalize - normalize whitespace + # (remove spaces around newlines); :collapse - collapse adjacent whitespace to a single + # space (default: :collapse) # * :append_newline a Boolean that indicates whether to append an endline to the result (default: false) def manify str, opts = {} - str = ((opts.fetch :preserve_space, true) ? (str.gsub TAB, ET) : (str.tr_s WHITESPACE, ' ')). + case opts.fetch :whitespace, :collapse + when :preserve + str = str.gsub TAB, ET + when :normalize + str = str.gsub WrappedIndentRx, LF + else + str = str.tr_s WHITESPACE, ' ' + end + str = str. 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? ? %(.#$1"#$2") : %(.#$1"#$2"#{LF}#{rest}) }. gsub('-', '\-'). @@ -76,23 +87,25 @@ 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 + manmanual = node.attr 'manmanual' + mansource = node.attr 'mansource' docdate = (node.attr? 'reproducible') ? nil : (node.attr 'docdate') # NOTE the first line enables the table (tbl) preprocessor, necessary for non-Linux systems result = [%('\\" t .\\" Title: #{mantitle} .\\" Author: #{(node.attr? 'authors') ? (node.attr 'authors') : '[see the "AUTHOR(S)" section]'} .\\" Generator: Asciidoctor #{node.attr 'asciidoctor-version'})] result << %(.\\" Date: #{docdate}) if docdate - result << %(.\\" Manual: #{(manual = node.attr 'manmanual') || '\ \&'} -.\\" Source: #{(source = node.attr 'mansource') || '\ \&'} + result << %(.\\" Manual: #{manmanual ? (manmanual.tr_s WHITESPACE, ' ') : '\ \&'} +.\\" Source: #{mansource ? (mansource.tr_s WHITESPACE, ' ') : '\ \&'} .\\" Language: English .\\") # TODO add document-level setting to disable capitalization of manname - result << %(.TH "#{manify manname.upcase}" "#{manvolnum}" "#{docdate}" "#{source ? (manify source) : '\ \&'}" "#{manual ? (manify manual) : '\ \&'}") + result << %(.TH "#{manify manname.upcase}" "#{manvolnum}" "#{docdate}" "#{mansource ? (manify mansource) : '\ \&'}" "#{manmanual ? (manify manmanual) : '\ \&'}") # 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 \'' @@ -129,12 +142,12 @@ result << '.\}' unless node.noheader if node.attr? 'manpurpose' mannames = node.attr 'mannames', [manname] - result << %(.SH "#{(node.attr 'manname-title').upcase}" -#{mannames.map {|n| manify n } * ', '} \\- #{manify node.attr 'manpurpose'}) + result << %(.SH "#{(node.attr 'manname-title', 'NAME').upcase}" +#{mannames.map {|n| manify n }.join ', '} \\- #{manify node.attr('manpurpose'), :whitespace => :normalize}) end end result << node.content @@ -142,26 +155,25 @@ if node.footnotes? && !(node.attr? 'nofootnotes') result << '.SH "NOTES"' result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) }) end - # FIXME we really need an API that returns the authors as an array - if (num_authors = (node.attr 'authorcount') || 0) > 0 - if num_authors == 1 - result << %(.SH "AUTHOR" -.sp -#{node.attr 'author'}) - else + unless (authors = node.authors).empty? + if authors.size > 1 result << '.SH "AUTHORS"' - (1.upto num_authors).each do |i| + authors.each do |author| result << %(.sp -#{node.attr "author_#{i}"}) +#{author.name}) end + else + result << %(.SH "AUTHOR" +.sp +#{authors[0].name}) end end - result * LF + result.join LF end # NOTE embedded doesn't really make sense in the manpage backend def embedded node result = [node.content] @@ -171,11 +183,11 @@ result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) }) end # QUESTION should we add an AUTHOR(S) section? - result * LF + result.join LF end def section node result = [] if node.level > 1 @@ -186,11 +198,11 @@ macro = 'SH' stitle = node.title.upcase end result << %(.#{macro} "#{manify stitle}" #{node.content}) - result * LF + result.join LF end def admonition node result = [] result << %(.if n .sp @@ -204,11 +216,11 @@ .ps -1 .br #{resolve_content node} .sp .5v .RE) - result * LF + result.join LF end alias audio skip_with_warning def colist node @@ -221,16 +233,16 @@ r lw(\n(.lu*75u/100u).' num = 0 node.items.each do |item| result << %(\\fB(#{num += 1})\\fP\\h'-2n':T{) - result << (manify item.text) + result << (manify item.text, :whitespace => :normalize) result << item.content if item.blocks? result << 'T}' end result << '.TE' - result * LF + result.join LF end # TODO implement horizontal (if it makes sense) def dlist node result = [] @@ -241,35 +253,35 @@ node.items.each do |terms, dd| counter += 1 case node.style when 'qanda' result << %(.sp -#{counter}. #{manify([*terms].map {|dt| dt.text } * ' ')} +#{counter}. #{manify [*terms].map {|dt| dt.text }.join ' '} .RS 4) else result << %(.sp -#{manify([*terms].map {|dt| dt.text } * ', ')} +#{manify [*terms].map {|dt| dt.text }.join(', '), :whitespace => :normalize} .RS 4) end if dd - result << (manify dd.text) if dd.text? + result << (manify dd.text, :whitespace => :normalize) if dd.text? result << dd.content if dd.blocks? end result << '.RE' end - result * LF + result.join LF end def example node result = [] result << %(.sp .B #{manify node.captioned_title} .br) if node.title? result << %(.RS 4 #{resolve_content node} .RE) - result * LF + result.join LF end def floating_title node %(.SS "#{manify node.title}") end @@ -282,28 +294,28 @@ .B #{manify node.captioned_title} .br) if node.title? result << %(.sp .if n .RS 4 .nf -#{manify node.content} +#{manify node.content, :whitespace => :preserve} .fi .if n .RE) - result * LF + result.join LF end def literal node result = [] result << %(.sp .B #{manify node.title} .br) if node.title? result << %(.sp .if n .RS 4 .nf -#{manify node.content} +#{manify node.content, :whitespace => :preserve} .fi .if n .RE) - result * LF + result.join LF end def olist node result = [] result << %(.sp @@ -318,15 +330,15 @@ .\\} .el \\{\\ . sp -1 . IP " #{idx + 1}." 4.2 .\\} -#{manify item.text}) +#{manify item.text, :whitespace => :normalize}) result << item.content if item.blocks? result << '.RE' end - result * LF + result.join LF end def open node case node.style when 'abstract', 'partintro' @@ -342,46 +354,44 @@ def paragraph node if node.title? %(.sp .B #{manify node.title} .br -#{manify node.content}) +#{manify node.content, :whitespace => :normalize}) else %(.sp -#{manify node.content}) +#{manify node.content, :whitespace => :normalize}) end end alias preamble content def quote node result = [] if node.title? result << %(.sp -.in +.3i +.RS 3 .B #{manify node.title} .br -.in) +.RE) end attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil - result << %(.in +.3i -.ll -.3i -.nf + result << %(.RS 3 +.ll -.6i #{resolve_content node} -.fi .br -.in +.RE .ll) if attribution_line - result << %(.in +.5i -.ll -.5i + result << %(.RS 5 +.ll -.10i #{attribution_line} -.in +.RE .ll) end - result * LF + result.join LF end alias sidebar skip_with_warning def stem node @@ -446,11 +456,11 @@ row_header[row_index][cell_index] << %(#{cell_halign}tB) else row_header[row_index][cell_index + 1] ||= [] row_header[row_index][cell_index + 1] << %(#{cell_halign}tB) end - row_text[row_index] << %(#{manify cell.text}#{LF}) + row_text[row_index] << %(#{manify cell.text, :whitespace => :normalize}#{LF}) elsif tsec == :body if row_header[row_index].empty? || row_header[row_index][cell_index].empty? row_header[row_index][cell_index] << %(#{cell_halign}t) else row_header[row_index][cell_index + 1] ||= [] @@ -458,25 +468,25 @@ end case cell.style when :asciidoc cell_content = cell.content when :literal - cell_content = %(.nf#{LF}#{manify cell.text}#{LF}.fi) + cell_content = %(.nf#{LF}#{manify cell.text, :whitespace => :preserve}#{LF}.fi) when :verse - cell_content = %(.nf#{LF}#{manify cell.text}#{LF}.fi) + cell_content = %(.nf#{LF}#{manify cell.text, :whitespace => :preserve}#{LF}.fi) else - cell_content = manify cell.content.join + cell_content = manify cell.content.join, :whitespace => :normalize end row_text[row_index] << %(#{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] << %(#{cell_halign}tB) else row_header[row_index][cell_index + 1] ||= [] row_header[row_index][cell_index + 1] << %(#{cell_halign}tB) end - row_text[row_index] << %(#{manify cell.text}#{LF}) + row_text[row_index] << %(#{manify cell.text, :whitespace => :normalize}#{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' @@ -509,17 +519,17 @@ end #row_header.each do |row| # result << LF # row.each_with_index do |cell, i| - # result << (cell * ' ') + # result << (cell.join ' ') # 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 << ('lt ' * row_header[0].size).chop result << %(.#{LF}) row_text.each do |row| result << row.join end @@ -548,15 +558,15 @@ .\\} .el \\{\\ . sp -1 . IP \\(bu 2.3 .\\} -#{manify item.text}] +#{manify item.text, :whitespace => :normalize}] result << item.content if item.blocks? result << '.RE' } - result * LF + result.join LF end # FIXME git uses [verse] for the synopsis; detect this special case def verse node result = [] @@ -567,32 +577,32 @@ end attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil result << %(.sp .nf -#{manify node.content} +#{manify node.content, :whitespace => :preserve} .fi .br) if attribution_line result << %(.in +.5i .ll -.5i #{attribution_line} .in .ll) end - result * LF + result.join LF end def video node start_param = (node.attr? 'start', nil, false) ? %(&start=#{node.attr 'start'}) : '' end_param = (node.attr? 'end', nil, false) ? %(&end=#{node.attr 'end'}) : '' result = [] result << %(.sp .B #{manify node.title} .br) if node.title? result << %(<#{node.media_uri(node.attr 'target')}#{start_param}#{end_param}> (video)) - result * LF + result.join LF end def inline_anchor node target = node.target case node.type @@ -653,19 +663,19 @@ def inline_kbd node if (keys = node.attr 'keys').size == 1 keys[0] else - keys * %(#{ESC_BS}0+#{ESC_BS}0) + keys.join %(#{ESC_BS}0+#{ESC_BS}0) end end def inline_menu node caret = %[#{ESC_BS}0#{ESC_BS}(fc#{ESC_BS}0] menu = node.attr 'menu' if !(submenus = node.attr 'submenus').empty? - submenu_path = submenus.map {|item| %(#{ESC_BS}fI#{item}#{ESC_BS}fP) } * caret + submenu_path = submenus.map {|item| %(#{ESC_BS}fI#{item}#{ESC_BS}fP) }.join caret %(#{ESC_BS}fI#{menu}#{ESC_BS}fP#{caret}#{submenu_path}#{caret}#{ESC_BS}fI#{node.attr 'menuitem'}#{ESC_BS}fP) elsif (menuitem = node.attr 'menuitem') %(#{ESC_BS}fI#{menu}#{caret}#{menuitem}#{ESC_BS}fP) else %(#{ESC_BS}fI#{menu}#{ESC_BS}fP) @@ -689,10 +699,10 @@ node.text end end def resolve_content node - node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content}) + node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content, :whitespace => :normalize}) end def write_alternate_pages mannames, manvolnum, target if mannames && mannames.size > 1 mannames.shift