PageRenderTime 76ms CodeModel.GetById 32ms app.highlight 39ms RepoModel.GetById 2ms app.codeStats 0ms

/tools/Ruby/lib/ruby/1.8/rdoc/ri/ri_formatter.rb

http://github.com/agross/netopenspace
Ruby | 672 lines | 505 code | 123 blank | 44 comment | 35 complexity | f6f85e55107da4f50f54190dbce82189 MD5 | raw file
  1module RI
  2  class TextFormatter
  3
  4    attr_reader :indent
  5    
  6    def initialize(options, indent)
  7      @options = options
  8      @width   = options.width
  9      @indent  = indent
 10    end
 11    
 12    
 13    ######################################################################
 14    
 15    def draw_line(label=nil)
 16      len = @width
 17      len -= (label.size+1) if label
 18      print "-"*len
 19      if label
 20        print(" ")
 21        bold_print(label) 
 22      end
 23      puts
 24    end
 25    
 26    ######################################################################
 27    
 28    def wrap(txt,  prefix=@indent, linelen=@width)
 29      return unless txt && !txt.empty?
 30      work = conv_markup(txt)
 31      textLen = linelen - prefix.length
 32      patt = Regexp.new("^(.{0,#{textLen}})[ \n]")
 33      next_prefix = prefix.tr("^ ", " ")
 34
 35      res = []
 36
 37      while work.length > textLen
 38        if work =~ patt
 39          res << $1
 40          work.slice!(0, $&.length)
 41        else
 42          res << work.slice!(0, textLen)
 43        end
 44      end
 45      res << work if work.length.nonzero?
 46      puts(prefix + res.join("\n" + next_prefix))
 47    end
 48
 49    ######################################################################
 50
 51    def blankline
 52      puts
 53    end
 54    
 55    ######################################################################
 56
 57    # called when we want to ensure a nbew 'wrap' starts on a newline
 58    # Only needed for HtmlFormatter, because the rest do their
 59    # own line breaking
 60
 61    def break_to_newline
 62    end
 63    
 64    ######################################################################
 65
 66    def bold_print(txt)
 67      print txt
 68    end
 69
 70    ######################################################################
 71
 72    def raw_print_line(txt)
 73      puts txt
 74    end
 75
 76    ######################################################################
 77
 78    # convert HTML entities back to ASCII
 79    def conv_html(txt)
 80      txt.
 81          gsub(/&gt;/, '>').
 82          gsub(/&lt;/, '<').
 83          gsub(/&quot;/, '"').
 84          gsub(/&amp;/, '&')
 85          
 86    end
 87
 88    # convert markup into display form
 89    def conv_markup(txt)
 90      txt.
 91          gsub(%r{<tt>(.*?)</tt>}) { "+#$1+" } .
 92          gsub(%r{<code>(.*?)</code>}) { "+#$1+" } .
 93          gsub(%r{<b>(.*?)</b>}) { "*#$1*" } .
 94          gsub(%r{<em>(.*?)</em>}) { "_#$1_" }
 95    end
 96
 97    ######################################################################
 98
 99    def display_list(list)
