/benchmark/app/rdoc-2.4.3/lib/rdoc/ri/formatter.rb
Ruby | 654 lines | 478 code | 127 blank | 49 comment | 34 complexity | cd52ada6b10eed08d346ef9e2af179bd MD5 | raw file
Possible License(s): BSD-3-Clause, MPL-2.0-no-copyleft-exception, 0BSD, GPL-2.0, LGPL-2.1
- require 'rdoc/ri'
- require 'rdoc/markup'
- class RDoc::RI::Formatter
- attr_writer :indent
- attr_accessor :output
- FORMATTERS = { }
- def self.for(name)
- FORMATTERS[name.downcase]
- end
- def self.list
- FORMATTERS.keys.sort.join ", "
- end
- def initialize(output, width, indent)
- @output = output
- @width = width
- @indent = indent
- @original_indent = indent.dup
- end
- def draw_line(label=nil)
- len = @width
- len -= (label.size + 1) if label
- if len > 0 then
- @output.print '-' * len
- if label
- @output.print ' '
- bold_print label
- end
- @output.puts
- else
- @output.print '-' * @width
- @output.puts
- @output.puts label
- end
- end
- def indent
- return @indent unless block_given?
- begin
- indent = @indent.dup
- @indent += @original_indent
- yield
- ensure
- @indent = indent
- end
- end
- def wrap(txt, prefix=@indent, linelen=@width)
- return unless txt && !txt.empty?
- work = conv_markup(txt)
- textLen = linelen - prefix.length
- patt = Regexp.new("^(.{0,#{textLen}})[ \n]")
- next_prefix = prefix.tr("^ ", " ")
- res = []
- while work.length > textLen
- if work =~ patt
- res << $1
- work.slice!(0, $&.length)
- else
- res << work.slice!(0, textLen)
- end
- end
- res << work if work.length.nonzero?
- @output.puts(prefix + res.join("\n" + next_prefix))
- end
- def blankline
- @output.puts
- end
- ##
- # Called when we want to ensure a new 'wrap' starts on a newline. Only
- # needed for HtmlFormatter, because the rest do their own line breaking.
- def break_to_newline
- end
- def bold_print(txt)
- @output.print txt
- end
- def raw_print_line(txt)
- @output.print txt
- end
- ##
- # Convert HTML entities back to ASCII
- def conv_html(txt)
- txt = txt.gsub(/>/, '>')
- txt.gsub!(/</, '<')
- txt.gsub!(/"/, '"')
- txt.gsub!(/&/, '&')
- txt
- end
- ##
- # Convert markup into display form
- def conv_markup(txt)
- txt = txt.gsub(%r{<tt>(.*?)</tt>}, '+\1+')
- txt.gsub!(%r{<code>(.*?)</code>}, '+\1+')
- txt.gsub!(%r{<b>(.*?)</b>}, '*\1*')
- txt.gsub!(%r{<em>(.*?)</em>}, '_\1_')
- txt
- end
- def display_list(list)
- case list.type
- when :BULLET
- prefixer = proc { |ignored| @indent + "* " }
- when :NUMBER, :UPPERALPHA, :LOWERALPHA then
- start = case list.type
- when :NUMBER then 1
- when :UPPERALPHA then 'A'
- when :LOWERALPHA then 'a'
- end
- prefixer = proc do |ignored|
- res = @indent + "#{start}.".ljust(4)
- start = start.succ
- res
- end
- when :LABELED, :NOTE then
- longest = 0
- list.contents.each do |item|
- if RDoc::Markup::Flow::LI === item and item.label.length > longest then
- longest = item.label.length
- end
- end
- longest += 1
- prefixer = proc { |li| @indent + li.label.ljust(longest) }
- else
- raise ArgumentError, "unknown list type #{list.type}"
- end
- list.contents.each do |item|
- if RDoc::Markup::Flow::LI === item then
- prefix = prefixer.call item
- display_flow_item item, prefix
- else
- display_flow_item item
- end
- end
- end
- def display_flow_item(item, prefix = @indent)
- case item
- when RDoc::Markup::Flow::P, RDoc::Markup::Flow::LI
- wrap(conv_html(item.body), prefix)
- blankline
- when RDoc::Markup::Flow::LIST
- display_list(item)
- when RDoc::Markup::Flow::VERB
- display_verbatim_flow_item(item, @indent)
- when RDoc::Markup::Flow::H
- display_heading(conv_html(item.text), item.level, @indent)
- when RDoc::Markup::Flow::RULE
- draw_line
- else
- raise RDoc::Error, "Unknown flow element: #{item.class}"
- end
- end
- def display_verbatim_flow_item(item, prefix=@indent)
- item.body.split(/\n/).each do |line|
- @output.print @indent, conv_html(line), "\n"
- end
- blankline
- end
- def display_heading(text, level, indent)
- text = strip_attributes text
- case level
- when 1 then
- ul = "=" * text.length
- @output.puts
- @output.puts text.upcase
- @output.puts ul
- when 2 then
- ul = "-" * text.length
- @output.puts
- @output.puts text
- @output.puts ul
- else
- @output.print indent, text, "\n"
- end
- @output.puts
- end
- def display_flow(flow)
- flow.each do |f|
- display_flow_item(f)
- end
- end
- def strip_attributes(text)
- text.gsub(/(<\/?(?:b|code|em|i|tt)>)/, '')
- end
- end
- ##
- # Handle text with attributes. We're a base class: there are different
- # presentation classes (one, for example, uses overstrikes to handle bold and
- # underlining, while another using ANSI escape sequences.
- class RDoc::RI::AttributeFormatter < RDoc::RI::Formatter
- BOLD = 1
- ITALIC = 2
- CODE = 4
- ATTR_MAP = {
- "b" => BOLD,
- "code" => CODE,
- "em" => ITALIC,
- "i" => ITALIC,
- "tt" => CODE
- }
- AttrChar = Struct.new :char, :attr
- class AttributeString
- attr_reader :txt
- def initialize
- @txt = []
- @optr = 0
- end
- def <<(char)
- @txt << char
- end
- def empty?
- @optr >= @txt.length
- end
- # accept non space, then all following spaces
- def next_word
- start = @optr
- len = @txt.length
- while @optr < len && @txt[@optr].char != " "
- @optr += 1
- end
- while @optr < len && @txt[@optr].char == " "
- @optr += 1
- end
- @txt[start...@optr]
- end
- end
- ##
- # Overrides base class. Looks for <tt>...</tt> etc sequences and generates
- # an array of AttrChars. This array is then used as the basis for the
- # split.
- def wrap(txt, prefix=@indent, linelen=@width)
- return unless txt && !txt.empty?
- txt = add_attributes_to(txt)
- next_prefix = prefix.tr("^ ", " ")
- linelen -= prefix.size
- line = []
- until txt.empty?
- word = txt.next_word
- if word.size + line.size > linelen
- write_attribute_text(prefix, line)
- prefix = next_prefix
- line = []
- end
- line.concat(word)
- end
- write_attribute_text(prefix, line) if line.length > 0
- end
- protected
- def write_attribute_text(prefix, line)
- @output.print prefix
- line.each do |achar|
- @output.print achar.char
- end
- @output.puts
- end
- def bold_print(txt)
- @output.print txt
- end
- private
- def add_attributes_to(txt)
- tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
- text = AttributeString.new
- attributes = 0
- tokens.each do |tok|
- case tok
- when %r{^</(\w+)>$} then attributes &= ~(ATTR_MAP[$1]||0)
- when %r{^<(\w+)>$} then attributes |= (ATTR_MAP[$1]||0)
- else
- tok.split(//).each {|ch| text << AttrChar.new(ch, attributes)}
- end
- end
- text
- end
- end
- ##
- # This formatter generates overstrike-style formatting, which works with
- # pagers such as man and less.
- class RDoc::RI::OverstrikeFormatter < RDoc::RI::AttributeFormatter
- BS = "\C-h"
- def write_attribute_text(prefix, line)
- @output.print prefix
- line.each do |achar|
- attr = achar.attr
- @output.print "_", BS if (attr & (ITALIC + CODE)) != 0
- @output.print achar.char, BS if (attr & BOLD) != 0
- @output.print achar.char
- end
- @output.puts
- end
- ##
- # Draw a string in bold
- def bold_print(text)
- text.split(//).each do |ch|
- @output.print ch, BS, ch
- end
- end
- end
- ##
- # This formatter uses ANSI escape sequences to colorize stuff works with
- # pagers such as man and less.
- class RDoc::RI::AnsiFormatter < RDoc::RI::AttributeFormatter
- def initialize(*args)
- super
- @output.print "\033[0m"
- end
- def write_attribute_text(prefix, line)
- @output.print prefix
- curr_attr = 0
- line.each do |achar|
- attr = achar.attr
- if achar.attr != curr_attr
- update_attributes(achar.attr)
- curr_attr = achar.attr
- end
- @output.print achar.char
- end
- update_attributes(0) unless curr_attr.zero?
- @output.puts
- end
- def bold_print(txt)
- @output.print "\033[1m#{txt}\033[m"
- end
- HEADINGS = {
- 1 => ["\033[1;32m", "\033[m"],
- 2 => ["\033[4;32m", "\033[m"],
- 3 => ["\033[32m", "\033[m"],
- }
- def display_heading(text, level, indent)
- level = 3 if level > 3
- heading = HEADINGS[level]
- @output.print indent
- @output.print heading[0]
- @output.print strip_attributes(text)
- @output.puts heading[1]
- end
- private
- ATTR_MAP = {
- BOLD => "1",
- ITALIC => "33",
- CODE => "36"
- }
- def update_attributes(attr)
- str = "\033["
- for quality in [ BOLD, ITALIC, CODE]
- unless (attr & quality).zero?
- str << ATTR_MAP[quality]
- end
- end
- @output.print str, "m"
- end
- end
- ##
- # This formatter uses HTML.
- class RDoc::RI::HtmlFormatter < RDoc::RI::AttributeFormatter
- ##
- # We depend on HTML4-conforming user agents to ignore an empty p element
- def blankline
- @output.puts '<p />'
- end
- ##
- # Emboldens +text+
- def bold_print(text)
- tag("b") { text }
- end
- ##
- # Outputs a forced line break element
- def break_to_newline
- @output.puts '<br />'
- end
- ##
- # Outputs heading elements for +text+ with +level+ up to 4. Ignores
- # +indent+.
- def display_heading(text, level, indent)
- level = 4 if level > 4
- tag("h#{level}") { text }
- @output.puts
- end
- ##
- # Outputs +list+ which is displayed as follows:
- #
- # BULLET:: unordered list
- # NUMBER:: ordered list
- # LABELED:: definition list
- # NOTE:: table
- def display_list(list)
- case list.type
- when :BULLET then
- list_type = "ul"
- prefixer = proc { |ignored| '<li>' }
- suffix = '</li>'
- when :NUMBER, :UPPERALPHA, :LOWERALPHA then
- list_type = "ol"
- prefixer = proc { |ignored| '<li>' }
- suffix = '</li>'
- when :LABELED then
- list_type = "dl"
- prefixer = proc do |li|
- "<dt><b>#{escape li.label}</b></dt><dd>"
- end
- suffix = '</dd>'
- when :NOTE then
- list_type = "table"
- prefixer = proc do |li|
- %{<tr valign="top"><td>#{li.label.gsub(/ /, ' ')}</td><td>}
- end
- suffix = '</td></tr>'
- else
- fail "unknown list type"
- end
- @output.print "<#{list_type}>"
- list.contents.each do |item|
- if item.kind_of? RDoc::Markup::Flow::LI
- prefix = prefixer.call item
- @output.print prefix
- display_flow_item item, prefix
- @output.print suffix
- else
- display_flow_item item
- end
- end
- @output.print "</#{list_type}>"
- end
- ##
- # Outputs a preformatted section for +item+. +prefix+ is ignored.
- def display_verbatim_flow_item(item, prefix=@indent)
- @output.print '<pre>'
- item.body.split(/\n/).each do |line|
- @output.puts escape(line)
- end
- @output.puts '</pre>'
- end
- ##
- # Outputs a horizontal rule element, optionally labeled above with +label+ in
- # bold.
- def draw_line(label = nil)
- bold_print label if label
- @output.puts "<hr />"
- end
- def write_attribute_text(prefix, line)
- curr_attr = 0
- line.each do |achar|
- attr = achar.attr
- if achar.attr != curr_attr then
- update_attributes curr_attr, achar.attr
- curr_attr = achar.attr
- end
- @output.print escape(achar.char)
- end
- update_attributes curr_attr, 0 unless curr_attr.zero?
- end
- private
- ATTR_MAP = {
- BOLD => "b>",
- ITALIC => "i>",
- CODE => "tt>"
- }
- def update_attributes(current, wanted)
- str = ""
- # first turn off unwanted ones
- off = current & ~wanted
- for quality in [ BOLD, ITALIC, CODE]
- if (off & quality) > 0
- str << "</" + ATTR_MAP[quality]
- end
- end
- # now turn on wanted
- for quality in [ BOLD, ITALIC, CODE]
- unless (wanted & quality).zero?
- str << "<" << ATTR_MAP[quality]
- end
- end
- @output.print str
- end
- def tag(code)
- @output.print("<#{code}>")
- @output.print(yield)
- @output.print("</#{code}>")
- end
- def escape(str)
- str = str.gsub(/&/n, '&')
- str.gsub!(/\"/n, '"')
- str.gsub!(/>/n, '>')
- str.gsub!(/</n, '<')
- str
- end
- end
- ##
- # This formatter reduces extra lines for a simpler output. It improves way
- # output looks for tools like IRC bots.
- class RDoc::RI::SimpleFormatter < RDoc::RI::Formatter
- ##
- # No extra blank lines
- def blankline
- end
- ##
- # Display labels only, no lines
- def draw_line(label=nil)
- unless label.nil? then
- bold_print(label)
- @output.puts
- end
- end
- ##
- # Place heading level indicators inline with heading.
- def display_heading(text, level, indent)
- text = strip_attributes(text)
- case level
- when 1
- @output.puts "= " + text.upcase
- when 2
- @output.puts "-- " + text
- else
- @output.print indent, text, "\n"
- end
- end
- end
- RDoc::RI::Formatter::FORMATTERS['plain'] = RDoc::RI::Formatter
- RDoc::RI::Formatter::FORMATTERS['simple'] = RDoc::RI::SimpleFormatter
- RDoc::RI::Formatter::FORMATTERS['bs'] = RDoc::RI::OverstrikeFormatter
- RDoc::RI::Formatter::FORMATTERS['ansi'] = RDoc::RI::AnsiFormatter
- RDoc::RI::Formatter::FORMATTERS['html'] = RDoc::RI::HtmlFormatter