4 class RDoc::RI::Formatter
12 FORMATTERS[name.downcase]
16 FORMATTERS.keys.sort.join ", "
19 def initialize(output, width, indent)
23 @original_indent = indent.dup
26 def draw_line(label=nil)
28 len -= (label.size + 1) if label
31 @output.print '-' * len
39 @output.print '-' * @width
47 return @indent unless block_given?
51 @indent += @original_indent
58 def wrap(txt, prefix=@indent, linelen=@width)
59 return unless txt && !txt.empty?
61 work = conv_markup(txt)
62 textLen = linelen - prefix.length
63 patt = Regexp.new("^(.{0,#{textLen}})[ \n]")
64 next_prefix = prefix.tr("^ ", " ")
68 while work.length > textLen
71 work.slice!(0, $&.length)
73 res << work.slice!(0, textLen)
76 res << work if work.length.nonzero?
77 @output.puts(prefix + res.join("\n" + next_prefix))
85 # Called when we want to ensure a new 'wrap' starts on a newline. Only
86 # needed for HtmlFormatter, because the rest do their own line breaking.
95 def raw_print_line(txt)
100 # Convert HTML entities back to ASCII
103 txt = txt.gsub(/>/, '>')
104 txt.gsub!(/</, '<')
105 txt.gsub!(/"/, '"')
106 txt.gsub!(/&/, '&')
111 # Convert markup into display form
114 txt = txt.gsub(%r{<tt>(.*?)</tt>}, '+\1+')
115 txt.gsub!(%r{<code>(.*?)</code>}, '+\1+')
116 txt.gsub!(%r{<b>(.*?)</b>}, '*\1*')
117 txt.gsub!(%r{<em>(.*?)</em>}, '_\1_')
121 def display_list(list)
124 prefixer = proc { |ignored| @indent + "* " }
126 when :NUMBER, :UPPERALPHA, :LOWERALPHA then
127 start = case list.type
129 when :UPPERALPHA then 'A'
130 when :LOWERALPHA then 'a'
133 prefixer = proc do |ignored|
134 res = @indent + "#{start}.".ljust(4)
139 when :LABELED, :NOTE then
142 list.contents.each do |item|
143 if RDoc::Markup::Flow::LI === item and item.label.length > longest then
144 longest = item.label.length
150 prefixer = proc { |li| @indent + li.label.ljust(longest) }
153 raise ArgumentError, "unknown list type #{list.type}"
156 list.contents.each do |item|
157 if RDoc::Markup::Flow::LI === item then
158 prefix = prefixer.call item
159 display_flow_item item, prefix
161 display_flow_item item
166 def display_flow_item(item, prefix = @indent)
168 when RDoc::Markup::Flow::P, RDoc::Markup::Flow::LI
169 wrap(conv_html(item.body), prefix)
172 when RDoc::Markup::Flow::LIST
175 when RDoc::Markup::Flow::VERB
176 display_verbatim_flow_item(item, @indent)
178 when RDoc::Markup::Flow::H
179 display_heading(conv_html(item.text), item.level, @indent)
181 when RDoc::Markup::Flow::RULE
185 raise RDoc::Error, "Unknown flow element: #{item.class}"
189 def display_verbatim_flow_item(item, prefix=@indent)
190 item.body.split(/\n/).each do |line|
191 @output.print @indent, conv_html(line), "\n"
196 def display_heading(text, level, indent)
197 text = strip_attributes text
201 ul = "=" * text.length
203 @output.puts text.upcase
207 ul = "-" * text.length
212 @output.print indent, text, "\n"
218 def display_flow(flow)
224 def strip_attributes(text)
225 text.gsub(/(<\/?(?:b|code|em|i|tt)>)/, '')
231 # Handle text with attributes. We're a base class: there are different
232 # presentation classes (one, for example, uses overstrikes to handle bold and
233 # underlining, while another using ANSI escape sequences.
235 class RDoc::RI::AttributeFormatter < RDoc::RI::Formatter
249 AttrChar = Struct.new :char, :attr
251 class AttributeString
267 # accept non space, then all following spaces
272 while @optr < len && @txt[@optr].char != " "
276 while @optr < len && @txt[@optr].char == " "
285 # Overrides base class. Looks for <tt>...</tt> etc sequences and generates
286 # an array of AttrChars. This array is then used as the basis for the
289 def wrap(txt, prefix=@indent, linelen=@width)
290 return unless txt && !txt.empty?
292 txt = add_attributes_to(txt)
293 next_prefix = prefix.tr("^ ", " ")
294 linelen -= prefix.size
300 if word.size + line.size > linelen
301 write_attribute_text(prefix, line)
308 write_attribute_text(prefix, line) if line.length > 0
313 def write_attribute_text(prefix, line)
316 @output.print achar.char
327 def add_attributes_to(txt)
328 tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
329 text = AttributeString.new
333 when %r{^</(\w+)>$} then attributes &= ~(ATTR_MAP[$1]||0)
334 when %r{^<(\w+)>$} then attributes |= (ATTR_MAP[$1]||0)
336 tok.split(//).each {|ch| text << AttrChar.new(ch, attributes)}
345 # This formatter generates overstrike-style formatting, which works with
346 # pagers such as man and less.
348 class RDoc::RI::OverstrikeFormatter < RDoc::RI::AttributeFormatter
352 def write_attribute_text(prefix, line)
357 @output.print "_", BS if (attr & (ITALIC + CODE)) != 0
358 @output.print achar.char, BS if (attr & BOLD) != 0
359 @output.print achar.char
366 # Draw a string in bold
369 text.split(//).each do |ch|
370 @output.print ch, BS, ch
377 # This formatter uses ANSI escape sequences to colorize stuff works with
378 # pagers such as man and less.
380 class RDoc::RI::AnsiFormatter < RDoc::RI::AttributeFormatter
382 def initialize(*args)
384 @output.print "\033[0m"
387 def write_attribute_text(prefix, line)
392 if achar.attr != curr_attr
393 update_attributes(achar.attr)
394 curr_attr = achar.attr
396 @output.print achar.char
398 update_attributes(0) unless curr_attr.zero?
403 @output.print "\033[1m#{txt}\033[m"
407 1 => ["\033[1;32m", "\033[m"],
408 2 => ["\033[4;32m", "\033[m"],
409 3 => ["\033[32m", "\033[m"],
412 def display_heading(text, level, indent)
413 level = 3 if level > 3
414 heading = HEADINGS[level]
416 @output.print heading[0]
417 @output.print strip_attributes(text)
418 @output.puts heading[1]
429 def update_attributes(attr)
431 for quality in [ BOLD, ITALIC, CODE]
432 unless (attr & quality).zero?
433 str << ATTR_MAP[quality]
436 @output.print str, "m"
442 # This formatter uses HTML.
444 class RDoc::RI::HtmlFormatter < RDoc::RI::AttributeFormatter
446 def write_attribute_text(prefix, line)
450 if achar.attr != curr_attr
451 update_attributes(curr_attr, achar.attr)
452 curr_attr = achar.attr
454 @output.print(escape(achar.char))
456 update_attributes(curr_attr, 0) unless curr_attr.zero?
459 def draw_line(label=nil)
478 def display_heading(text, level, indent)
479 level = 4 if level > 4
480 tag("h#{level}") { text }
484 def display_list(list)
488 prefixer = proc { |ignored| "<li>" }
490 when :NUMBER, :UPPERALPHA, :LOWERALPHA then
492 prefixer = proc { |ignored| "<li>" }
496 prefixer = proc do |li|
497 "<dt><b>" + escape(li.label) + "</b><dd>"
502 prefixer = proc do |li|
503 %{<tr valign="top"><td>#{li.label.gsub(/ /, ' ')}</td><td>}
506 fail "unknown list type"
509 @output.print "<#{list_type}>"
510 list.contents.each do |item|
511 if item.kind_of? RDoc::Markup::Flow::LI
512 prefix = prefixer.call(item)
514 display_flow_item(item, prefix)
516 display_flow_item(item)
519 @output.print "</#{list_type}>"
522 def display_verbatim_flow_item(item, prefix=@indent)
523 @output.print("<pre>")
524 item.body.split(/\n/).each do |line|
525 @output.puts conv_html(line)
527 @output.puts("</pre>")
538 def update_attributes(current, wanted)
540 # first turn off unwanted ones
541 off = current & ~wanted
542 for quality in [ BOLD, ITALIC, CODE]
543 if (off & quality) > 0
544 str << "</" + ATTR_MAP[quality]
549 for quality in [ BOLD, ITALIC, CODE]
550 unless (wanted & quality).zero?
551 str << "<" << ATTR_MAP[quality]
558 @output.print("<#{code}>")
560 @output.print("</#{code}>")
564 str = str.gsub(/&/n, '&')
565 str.gsub!(/\"/n, '"')
566 str.gsub!(/>/n, '>')
567 str.gsub!(/</n, '<')
574 # This formatter reduces extra lines for a simpler output. It improves way
575 # output looks for tools like IRC bots.
577 class RDoc::RI::SimpleFormatter < RDoc::RI::Formatter
580 # No extra blank lines
586 # Display labels only, no lines
588 def draw_line(label=nil)
589 unless label.nil? then
596 # Place heading level indicators inline with heading.
598 def display_heading(text, level, indent)
599 text = strip_attributes(text)
602 @output.puts "= " + text.upcase
604 @output.puts "-- " + text
606 @output.print indent, text, "\n"
612 RDoc::RI::Formatter::FORMATTERS['plain'] = RDoc::RI::Formatter
613 RDoc::RI::Formatter::FORMATTERS['simple'] = RDoc::RI::SimpleFormatter
614 RDoc::RI::Formatter::FORMATTERS['bs'] = RDoc::RI::OverstrikeFormatter
615 RDoc::RI::Formatter::FORMATTERS['ansi'] = RDoc::RI::AnsiFormatter
616 RDoc::RI::Formatter::FORMATTERS['html'] = RDoc::RI::HtmlFormatter