PageRenderTime 108ms CodeModel.GetById 68ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 1ms

/tools/Ruby/lib/ruby/1.8/rdoc/markup/simple_markup/fragments.rb

http://github.com/agross/netopenspace
Ruby | 328 lines | 214 code | 57 blank | 57 comment | 13 complexity | dd077d1935509b50d8b3370a2ca160d6 MD5 | raw file
  1require 'rdoc/markup/simple_markup/lines.rb'
  2#require 'rdoc/markup/simple_markup/to_flow.rb'
  3
  4module SM
  5
  6  ##
  7  # A Fragment is a chunk of text, subclassed as a paragraph, a list
  8  # entry, or verbatim text
  9
 10  class Fragment
 11    attr_reader   :level, :param, :txt
 12    attr_accessor :type
 13
 14    def initialize(level, param, type, txt)
 15      @level = level
 16      @param = param
 17      @type  = type
 18      @txt   = ""
 19      add_text(txt) if txt
 20    end
 21
 22    def add_text(txt)
 23      @txt << " " if @txt.length > 0
 24      @txt << txt.tr_s("\n ", "  ").strip
 25    end
 26
 27    def to_s
 28      "L#@level: #{self.class.name.split('::')[-1]}\n#@txt"
 29    end
 30
 31    ######
 32    # This is a simple factory system that lets us associate fragement
 33    # types (a string) with a subclass of fragment
 34
 35    TYPE_MAP = {}
 36
 37    def Fragment.type_name(name)
 38      TYPE_MAP[name] = self
 39    end
 40
 41    def Fragment.for(line)
 42      klass =  TYPE_MAP[line.type] ||
 43        raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'")
 44      return klass.new(line.level, line.param, line.flag, line.text)
 45    end
 46  end
 47
 48  ##
 49  # A paragraph is a fragment which gets wrapped to fit. We remove all
 50  # newlines when we're created, and have them put back on output
 51
 52  class Paragraph < Fragment
 53    type_name Line::PARAGRAPH
 54  end
 55
 56  class BlankLine < Paragraph
 57    type_name Line::BLANK
 58  end
 59
 60  class Heading < Paragraph
 61    type_name Line::HEADING
 62
 63    def head_level
 64      @param.to_i
 65    end
 66  end
 67
 68  ##
 69  # A List is a fragment with some kind of label
 70  #
 71
 72  class ListBase < Paragraph
 73    # List types
 74    BULLET  = :BULLET
 75    NUMBER  = :NUMBER
 76    UPPERALPHA  = :UPPERALPHA
 77    LOWERALPHA  = :LOWERALPHA
 78    LABELED = :LABELED
 79    NOTE    = :NOTE
 80  end
 81
 82  class ListItem < ListBase
 83    type_name Line::LIST
 84
 85    #  def label
 86    #    am = AttributeManager.new(@param)
 87    #    am.flow
 88    #  end
 89  end
 90
 91  class ListStart < ListBase
 92    def initialize(level, param, type)
 93      super(level, param, type, nil)
 94    end
 95  end
 96
 97  class ListEnd < ListBase
 98    def initialize(level, type)
 99      super(level, "", type, nil)
