6
def initialize(options, indent)
13
######################################################################
15
def draw_line(label=nil)
17
len -= (label.size+1) if label
26
######################################################################
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("^ ", " ")
37
while work.length > textLen
40
work.slice!(0, $&.length)
42
res << work.slice!(0, textLen)
45
res << work if work.length.nonzero?
46
puts(prefix + res.join("\n" + next_prefix))
49
######################################################################
55
######################################################################
57
# called when we want to ensure a nbew 'wrap' starts on a newline
58
# Only needed for HtmlFormatter, because the rest do their
64
######################################################################
70
######################################################################
72
def raw_print_line(txt)
76
######################################################################
78
# convert HTML entities back to ASCII
88
# convert markup into display form
91
gsub(%r{<tt>(.*?)</tt>}) { "+#$1+" } .
92
gsub(%r{<code>(.*?)</code>}) { "+#$1+" } .
93
gsub(%r{<b>(.*?)</b>}) { "*#$1*" } .
94
gsub(%r{<em>(.*?)</em>}) { "_#$1_" }
97
######################################################################
99
def display_list(list)
102
when SM::ListBase::BULLET
103
prefixer = proc { |ignored| @indent + "* " }
105
when SM::ListBase::NUMBER,
106
SM::ListBase::UPPERALPHA,
107
SM::ListBase::LOWERALPHA
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'
114
prefixer = proc do |ignored|
115
res = @indent + "#{start}.".ljust(4)
120
when SM::ListBase::LABELED
121
prefixer = proc do |li|
125
when SM::ListBase::NOTE
127
list.contents.each do |item|
128
if item.kind_of?(SM::Flow::LI) && item.label.length > longest
129
longest = item.label.length
133
prefixer = proc do |li|
134
@indent + li.label.ljust(longest+1)
138
fail "unknown list type"
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)
147
display_flow_item(item)
152
######################################################################
154
def display_flow_item(item, prefix=@indent)
156
when SM::Flow::P, SM::Flow::LI
157
wrap(conv_html(item.body), prefix)
164
display_verbatim_flow_item(item, @indent)
167
display_heading(conv_html(item.text), item.level, @indent)
173
fail "Unknown flow element: #{item.class}"
177
######################################################################
179
def display_verbatim_flow_item(item, prefix=@indent)
180
item.body.split(/\n/).each do |line|
181
print @indent, conv_html(line), "\n"
186
######################################################################
188
def display_heading(text, level, indent)
189
text = strip_attributes(text)
192
ul = "=" * text.length
199
ul = "-" * text.length
205
print indent, text, "\n"
210
def display_flow(flow)
216
def strip_attributes(txt)
217
tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
222
when %r{^</(\w+)>$}, %r{^<(\w+)>$}
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
241
class AttributeFormatter < TextFormatter
260
def initialize(char, attr)
267
class AttributeString
283
# accept non space, then all following spaces
288
while @optr < len && @txt[@optr].char != " "
292
while @optr < len && @txt[@optr].char == " "
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
305
def wrap(txt, prefix=@indent, linelen=@width)
306
return unless txt && !txt.empty?
308
txt = add_attributes_to(txt)
309
next_prefix = prefix.tr("^ ", " ")
310
linelen -= prefix.size
316
if word.size + line.size > linelen
317
write_attribute_text(prefix, line)
324
write_attribute_text(prefix, line) if line.length > 0
329
# overridden in specific formatters
331
def write_attribute_text(prefix, line)
347
def add_attributes_to(txt)
348
tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
349
text = AttributeString.new
353
when %r{^</(\w+)>$} then attributes &= ~(ATTR_MAP[$1]||0)
354
when %r{^<(\w+)>$} then attributes |= (ATTR_MAP[$1]||0)
356
tok.split(//).each {|ch| text << AttrChar.new(ch, attributes)}
365
##################################################
367
# This formatter generates overstrike-style formatting, which
368
# works with pagers such as man and less.
370
class OverstrikeFormatter < AttributeFormatter
374
def write_attribute_text(prefix, line)
378
if (attr & (ITALIC+CODE)) != 0
381
if (attr & BOLD) != 0
389
# draw a string in bold
391
text.split(//).each do |ch|
397
##################################################
399
# This formatter uses ANSI escape sequences
401
# works with pages such as man and less.
403
class AnsiFormatter < AttributeFormatter
405
def initialize(*args)
410
def write_attribute_text(prefix, line)
415
if achar.attr != curr_attr
416
update_attributes(achar.attr)
417
curr_attr = achar.attr
421
update_attributes(0) unless curr_attr.zero?
427
print "\033[1m#{txt}\033[m"
431
1 => [ "\033[1;32m", "\033[m" ] ,
432
2 => ["\033[4;32m", "\033[m" ],
433
3 => ["\033[32m", "\033[m" ]
436
def display_heading(text, level, indent)
437
level = 3 if level > 3
438
heading = HEADINGS[level]
441
print strip_attributes(text)
453
def update_attributes(attr)
455
for quality in [ BOLD, ITALIC, CODE]
456
unless (attr & quality).zero?
457
str << ATTR_MAP[quality]
464
##################################################
466
# This formatter uses HTML.
468
class HtmlFormatter < AttributeFormatter
470
def initialize(*args)
474
def write_attribute_text(prefix, line)
478
if achar.attr != curr_attr
479
update_attributes(curr_attr, achar.attr)
480
curr_attr = achar.attr
482
print(escape(achar.char))
484
update_attributes(curr_attr, 0) unless curr_attr.zero?
487
def draw_line(label=nil)
506
def display_heading(text, level, indent)
507
level = 4 if level > 4
508
tag("h#{level}") { text }
512
######################################################################
514
def display_list(list)
517
when SM::ListBase::BULLET
519
prefixer = proc { |ignored| "<li>" }
521
when SM::ListBase::NUMBER,
522
SM::ListBase::UPPERALPHA,
523
SM::ListBase::LOWERALPHA
525
prefixer = proc { |ignored| "<li>" }
527
when SM::ListBase::LABELED
529
prefixer = proc do |li|
530
"<dt><b>" + escape(li.label) + "</b><dd>"
533
when SM::ListBase::NOTE
535
prefixer = proc do |li|
536
%{<tr valign="top"><td>#{li.label.gsub(/ /, ' ')}</td><td>}
539
fail "unknown list type"
542
print "<#{list_type}>"
543
list.contents.each do |item|
544
if item.kind_of? SM::Flow::LI
545
prefix = prefixer.call(item)
547
display_flow_item(item, prefix)
549
display_flow_item(item)
552
print "</#{list_type}>"
555
def display_verbatim_flow_item(item, prefix=@indent)
569
def update_attributes(current, wanted)
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]
580
for quality in [ BOLD, ITALIC, CODE]
581
unless (wanted & quality).zero?
582
str << "<" << ATTR_MAP[quality]
597
gsub(/\"/n, '"').
604
##################################################
606
# This formatter reduces extra lines for a simpler output.
607
# It improves way output looks for tools like IRC bots.
609
class SimpleFormatter < TextFormatter
611
######################################################################
613
# No extra blank lines
618
######################################################################
620
# Display labels only, no lines
622
def draw_line(label=nil)
623
unless label.nil? then
629
######################################################################
631
# Place heading level indicators inline with heading.
633
def display_heading(text, level, indent)
634
text = strip_attributes(text)
637
puts "= " + text.upcase
641
print indent, text, "\n"
648
# Finally, fill in the list of known formatters
653
"ansi" => AnsiFormatter,
654
"bs" => OverstrikeFormatter,
655
"html" => HtmlFormatter,
656
"plain" => TextFormatter,
657
"simple" => SimpleFormatter,
660
def TextFormatter.list
661
FORMATTERS.keys.sort.join(", ")
664
def TextFormatter.for(name)
665
FORMATTERS[name.downcase]