require 'rdoc/markup/simple_markup/lines.rb' require 'rdoc/markup/simple_markup/inline.rb' module SM ## # 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 # For testing def to_a @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 # 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 # now insert 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 # Finally 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