mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
287a34ae0d
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@22784 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
337 lines
7.2 KiB
Ruby
337 lines
7.2 KiB
Ruby
require 'rdoc/markup'
|
|
require 'rdoc/markup/lines'
|
|
|
|
class RDoc::Markup
|
|
|
|
##
|
|
# A Fragment is a chunk of text, subclassed as a paragraph, a list
|
|
# entry, or verbatim text.
|
|
|
|
class Fragment
|
|
attr_reader :level, :param, :txt
|
|
attr_accessor :type
|
|
|
|
##
|
|
# This is a simple factory system that lets us associate fragement
|
|
# types (a string) with a subclass of fragment
|
|
|
|
TYPE_MAP = {}
|
|
|
|
def self.type_name(name)
|
|
TYPE_MAP[name] = self
|
|
end
|
|
|
|
def self.for(line)
|
|
klass = TYPE_MAP[line.type] ||
|
|
raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'")
|
|
return klass.new(line.level, line.param, line.flag, line.text)
|
|
end
|
|
|
|
def initialize(level, param, type, txt)
|
|
@level = level
|
|
@param = param
|
|
@type = type
|
|
@txt = ""
|
|
add_text(txt) if txt
|
|
end
|
|
|
|
def add_text(txt)
|
|
@txt << " " if @txt.length > 0
|
|
@txt << txt.tr_s("\n ", " ").strip
|
|
end
|
|
|
|
def to_s
|
|
"L#@level: #{self.class.name.split('::')[-1]}\n#@txt"
|
|
end
|
|
|
|
end
|
|
|
|
##
|
|
# A paragraph is a fragment which gets wrapped to fit. We remove all
|
|
# newlines when we're created, and have them put back on output.
|
|
|
|
class Paragraph < Fragment
|
|
type_name :PARAGRAPH
|
|
end
|
|
|
|
class BlankLine < Paragraph
|
|
type_name :BLANK
|
|
end
|
|
|
|
class Heading < Paragraph
|
|
type_name :HEADING
|
|
|
|
def head_level
|
|
@param.to_i
|
|
end
|
|
end
|
|
|
|
##
|
|
# A List is a fragment with some kind of label
|
|
|
|
class ListBase < Paragraph
|
|
LIST_TYPES = [
|
|
:BULLET,
|
|
:NUMBER,
|
|
:UPPERALPHA,
|
|
:LOWERALPHA,
|
|
:LABELED,
|
|
:NOTE,
|
|
]
|
|
end
|
|
|
|
class ListItem < ListBase
|
|
type_name :LIST
|
|
|
|
def to_s
|
|
text = if [:NOTE, :LABELED].include? type then
|
|
"#{@param}: #{@txt}"
|
|
else
|
|
@txt
|
|
end
|
|
|
|
"L#@level: #{type} #{self.class.name.split('::')[-1]}\n#{text}"
|
|
end
|
|
|
|
end
|
|
|
|
class ListStart < ListBase
|
|
def initialize(level, param, type)
|
|
super(level, param, type, nil)
|
|
end
|
|
end
|
|
|
|
class ListEnd < ListBase
|
|
def initialize(level, type)
|
|
super(level, "", type, nil)
|
|
end
|
|
end
|
|
|
|
##
|
|
# Verbatim code contains lines that don't get wrapped.
|
|
|
|
class Verbatim < Fragment
|
|
type_name :VERBATIM
|
|
|
|
def add_text(txt)
|
|
@txt << txt.chomp << "\n"
|
|
end
|
|
|
|
end
|
|
|
|
##
|
|
# A horizontal rule
|
|
|
|
class Rule < Fragment
|
|
type_name :RULE
|
|
end
|
|
|
|
##
|
|
# Collect groups of lines together. Each group will end up containing a flow
|
|
# of text.
|
|
|
|
class LineCollection
|
|
|
|
def initialize
|
|
@fragments = []
|
|
end
|
|
|
|
def add(fragment)
|
|
@fragments << fragment
|
|
end
|
|
|
|
def each(&b)
|
|
@fragments.each(&b)
|
|
end
|
|
|
|
def to_a # :nodoc:
|
|
@fragments.map {|fragment| fragment.to_s}
|
|
end
|
|
|
|
##
|
|
# Factory for different fragment types
|
|
|
|
def fragment_for(*args)
|
|
Fragment.for(*args)
|
|
end
|
|
|
|
##
|
|
# Tidy up at the end
|
|
|
|
def normalize
|
|
change_verbatim_blank_lines
|
|
add_list_start_and_ends
|
|
add_list_breaks
|
|
tidy_blank_lines
|
|
end
|
|
|
|
def to_s
|
|
@fragments.join("\n----\n")
|
|
end
|
|
|
|
def accept(am, visitor)
|
|
visitor.start_accepting
|
|
|
|
@fragments.each do |fragment|
|
|
case fragment
|
|
when Verbatim
|
|
visitor.accept_verbatim(am, fragment)
|
|
when Rule
|
|
visitor.accept_rule(am, fragment)
|
|
when ListStart
|
|
visitor.accept_list_start(am, fragment)
|
|
when ListEnd
|
|
visitor.accept_list_end(am, fragment)
|
|
when ListItem
|
|
visitor.accept_list_item(am, fragment)
|
|
when BlankLine
|
|
visitor.accept_blank_line(am, fragment)
|
|
when Heading
|
|
visitor.accept_heading(am, fragment)
|
|
when Paragraph
|
|
visitor.accept_paragraph(am, fragment)
|
|
end
|
|
end
|
|
|
|
visitor.end_accepting
|
|
end
|
|
|
|
private
|
|
|
|
# If you have:
|
|
#
|
|
# normal paragraph text.
|
|
#
|
|
# this is code
|
|
#
|
|
# and more code
|
|
#
|
|
# You'll end up with the fragments Paragraph, BlankLine, Verbatim,
|
|
# BlankLine, Verbatim, BlankLine, etc.
|
|
#
|
|
# The BlankLine in the middle of the verbatim chunk needs to be changed to
|
|
# a real verbatim newline, and the two verbatim blocks merged
|
|
|
|
def change_verbatim_blank_lines
|
|
frag_block = nil
|
|
blank_count = 0
|
|
@fragments.each_with_index do |frag, i|
|
|
if frag_block.nil?
|
|
frag_block = frag if Verbatim === frag
|
|
else
|
|
case frag
|
|
when Verbatim
|
|
blank_count.times { frag_block.add_text("\n") }
|
|
blank_count = 0
|
|
frag_block.add_text(frag.txt)
|
|
@fragments[i] = nil # remove out current fragment
|
|
when BlankLine
|
|
if frag_block
|
|
blank_count += 1
|
|
@fragments[i] = nil
|
|
end
|
|
else
|
|
frag_block = nil
|
|
blank_count = 0
|
|
end
|
|
end
|
|
end
|
|
@fragments.compact!
|
|
end
|
|
|
|
##
|
|
# List nesting is implicit given the level of indentation. Make it
|
|
# explicit, just to make life a tad easier for the output processors
|
|
|
|
def add_list_start_and_ends
|
|
level = 0
|
|
res = []
|
|
type_stack = []
|
|
|
|
@fragments.each do |fragment|
|
|
# $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}"
|
|
new_level = fragment.level
|
|
while (level < new_level)
|
|
level += 1
|
|
type = fragment.type
|
|
res << ListStart.new(level, fragment.param, type) if type
|
|
type_stack.push type
|
|
# $stderr.puts "Start: #{level}"
|
|
end
|
|
|
|
while level > new_level
|
|
type = type_stack.pop
|
|
res << ListEnd.new(level, type) if type
|
|
level -= 1
|
|
# $stderr.puts "End: #{level}, #{type}"
|
|
end
|
|
|
|
res << fragment
|
|
level = fragment.level
|
|
end
|
|
level.downto(1) do |i|
|
|
type = type_stack.pop
|
|
res << ListEnd.new(i, type) if type
|
|
end
|
|
|
|
@fragments = res
|
|
end
|
|
|
|
##
|
|
# Inserts start/ends between list entries at the same level that have
|
|
# different element types
|
|
|
|
def add_list_breaks
|
|
res = @fragments
|
|
|
|
@fragments = []
|
|
list_stack = []
|
|
|
|
res.each do |fragment|
|
|
case fragment
|
|
when ListStart
|
|
list_stack.push fragment
|
|
when ListEnd
|
|
start = list_stack.pop
|
|
fragment.type = start.type
|
|
when ListItem
|
|
l = list_stack.last
|
|
if fragment.type != l.type
|
|
@fragments << ListEnd.new(l.level, l.type)
|
|
start = ListStart.new(l.level, fragment.param, fragment.type)
|
|
@fragments << start
|
|
list_stack.pop
|
|
list_stack.push start
|
|
end
|
|
else
|
|
;
|
|
end
|
|
@fragments << fragment
|
|
end
|
|
end
|
|
|
|
##
|
|
# Tidy up the blank lines:
|
|
# * change Blank/ListEnd into ListEnd/Blank
|
|
# * remove blank lines at the front
|
|
|
|
def tidy_blank_lines
|
|
(@fragments.size - 1).times do |i|
|
|
if BlankLine === @fragments[i] and ListEnd === @fragments[i+1] then
|
|
@fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i]
|
|
end
|
|
end
|
|
|
|
# remove leading blanks
|
|
@fragments.each_with_index do |f, i|
|
|
break unless f.kind_of? BlankLine
|
|
@fragments[i] = nil
|
|
end
|
|
|
|
@fragments.compact!
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|