100      case list.type
101
102      when SM::ListBase::BULLET 
103        prefixer = proc { |ignored| @indent + "*   " }
104
105      when SM::ListBase::NUMBER,
106      SM::ListBase::UPPERALPHA,
107      SM::ListBase::LOWERALPHA
108
109        start = case list.type
110                when SM::ListBase::NUMBER      then 1
111                when  SM::ListBase::UPPERALPHA then 'A'
112                when SM::ListBase::LOWERALPHA  then 'a'
113                end
114        prefixer = proc do |ignored|
115          res = @indent + "#{start}.".ljust(4)
116          start = start.succ
117          res
118        end
119        
120      when SM::ListBase::LABELED
121        prefixer = proc do |li|
122          li.label
123        end
124
125      when SM::ListBase::NOTE
126        longest = 0
127        list.contents.each do |item|
128          if item.kind_of?(SM::Flow::LI) && item.label.length > longest
129            longest = item.label.length
130          end
131        end
132
133        prefixer = proc do |li|
134          @indent + li.label.ljust(longest+1)
135        end
136
137      else
138        fail "unknown list type"
139
140      end
141
142      list.contents.each do |item|
143        if item.kind_of? SM::Flow::LI
144          prefix = prefixer.call(item)
145          display_flow_item(item, prefix)
146        else
147          display_flow_item(item)
148        end
149       end
150    end
151
152    ######################################################################
153
154    def display_flow_item(item, prefix=@indent)
155      case item
156      when SM::Flow::P, SM::Flow::LI
157        wrap(conv_html(item.body), prefix)
158        blankline
159        
160      when SM::Flow::LIST
161        display_list(item)
162
163      when SM::Flow::VERB
164        display_verbatim_flow_item(item, @indent)
165
166      when SM::Flow::H
167        display_heading(conv_html(item.text), item.level, @indent)
168
169      when SM::Flow::RULE
170        draw_line
171
172      else
173        fail "Unknown flow element: #{item.class}"
174      end
175    end
176
177    ######################################################################
178
179    def display_verbatim_flow_item(item, prefix=@indent)
180        item.body.split(/\n/).each do |line|
181          print @indent, conv_html(line), "\n"
182        end
183        blankline
184    end
185
186    ######################################################################
187
188    def display_heading(text, level, indent)
189      text = strip_attributes(text)
190      case level
191      when 1
192        ul = "=" * text.length
193        puts
194        puts text.upcase
195        puts ul
196#        puts
197        
198      when 2
199        ul = "-" * text.length
200        puts
201        puts text
202        puts ul
203#        puts
204      else
205        print indent, text, "\n"
206      end
207    end
208
209
210    def display_flow(flow)
211      flow.each do |f|
212        display_flow_item(f)
213      end
214    end
215
216    def strip_attributes(txt)
217      tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
218      text = [] 
219      attributes = 0
220      tokens.each do |tok|
221        case tok
222        when %r{^</(\w+)>$}, %r{^<(\w+)>$}
223          ;
224        else
225          text << tok
226        end
227      end
228      text.join
229    end
230
231
232  end
233  
234  
235  ######################################################################
236  # Handle text with attributes. We're a base class: there are
237  # different presentation classes (one, for example, uses overstrikes
238  # to handle bold and underlining, while another using ANSI escape
239  # sequences
240  
241  class AttributeFormatter < TextFormatter
242    
243    BOLD      = 1
244    ITALIC    = 2
245    CODE      = 4
246
247    ATTR_MAP = {
248      "b"    => BOLD,
249      "code" => CODE,
250      "em"   => ITALIC,
251      "i"    => ITALIC,
252      "tt"   => CODE
253    }
254
255    # TODO: struct?
256    class AttrChar
257      attr_reader :char
258      attr_reader :attr
259
260      def initialize(char, attr)
261        @char = char
262        @attr = attr
263      end
264    end
265
266    
267    class AttributeString
268      attr_reader :txt
269
270      def initialize
271        @txt = []
272        @optr = 0
273      end
274
275      def <<(char)
276        @txt << char
277      end
278
279      def empty?
280        @optr >= @txt.length
281      end
282
283      # accept non space, then all following spaces
284      def next_word
285        start = @optr
286        len = @txt.length
287
288        while @optr < len && @txt[@optr].char != " "
289          @optr += 1
290        end
291
292        while @optr < len && @txt[@optr].char == " "
293          @optr += 1
294        end
295
296        @txt[start...@optr]
297      end
298    end
299
300    ######################################################################
301    # overrides base class. Looks for <tt>...</tt> etc sequences
302    # and generates an array of AttrChars. This array is then used
303    # as the basis for the split
304
305    def wrap(txt,  prefix=@indent, linelen=@width)
306      return unless txt && !txt.empty?
307
308      txt = add_attributes_to(txt)
309      next_prefix = prefix.tr("^ ", " ")
310      linelen -= prefix.size
311
312      line = []
313
314      until txt.empty?
315        word = txt.next_word
316        if word.size + line.size > linelen
317          write_attribute_text(prefix, line)
318          prefix = next_prefix
319          line = []
320        end
321        line.concat(word)
322      end
323
324      write_attribute_text(prefix, line) if line.length > 0
325    end
326
327    protected
328
329    # overridden in specific formatters
330
331    def write_attribute_text(prefix, line)
332      print prefix
333      line.each do |achar|
334        print achar.char
335      end
336      puts
337    end
338
339    # again, overridden
340
341    def bold_print(txt)
342      print txt
343    end
344
345    private
346
347    def add_attributes_to(txt)
348      tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
349      text = AttributeString.new
350      attributes = 0
351      tokens.each do |tok|
352        case tok
353        when %r{^</(\w+)>$} then attributes &= ~(ATTR_MAP[$1]||0)
354        when %r{^<(\w+)>$}  then attributes  |= (ATTR_MAP[$1]||0)
355        else
356          tok.split(//).each {|ch| text << AttrChar.new(ch, attributes)}
357        end
358      end
359      text
360    end
361
362  end
363
364
365  ##################################################
366  
367  # This formatter generates overstrike-style formatting, which
368  # works with pagers such as man and less.
369
370  class OverstrikeFormatter < AttributeFormatter
371
372    BS = "\C-h"
373
374    def write_attribute_text(prefix, line)
375      print prefix
376      line.each do |achar|
377        attr = achar.attr
378        if (attr & (ITALIC+CODE)) != 0
379          print "_", BS
380        end
381        if (attr & BOLD) != 0
382          print achar.char, BS
383        end
384        print achar.char
385      end
386      puts
387    end
388
389    # draw a string in bold
390    def bold_print(text)
391      text.split(//).each do |ch|
392        print ch, BS, ch
393      end
394    end
395  end
396
397  ##################################################
398  
399  # This formatter uses ANSI escape sequences
400  # to colorize stuff
401  # works with pages such as man and less.
402
403  class AnsiFormatter < AttributeFormatter
404
405    def initialize(*args)
406      print "\033[0m"
407      super
408    end
409
410    def write_attribute_text(prefix, line)
411      print prefix
412      curr_attr = 0
413      line.each do |achar|
414        attr = achar.attr
415        if achar.attr != curr_attr
416          update_attributes(achar.attr)
417          curr_attr = achar.attr
418        end
419        print achar.char
420      end
421      update_attributes(0) unless curr_attr.zero?
422      puts
423    end
424
425
426    def bold_print(txt)
427      print "\033[1m#{txt}\033[m"
428    end
429
430    HEADINGS = {
431      1 => [ "\033[1;32m", "\033[m" ] ,
432      2 => ["\033[4;32m", "\033[m" ],
433      3 => ["\033[32m", "\033[m" ]
434    }
435
436    def display_heading(text, level, indent)
437      level = 3 if level > 3
438      heading = HEADINGS[level]
439      print indent
440      print heading[0]
441      print strip_attributes(text)
442      puts heading[1]
443    end
444    
445    private
446
447    ATTR_MAP = {
448      BOLD   => "1",
449      ITALIC => "33",
450      CODE   => "36"
451    }
452
453    def update_attributes(attr)
454      str = "\033["
455      for quality in [ BOLD, ITALIC, CODE]
456        unless (attr & quality).zero?
457          str << ATTR_MAP[quality]
458        end
459      end
460      print str, "m"
461    end
462  end
463
464  ##################################################
465  
466  # This formatter uses HTML.
467
468  class HtmlFormatter < AttributeFormatter
469
470    def initialize(*args)
471      super
472    end
473
474    def write_attribute_text(prefix, line)
475      curr_attr = 0
476      line.each do |achar|
477        attr = achar.attr
478        if achar.attr != curr_attr
479          update_attributes(curr_attr, achar.attr)
480          curr_attr = achar.attr
481        end
482        print(escape(achar.char))
483      end
484      update_attributes(curr_attr, 0) unless curr_attr.zero?
485    end
486
487    def draw_line(label=nil)
488      if label != nil
489        bold_print(label)
490      end
491      puts("<hr>")
492    end
493
494    def bold_print(txt)
495      tag("b") { txt }
496    end
497
498    def blankline()
499      puts("<p>")
500    end
501
502    def break_to_newline
503      puts("<br>")
504    end
505
506    def display_heading(text, level, indent)
507      level = 4 if level > 4
508      tag("h#{level}") { text }
509      puts
510    end
511    
512    ######################################################################
513
514    def display_list(list)
515
516      case list.type
517      when SM::ListBase::BULLET 
518        list_type = "ul"
519        prefixer = proc { |ignored| "<li>" }
520
521      when SM::ListBase::NUMBER,
522      SM::ListBase::UPPERALPHA,
523      SM::ListBase::LOWERALPHA
524        list_type = "ol"
525        prefixer = proc { |ignored| "<li>" }
526        
527      when SM::ListBase::LABELED
528        list_type = "dl"
529        prefixer = proc do |li|
530          "<dt><b>" + escape(li.label) + "</b><dd>"
531        end
532
533      when SM::ListBase::NOTE
534        list_type = "table"
535        prefixer = proc do |li|
536          %{<tr valign="top"><td>#{li.label.gsub(/ /, '&nbsp;')}</td><td>}
537        end
538      else
539        fail "unknown list type"
540      end
541
542      print "<#{list_type}>"
543      list.contents.each do |item|
544        if item.kind_of? SM::Flow::LI
545          prefix = prefixer.call(item)
546          print prefix
547          display_flow_item(item, prefix)
548        else
549          display_flow_item(item)
550        end
551      end
552      print "</#{list_type}>"
553    end
554
555    def display_verbatim_flow_item(item, prefix=@indent)
556        print("<pre>")
557        puts item.body
558        puts("</pre>")
559    end
560
561    private
562
563    ATTR_MAP = {
564      BOLD   => "b>",
565      ITALIC => "i>",
566      CODE   => "tt>"
567    }
568
569    def update_attributes(current, wanted)
570      str = ""
571      # first turn off unwanted ones
572      off = current & ~wanted
573      for quality in [ BOLD, ITALIC, CODE]
574        if (off & quality) > 0
575          str << "</" + ATTR_MAP[quality]
576        end
577      end
578
579      # now turn on wanted
580      for quality in [ BOLD, ITALIC, CODE]
581        unless (wanted & quality).zero?
582          str << "<" << ATTR_MAP[quality]
583        end
584      end
585      print str
586    end
587
588    def tag(code)
589        print("<#{code}>")
590        print(yield)
591        print("</#{code}>")
592    end
593
594    def escape(str)
595      str.
596          gsub(/&/n, '&amp;').
597          gsub(/\"/n, '&quot;').
598          gsub(/>/n, '&gt;').
599          gsub(/</n, '&lt;')
600    end
601
602  end
603
604  ##################################################
605  
606  # This formatter reduces extra lines for a simpler output.
607  # It improves way output looks for tools like IRC bots.
608
609  class SimpleFormatter < TextFormatter
610
611    ######################################################################
612
613    # No extra blank lines
614
615    def blankline
616    end
617
618    ######################################################################
619
620    # Display labels only, no lines
621
622    def draw_line(label=nil)
623      unless label.nil? then
624        bold_print(label) 
625        puts
626      end
627    end
628
629    ######################################################################
630
631    # Place heading level indicators inline with heading.
632
633    def display_heading(text, level, indent)
634      text = strip_attributes(text)
635      case level
636      when 1
637        puts "= " + text.upcase
638      when 2
639        puts "-- " + text
640      else
641        print indent, text, "\n"
642      end
643    end
644
645  end
646
647
648  # Finally, fill in the list of known formatters
649
650  class TextFormatter
651
652    FORMATTERS = {
653      "ansi"   => AnsiFormatter,
654      "bs"     => OverstrikeFormatter,
655      "html"   => HtmlFormatter,
656      "plain"  => TextFormatter,
657      "simple" => SimpleFormatter,
658    }
659      
660    def TextFormatter.list
661      FORMATTERS.keys.sort.join(", ")
662    end
663
664    def TextFormatter.for(name)
665      FORMATTERS[name.downcase]
666    end
667
668  end
669
670end
671
672