관리-도구
편집 파일: ri_formatter.rb
module RI class TextFormatter attr_reader :indent def initialize(options, indent) @options = options @width = options.width @indent = indent end ###################################################################### def draw_line(label=nil) len = @width len -= (label.size+1) if label print "-"*len if label print(" ") bold_print(label) end puts end ###################################################################### def wrap(txt, prefix=@indent, linelen=@width) return unless txt && !txt.empty? work = conv_markup(txt) 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 ###################################################################### # 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 ###################################################################### def bold_print(txt) print txt end ###################################################################### def raw_print_line(txt) puts txt end ###################################################################### # convert HTML entities back to ASCII def conv_html(txt) txt. gsub(/>/, '>'). gsub(/</, '<'). gsub(/"/, '"'). gsub(/&/, '&') end # 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 ###################################################################### 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 end 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 display_verbatim_flow_item(item, @indent) when SM::Flow::H display_heading(conv_html(item.text), item.level, @indent) when SM::Flow::RULE draw_line else fail "Unknown flow element: #{item.class}" end end ###################################################################### def display_verbatim_flow_item(item, prefix=@indent) item.body.split(/\n/).each do |line| print @indent, conv_html(line), "\n" end blankline end ###################################################################### def display_heading(text, level, indent) text = strip_attributes(text) 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 def display_flow(flow) flow.each do |f| display_flow_item(f) end end def strip_attributes(txt) tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)}) text = [] attributes = 0 tokens.each do |tok| case tok when %r{^</(\w+)>$}, %r{^<(\w+)>$} ; else text << tok end end text.join end end ###################################################################### # Handle text with attributes. We're a base class: there are # different presentation classes (one, for example, uses overstrikes # to handle bold and underlining, 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 attr_reader :txt 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) next_prefix = prefix.tr("^ ", " ") linelen -= prefix.size line = [] until txt.empty? word = txt.next_word if word.size + line.size > linelen write_attribute_text(prefix, line) prefix = next_prefix line = [] end line.concat(word) end write_attribute_text(prefix, line) if line.length > 0 end protected # overridden in specific formatters def write_attribute_text(prefix, line) print prefix 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 pagers such as man and less. class OverstrikeFormatter < AttributeFormatter BS = "\C-h" def write_attribute_text(prefix, line) print prefix 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 def write_attribute_text(prefix, line) print prefix 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", "\033[m" ] , 2 => ["\033[4;32m", "\033[m" ], 3 => ["\033[32m", "\033[m" ] } def display_heading(text, level, indent) level = 3 if level > 3 heading = HEADINGS[level] print indent print heading[0] print strip_attributes(text) puts heading[1] 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 ################################################## # 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 puts("<hr>") end def bold_print(txt) tag("b") { txt } end def blankline() puts("<p>") end def break_to_newline puts("<br>") end 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>") puts item.body 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 ################################################## # This formatter reduces extra lines for a simpler output. # It improves way output looks for tools like IRC bots. class SimpleFormatter < TextFormatter ###################################################################### # No extra blank lines def blankline end ###################################################################### # Display labels only, no lines def draw_line(label=nil) unless label.nil? then bold_print(label) puts end end ###################################################################### # Place heading level indicators inline with heading. def display_heading(text, level, indent) text = strip_attributes(text) case level when 1 puts "= " + text.upcase when 2 puts "-- " + text else print indent, text, "\n" end end end # Finally, fill in the list of known formatters class TextFormatter FORMATTERS = { "ansi" => AnsiFormatter, "bs" => OverstrikeFormatter, "html" => HtmlFormatter, "plain" => TextFormatter, "simple" => SimpleFormatter, } def TextFormatter.list FORMATTERS.keys.sort.join(", ") end def TextFormatter.for(name) FORMATTERS[name.downcase] end end end