~ubuntu-branches/ubuntu/hardy/ruby1.8/hardy-updates

« back to all changes in this revision

Viewing changes to lib/rdoc/ri/ri_formatter.rb

  • Committer: Bazaar Package Importer
  • Author(s): akira yamada
  • Date: 2007-03-13 22:11:58 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20070313221158-h3oql37brlaf2go2
Tags: 1.8.6-1
* new upstream version, 1.8.6.
* libruby1.8 conflicts with libopenssl-ruby1.8 (< 1.8.6) (closes: #410018)
* changed packaging style to cdbs from dbs.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
module 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
 
 
670
end
 
671
 
 
672