diff options
Diffstat (limited to 'lib/rdoc/markup/fragments.rb')
-rw-r--r-- | lib/rdoc/markup/fragments.rb | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/lib/rdoc/markup/fragments.rb b/lib/rdoc/markup/fragments.rb new file mode 100644 index 0000000000..2dd3614fc1 --- /dev/null +++ b/lib/rdoc/markup/fragments.rb @@ -0,0 +1,330 @@ +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 + + 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 + + ###### + # This is a simple factory system that lets us associate fragement + # types (a string) with a subclass of fragment + + TYPE_MAP = {} + + def Fragment.type_name(name) + TYPE_MAP[name] = self + end + + def Fragment.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 + 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 Line::PARAGRAPH + end + + class BlankLine < Paragraph + type_name Line::BLANK + end + + class Heading < Paragraph + type_name Line::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 = :BULLET + NUMBER = :NUMBER + UPPERALPHA = :UPPERALPHA + LOWERALPHA = :LOWERALPHA + LABELED = :LABELED + NOTE = :NOTE + end + + class ListItem < ListBase + type_name Line::LIST + + # def label + # am = AttributeManager.new(@param) + # am.flow + # 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 Line::VERBATIM + + def add_text(txt) + @txt << txt.chomp << "\n" + end + + end + + ## + # A horizontal rule + + class Rule < Fragment + type_name Line::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 @fragments[i].kind_of?(BlankLine) and + @fragments[i+1].kind_of?(ListEnd) + @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 + |