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
329
330
331
332
333
334
335
336
337
|
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
##
# This is a simple factory system that lets us associate fragement
# types (a string) with a subclass of fragment
TYPE_MAP = {}
def self.type_name(name)
TYPE_MAP[name] = self
end
def self.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
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
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 :PARAGRAPH
end
class BlankLine < Paragraph
type_name :BLANK
end
class Heading < Paragraph
type_name :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,
:NUMBER,
:UPPERALPHA,
:LOWERALPHA,
:LABELED,
:NOTE,
]
end
class ListItem < ListBase
type_name :LIST
def to_s
text = if [:NOTE, :LABELED].include? type then
"#{@param}: #{@txt}"
else
@txt
end
"L#@level: #{type} #{self.class.name.split('::')[-1]}\n#{text}"
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 :VERBATIM
def add_text(txt)
@txt << txt.chomp << "\n"
end
end
##
# A horizontal rule
class Rule < Fragment
type_name :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 BlankLine === @fragments[i] and ListEnd === @fragments[i+1] then
@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
|