/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

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