1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
|
require 'rdoc/markup/simple_markup/lines.rb'
#require 'rdoc/markup/simple_markup/to_flow.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
|