100    end
101  end
102
103  ##
104  # Verbatim code contains lines that don't get wrapped.
105
106  class Verbatim < Fragment
107    type_name  Line::VERBATIM
108
109    def add_text(txt)
110      @txt << txt.chomp << "\n"
111    end
112
113  end
114
115  ##
116  # A horizontal rule
117  class Rule < Fragment
118    type_name Line::RULE
119  end
120
121
122  # Collect groups of lines together. Each group
123  # will end up containing a flow of text
124
125  class LineCollection
126    
127    def initialize
128      @fragments = []
129    end
130
131    def add(fragment)
132      @fragments << fragment
133    end
134
135    def each(&b)
136      @fragments.each(&b)
137    end
138
139    # For testing
140    def to_a
141      @fragments.map {|fragment| fragment.to_s}
142    end
143
144    # Factory for different fragment types
145    def fragment_for(*args)
146      Fragment.for(*args)
147    end
148
149    # tidy up at the end
150    def normalize
151      change_verbatim_blank_lines
152      add_list_start_and_ends
153      add_list_breaks
154      tidy_blank_lines
155    end
156
157    def to_s
158      @fragments.join("\n----\n")
159    end
160
161    def accept(am, visitor)
162
163      visitor.start_accepting
164
165      @fragments.each do |fragment|
166        case fragment
167        when Verbatim
168          visitor.accept_verbatim(am, fragment)
169        when Rule
170          visitor.accept_rule(am, fragment)
171        when ListStart
172          visitor.accept_list_start(am, fragment)
173        when ListEnd
174          visitor.accept_list_end(am, fragment)
175        when ListItem
176          visitor.accept_list_item(am, fragment)
177        when BlankLine
178          visitor.accept_blank_line(am, fragment)
179        when Heading
180          visitor.accept_heading(am, fragment)
181        when Paragraph
182          visitor.accept_paragraph(am, fragment)
183        end
184      end
185
186      visitor.end_accepting
187    end
188    #######
189    private
190    #######
191
192    # If you have:
193    #
194    #    normal paragraph text.
195    #
196    #       this is code
197    #   
198    #       and more code
199    #
200    # You'll end up with the fragments Paragraph, BlankLine, 
201    # Verbatim, BlankLine, Verbatim, BlankLine, etc
202    #
203    # The BlankLine in the middle of the verbatim chunk needs to
204    # be changed to a real verbatim newline, and the two
205    # verbatim blocks merged
206    #
207    #    
208    def change_verbatim_blank_lines
209      frag_block = nil
210      blank_count = 0
211      @fragments.each_with_index do |frag, i|
212        if frag_block.nil?
213          frag_block = frag if Verbatim === frag
214        else
215          case frag
216          when Verbatim
217            blank_count.times { frag_block.add_text("\n") }
218            blank_count = 0
219            frag_block.add_text(frag.txt)
220            @fragments[i] = nil    # remove out current fragment
221          when BlankLine
222            if frag_block
223              blank_count += 1
224              @fragments[i] = nil
225            end
226          else
227            frag_block = nil
228            blank_count = 0
229          end
230        end
231      end
232      @fragments.compact!
233    end
234
235    # List nesting is implicit given the level of
236    # Make it explicit, just to make life a tad
237    # easier for the output processors
238
239    def add_list_start_and_ends
240      level = 0
241      res = []
242      type_stack = []
243
244      @fragments.each do |fragment|
245        # $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}"
246        new_level = fragment.level
247        while (level < new_level)
248          level += 1
249          type = fragment.type
250          res << ListStart.new(level, fragment.param, type) if type
251          type_stack.push type
252          # $stderr.puts "Start: #{level}"
253        end
254
255        while level > new_level
256          type = type_stack.pop
257          res << ListEnd.new(level, type) if type
258          level -= 1
259          # $stderr.puts "End: #{level}, #{type}"
260        end
261
262        res << fragment
263        level = fragment.level
264      end
265      level.downto(1) do |i|
266        type = type_stack.pop
267        res << ListEnd.new(i, type) if type
268      end
269
270      @fragments = res
271    end
272
273    # now insert start/ends between list entries at the
274    # same level that have different element types
275
276    def add_list_breaks
277      res = @fragments
278
279      @fragments = []
280      list_stack = []
281
282      res.each do |fragment|
283        case fragment
284        when ListStart
285          list_stack.push fragment
286        when ListEnd
287          start = list_stack.pop
288          fragment.type = start.type
289        when ListItem
290          l = list_stack.last
291          if fragment.type != l.type
292            @fragments << ListEnd.new(l.level, l.type)
293            start = ListStart.new(l.level, fragment.param, fragment.type)
294            @fragments << start
295            list_stack.pop
296            list_stack.push start
297          end
298        else
299          ;
300        end
301        @fragments << fragment
302      end
303    end
304
305    # Finally tidy up the blank lines:
306    # * change Blank/ListEnd into ListEnd/Blank
307    # * remove blank lines at the front
308
309    def tidy_blank_lines
310      (@fragments.size - 1).times do |i|
311        if @fragments[i].kind_of?(BlankLine) and 
312            @fragments[i+1].kind_of?(ListEnd)
313          @fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i] 
314        end
315      end
316
317      # remove leading blanks
318      @fragments.each_with_index do |f, i|
319        break unless f.kind_of? BlankLine
320        @fragments[i] = nil
321      end
322
323      @fragments.compact!
324    end
325
326  end
327  
328end