2003-12-16 00:44:25 -05:00
|
|
|
module RI
|
2003-12-21 02:28:54 -05:00
|
|
|
class TextFormatter
|
|
|
|
|
2003-12-23 23:24:29 -05:00
|
|
|
def TextFormatter.list
|
2004-01-11 12:34:05 -05:00
|
|
|
"plain, html, bs, ansi"
|
2003-12-23 23:24:29 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def TextFormatter.for(name)
|
|
|
|
case name
|
|
|
|
when /plain/i then TextFormatter
|
2004-01-11 12:34:05 -05:00
|
|
|
when /html/i then HtmlFormatter
|
|
|
|
when /bs/i then OverstrikeFormatter
|
|
|
|
when /ansi/i then AnsiFormatter
|
2003-12-23 23:24:29 -05:00
|
|
|
else nil
|
|
|
|
end
|
2003-12-21 02:28:54 -05:00
|
|
|
end
|
2003-12-16 00:44:25 -05:00
|
|
|
|
|
|
|
attr_reader :indent
|
|
|
|
|
2003-12-21 02:28:54 -05:00
|
|
|
def initialize(options, indent)
|
|
|
|
@options = options
|
|
|
|
@width = options.width
|
|
|
|
@indent = indent
|
2003-12-16 00:44:25 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
|
|
|
def draw_line(label=nil)
|
|
|
|
len = @width
|
|
|
|
len -= (label.size+1) if label
|
|
|
|
print "-"*len
|
2003-12-23 23:24:29 -05:00
|
|
|
if label
|
|
|
|
print(" ")
|
|
|
|
bold_print(label)
|
|
|
|
end
|
2003-12-16 00:44:25 -05:00
|
|
|
puts
|
|
|
|
end
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
|
|
|
def wrap(txt, prefix=@indent, linelen=@width)
|
|
|
|
return unless txt && !txt.empty?
|
2003-12-21 02:28:54 -05:00
|
|
|
work = conv_markup(txt)
|
2003-12-16 00:44:25 -05:00
|
|
|
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?
|
|
|
|
puts (prefix + res.join("\n" + next_prefix))
|
|
|
|
end
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
|
|
|
def blankline
|
|
|
|
puts
|
|
|
|
end
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
2004-01-11 22:11:25 -05:00
|
|
|
# called when we want to ensure a nbew 'wrap' starts on a newline
|
|
|
|
# Only needed for HtmlFormatter, because the rest do their
|
|
|
|
# own line breaking
|
|
|
|
|
|
|
|
def break_to_newline
|
|
|
|
end
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
2003-12-23 23:24:29 -05:00
|
|
|
def bold_print(txt)
|
|
|
|
print txt
|
|
|
|
end
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
2003-12-16 00:44:25 -05:00
|
|
|
# convert HTML entities back to ASCII
|
|
|
|
def conv_html(txt)
|
|
|
|
txt.
|
|
|
|
gsub(/>/, '>').
|
|
|
|
gsub(/</, '<').
|
|
|
|
gsub(/"/, '"').
|
|
|
|
gsub(/&/, '&')
|
|
|
|
|
|
|
|
end
|
|
|
|
|
2003-12-21 02:28:54 -05:00
|
|
|
# convert markup into display form
|
|
|
|
def conv_markup(txt)
|
|
|
|
txt.
|
|
|
|
gsub(%r{<tt>(.*?)</tt>}) { "+#$1+" } .
|
|
|
|
gsub(%r{<code>(.*?)</code>}) { "+#$1+" } .
|
|
|
|
gsub(%r{<b>(.*?)</b>}) { "*#$1*" } .
|
|
|
|
gsub(%r{<em>(.*?)</em>}) { "_#$1_" }
|
|
|
|
end
|
|
|
|
|
2003-12-16 00:44:25 -05:00
|
|
|
######################################################################
|
|
|
|
|
|
|
|
def display_list(list)
|
|
|
|
case list.type
|
|
|
|
|
|
|
|
when SM::ListBase::BULLET
|
|
|
|
prefixer = proc { |ignored| @indent + "* " }
|
|
|
|
|
|
|
|
when SM::ListBase::NUMBER,
|
|
|
|
SM::ListBase::UPPERALPHA,
|
|
|
|
SM::ListBase::LOWERALPHA
|
|
|
|
|
|
|
|
start = case list.type
|
|
|
|
when SM::ListBase::NUMBER then 1
|
|
|
|
when SM::ListBase::UPPERALPHA then 'A'
|
|
|
|
when SM::ListBase::LOWERALPHA then 'a'
|
|
|
|
end
|
|
|
|
prefixer = proc do |ignored|
|
|
|
|
res = @indent + "#{start}.".ljust(4)
|
|
|
|
start = start.succ
|
|
|
|
res
|
|
|
|
end
|
|
|
|
|
|
|
|
when SM::ListBase::LABELED
|
|
|
|
prefixer = proc do |li|
|
|
|
|
li.label
|
|
|
|
end
|
|
|
|
|
|
|
|
when SM::ListBase::NOTE
|
|
|
|
longest = 0
|
|
|
|
list.contents.each do |item|
|
|
|
|
if item.kind_of?(SM::Flow::LI) && item.label.length > longest
|
|
|
|
longest = item.label.length
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
prefixer = proc do |li|
|
|
|
|
@indent + li.label.ljust(longest+1)
|
|
|
|
end
|
|
|
|
|
|
|
|
else
|
|
|
|
fail "unknown list type"
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
list.contents.each do |item|
|
|
|
|
if item.kind_of? SM::Flow::LI
|
|
|
|
prefix = prefixer.call(item)
|
|
|
|
display_flow_item(item, prefix)
|
|
|
|
else
|
|
|
|
display_flow_item(item)
|
|
|
|
end
|
2003-12-23 23:24:29 -05:00
|
|
|
end
|
2003-12-16 00:44:25 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
|
|
|
def display_flow_item(item, prefix=@indent)
|
|
|
|
case item
|
|
|
|
when SM::Flow::P, SM::Flow::LI
|
|
|
|
wrap(conv_html(item.body), prefix)
|
|
|
|
blankline
|
|
|
|
|
|
|
|
when SM::Flow::LIST
|
|
|
|
display_list(item)
|
|
|
|
|
|
|
|
when SM::Flow::VERB
|
2004-01-11 12:34:05 -05:00
|
|
|
display_verbatim_flow_item(item, @indent)
|
2003-12-16 00:44:25 -05:00
|
|
|
|
|
|
|
when SM::Flow::H
|
2003-12-23 23:24:29 -05:00
|
|
|
display_heading(conv_html(item.text.join), item.level, @indent)
|
2003-12-27 11:49:22 -05:00
|
|
|
|
|
|
|
when SM::Flow::RULE
|
|
|
|
draw_line
|
|
|
|
|
2003-12-16 00:44:25 -05:00
|
|
|
else
|
2003-12-28 01:33:07 -05:00
|
|
|
fail "Unknown flow element: #{item.class}"
|
2003-12-16 00:44:25 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
2004-01-11 12:34:05 -05:00
|
|
|
def display_verbatim_flow_item(item, prefix=@indent)
|
|
|
|
item.body.split(/\n/).each do |line|
|
|
|
|
print @indent, conv_html(line), "\n"
|
|
|
|
end
|
|
|
|
blankline
|
|
|
|
end
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
2003-12-23 23:24:29 -05:00
|
|
|
def display_heading(text, level, indent)
|
|
|
|
case level
|
|
|
|
when 1
|
|
|
|
ul = "=" * text.length
|
|
|
|
puts
|
|
|
|
puts text.upcase
|
|
|
|
puts ul
|
|
|
|
# puts
|
|
|
|
|
|
|
|
when 2
|
|
|
|
ul = "-" * text.length
|
|
|
|
puts
|
|
|
|
puts text
|
|
|
|
puts ul
|
|
|
|
# puts
|
|
|
|
else
|
|
|
|
print indent, text, "\n"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2003-12-16 00:44:25 -05:00
|
|
|
def display_flow(flow)
|
|
|
|
flow.each do |f|
|
|
|
|
display_flow_item(f)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2003-12-23 23:24:29 -05:00
|
|
|
|
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
######################################################################
|
2003-12-23 23:24:29 -05:00
|
|
|
# Handle text with attributes. We're a base class: there are
|
|
|
|
# different presentation classes (one, for example, uses overstrikes
|
|
|
|
# to handle bold and underlinig, while another using ANSI escape
|
|
|
|
# sequences
|
|
|
|
|
|
|
|
class AttributeFormatter < TextFormatter
|
|
|
|
|
|
|
|
BOLD = 1
|
|
|
|
ITALIC = 2
|
|
|
|
CODE = 4
|
|
|
|
|
|
|
|
ATTR_MAP = {
|
|
|
|
"b" => BOLD,
|
|
|
|
"code" => CODE,
|
|
|
|
"em" => ITALIC,
|
|
|
|
"i" => ITALIC,
|
|
|
|
"tt" => CODE
|
|
|
|
}
|
|
|
|
|
|
|
|
# TODO: struct?
|
|
|
|
class AttrChar
|
|
|
|
attr_reader :char
|
|
|
|
attr_reader :attr
|
|
|
|
|
|
|
|
def initialize(char, attr)
|
|
|
|
@char = char
|
|
|
|
@attr = attr
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
class AttributeString
|
|
|
|
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)
|
2004-01-02 01:01:12 -05:00
|
|
|
next_prefix = prefix.tr("^ ", " ")
|
|
|
|
linelen -= prefix.size
|
2003-12-23 23:24:29 -05:00
|
|
|
|
|
|
|
line = []
|
|
|
|
|
|
|
|
until txt.empty?
|
|
|
|
word = txt.next_word
|
2004-01-02 01:01:12 -05:00
|
|
|
if word.size + line.size > linelen
|
|
|
|
write_attribute_text(prefix, line)
|
|
|
|
prefix = next_prefix
|
2003-12-23 23:24:29 -05:00
|
|
|
line = []
|
|
|
|
end
|
|
|
|
line.concat(word)
|
|
|
|
end
|
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
write_attribute_text(prefix, line) if line.length > 0
|
2003-12-23 23:24:29 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
|
|
|
# overridden in specific formatters
|
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
def write_attribute_text(prefix, line)
|
|
|
|
print prefix
|
2003-12-23 23:24:29 -05:00
|
|
|
line.each do |achar|
|
|
|
|
print achar.char
|
|
|
|
end
|
|
|
|
puts
|
|
|
|
end
|
|
|
|
|
|
|
|
# again, overridden
|
|
|
|
|
|
|
|
def bold_print(txt)
|
|
|
|
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 pages such as man and less.
|
|
|
|
|
|
|
|
class OverstrikeFormatter < AttributeFormatter
|
|
|
|
|
|
|
|
BS = "\C-h"
|
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
def write_attribute_text(prefix, line)
|
|
|
|
print prefix
|
2003-12-23 23:24:29 -05:00
|
|
|
line.each do |achar|
|
|
|
|
attr = achar.attr
|
|
|
|
if (attr & (ITALIC+CODE)) != 0
|
|
|
|
print "_", BS
|
|
|
|
end
|
|
|
|
if (attr & BOLD) != 0
|
|
|
|
print achar.char, BS
|
|
|
|
end
|
|
|
|
print achar.char
|
|
|
|
end
|
|
|
|
puts
|
|
|
|
end
|
|
|
|
|
|
|
|
# draw a string in bold
|
|
|
|
def bold_print(text)
|
|
|
|
text.split(//).each do |ch|
|
|
|
|
print ch, BS, ch
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
##################################################
|
|
|
|
|
|
|
|
# This formatter uses ANSI escape sequences
|
|
|
|
# to colorize stuff
|
|
|
|
# works with pages such as man and less.
|
|
|
|
|
|
|
|
class AnsiFormatter < AttributeFormatter
|
|
|
|
|
|
|
|
def initialize(*args)
|
|
|
|
print "\033[0m"
|
|
|
|
super
|
|
|
|
end
|
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
def write_attribute_text(prefix, line)
|
|
|
|
print prefix
|
2003-12-23 23:24:29 -05:00
|
|
|
curr_attr = 0
|
|
|
|
line.each do |achar|
|
|
|
|
attr = achar.attr
|
|
|
|
if achar.attr != curr_attr
|
|
|
|
update_attributes(achar.attr)
|
|
|
|
curr_attr = achar.attr
|
|
|
|
end
|
|
|
|
print achar.char
|
|
|
|
end
|
|
|
|
update_attributes(0) unless curr_attr.zero?
|
|
|
|
puts
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def bold_print(txt)
|
|
|
|
print "\033[1m#{txt}\033[m"
|
|
|
|
end
|
|
|
|
|
|
|
|
HEADINGS = {
|
|
|
|
1 => "\033[1;32m%s\033[m",
|
|
|
|
2 => "\033[4;32m%s\033[m",
|
|
|
|
3 => "\033[32m%s\033[m"
|
|
|
|
}
|
|
|
|
|
|
|
|
def display_heading(text, level, indent)
|
|
|
|
level = 3 if level > 3
|
|
|
|
print indent
|
|
|
|
printf(HEADINGS[level], text)
|
|
|
|
puts
|
|
|
|
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
|
|
|
|
print str, "m"
|
|
|
|
end
|
|
|
|
end
|
2003-12-21 02:28:54 -05:00
|
|
|
|
2004-01-11 12:34:05 -05:00
|
|
|
##################################################
|
|
|
|
|
|
|
|
# This formatter uses HTML.
|
|
|
|
|
|
|
|
class HtmlFormatter < AttributeFormatter
|
|
|
|
|
|
|
|
def initialize(*args)
|
|
|
|
super
|
|
|
|
end
|
|
|
|
|
|
|
|
def write_attribute_text(prefix, line)
|
|
|
|
curr_attr = 0
|
|
|
|
line.each do |achar|
|
|
|
|
attr = achar.attr
|
|
|
|
if achar.attr != curr_attr
|
|
|
|
update_attributes(curr_attr, achar.attr)
|
|
|
|
curr_attr = achar.attr
|
|
|
|
end
|
|
|
|
print(escape(achar.char))
|
|
|
|
end
|
|
|
|
update_attributes(curr_attr, 0) unless curr_attr.zero?
|
|
|
|
end
|
|
|
|
|
|
|
|
def draw_line(label=nil)
|
|
|
|
if label != nil
|
|
|
|
bold_print(label)
|
|
|
|
end
|
2004-01-11 22:11:25 -05:00
|
|
|
puts("<hr>")
|
2004-01-11 12:34:05 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def bold_print(txt)
|
|
|
|
tag("b") { txt }
|
|
|
|
end
|
|
|
|
|
|
|
|
def blankline()
|
|
|
|
puts("<p>")
|
|
|
|
end
|
|
|
|
|
2004-01-11 22:11:25 -05:00
|
|
|
def break_to_newline
|
|
|
|
puts("<br>")
|
|
|
|
end
|
|
|
|
|
2004-01-11 12:34:05 -05:00
|
|
|
def display_heading(text, level, indent)
|
|
|
|
level = 4 if level > 4
|
|
|
|
tag("h#{level}") { text }
|
|
|
|
puts
|
|
|
|
end
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
|
|
|
def display_list(list)
|
|
|
|
|
|
|
|
case list.type
|
|
|
|
when SM::ListBase::BULLET
|
|
|
|
list_type = "ul"
|
|
|
|
prefixer = proc { |ignored| "<li>" }
|
|
|
|
|
|
|
|
when SM::ListBase::NUMBER,
|
|
|
|
SM::ListBase::UPPERALPHA,
|
|
|
|
SM::ListBase::LOWERALPHA
|
|
|
|
list_type = "ol"
|
|
|
|
prefixer = proc { |ignored| "<li>" }
|
|
|
|
|
|
|
|
when SM::ListBase::LABELED
|
|
|
|
list_type = "dl"
|
|
|
|
prefixer = proc do |li|
|
|
|
|
"<dt><b>" + escape(li.label) + "</b><dd>"
|
|
|
|
end
|
|
|
|
|
|
|
|
when SM::ListBase::NOTE
|
|
|
|
list_type = "table"
|
|
|
|
prefixer = proc do |li|
|
|
|
|
%{<tr valign="top"><td>#{li.label.gsub(/ /, ' ')}</td><td>}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
fail "unknown list type"
|
|
|
|
end
|
|
|
|
|
|
|
|
print "<#{list_type}>"
|
|
|
|
list.contents.each do |item|
|
|
|
|
if item.kind_of? SM::Flow::LI
|
|
|
|
prefix = prefixer.call(item)
|
|
|
|
print prefix
|
|
|
|
display_flow_item(item, prefix)
|
|
|
|
else
|
|
|
|
display_flow_item(item)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
print "</#{list_type}>"
|
|
|
|
end
|
|
|
|
|
|
|
|
def display_verbatim_flow_item(item, prefix=@indent)
|
|
|
|
print("<pre>")
|
|
|
|
item.body.split(/\n/).each do |line|
|
|
|
|
puts conv_html(line)
|
|
|
|
end
|
|
|
|
puts("</pre>")
|
|
|
|
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
|
|
|
|
print str
|
|
|
|
end
|
|
|
|
|
|
|
|
def tag(code)
|
|
|
|
print("<#{code}>")
|
|
|
|
print(yield)
|
|
|
|
print("</#{code}>")
|
|
|
|
end
|
|
|
|
|
|
|
|
def escape(str)
|
|
|
|
str.
|
|
|
|
gsub(/&/n, '&').
|
|
|
|
gsub(/\"/n, '"').
|
|
|
|
gsub(/>/n, '>').
|
|
|
|
gsub(/</n, '<')
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
2003-12-16 00:44:25 -05:00
|
|
|
end
|
2003-12-21 02:28:54 -05:00
|
|
|
|
|
|
|
|