rdoc 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rdoc might be problematic. Click here for more details.
- data/History.txt +13 -0
- data/Manifest.txt +61 -0
- data/README.txt +34 -0
- data/Rakefile +10 -0
- data/bin/rdoc +22 -0
- data/bin/ri +6 -0
- data/lib/rdoc.rb +277 -0
- data/lib/rdoc/code_objects.rb +776 -0
- data/lib/rdoc/diagram.rb +338 -0
- data/lib/rdoc/dot.rb +249 -0
- data/lib/rdoc/generator.rb +1048 -0
- data/lib/rdoc/generator/chm.rb +113 -0
- data/lib/rdoc/generator/chm/chm.rb +98 -0
- data/lib/rdoc/generator/html.rb +370 -0
- data/lib/rdoc/generator/html/hefss.rb +414 -0
- data/lib/rdoc/generator/html/html.rb +704 -0
- data/lib/rdoc/generator/html/kilmer.rb +418 -0
- data/lib/rdoc/generator/html/one_page_html.rb +121 -0
- data/lib/rdoc/generator/ri.rb +229 -0
- data/lib/rdoc/generator/xml.rb +120 -0
- data/lib/rdoc/generator/xml/rdf.rb +113 -0
- data/lib/rdoc/generator/xml/xml.rb +111 -0
- data/lib/rdoc/markup.rb +473 -0
- data/lib/rdoc/markup/attribute_manager.rb +274 -0
- data/lib/rdoc/markup/formatter.rb +14 -0
- data/lib/rdoc/markup/fragments.rb +337 -0
- data/lib/rdoc/markup/inline.rb +101 -0
- data/lib/rdoc/markup/lines.rb +152 -0
- data/lib/rdoc/markup/preprocess.rb +71 -0
- data/lib/rdoc/markup/to_flow.rb +185 -0
- data/lib/rdoc/markup/to_html.rb +353 -0
- data/lib/rdoc/markup/to_html_crossref.rb +86 -0
- data/lib/rdoc/markup/to_latex.rb +328 -0
- data/lib/rdoc/markup/to_test.rb +50 -0
- data/lib/rdoc/options.rb +616 -0
- data/lib/rdoc/parsers/parse_c.rb +775 -0
- data/lib/rdoc/parsers/parse_f95.rb +1841 -0
- data/lib/rdoc/parsers/parse_rb.rb +2584 -0
- data/lib/rdoc/parsers/parse_simple.rb +40 -0
- data/lib/rdoc/parsers/parserfactory.rb +99 -0
- data/lib/rdoc/rdoc.rb +277 -0
- data/lib/rdoc/ri.rb +4 -0
- data/lib/rdoc/ri/cache.rb +188 -0
- data/lib/rdoc/ri/descriptions.rb +150 -0
- data/lib/rdoc/ri/display.rb +274 -0
- data/lib/rdoc/ri/driver.rb +452 -0
- data/lib/rdoc/ri/formatter.rb +616 -0
- data/lib/rdoc/ri/paths.rb +102 -0
- data/lib/rdoc/ri/reader.rb +106 -0
- data/lib/rdoc/ri/util.rb +81 -0
- data/lib/rdoc/ri/writer.rb +68 -0
- data/lib/rdoc/stats.rb +25 -0
- data/lib/rdoc/template.rb +64 -0
- data/lib/rdoc/tokenstream.rb +33 -0
- data/test/test_rdoc_c_parser.rb +261 -0
- data/test/test_rdoc_markup.rb +613 -0
- data/test/test_rdoc_markup_attribute_manager.rb +224 -0
- data/test/test_rdoc_ri_attribute_formatter.rb +42 -0
- data/test/test_rdoc_ri_default_display.rb +295 -0
- data/test/test_rdoc_ri_formatter.rb +318 -0
- data/test/test_rdoc_ri_overstrike_formatter.rb +69 -0
- metadata +134 -0
@@ -0,0 +1,274 @@
|
|
1
|
+
require 'rdoc/markup/inline'
|
2
|
+
|
3
|
+
class RDoc::Markup::AttributeManager
|
4
|
+
|
5
|
+
NULL = "\000".freeze
|
6
|
+
|
7
|
+
##
|
8
|
+
# We work by substituting non-printing characters in to the text. For now
|
9
|
+
# I'm assuming that I can substitute a character in the range 0..8 for a 7
|
10
|
+
# bit character without damaging the encoded string, but this might be
|
11
|
+
# optimistic
|
12
|
+
|
13
|
+
A_PROTECT = 004
|
14
|
+
PROTECT_ATTR = A_PROTECT.chr
|
15
|
+
|
16
|
+
##
|
17
|
+
# This maps delimiters that occur around words (such as *bold* or +tt+)
|
18
|
+
# where the start and end delimiters and the same. This lets us optimize
|
19
|
+
# the regexp
|
20
|
+
|
21
|
+
MATCHING_WORD_PAIRS = {}
|
22
|
+
|
23
|
+
##
|
24
|
+
# And this is used when the delimiters aren't the same. In this case the
|
25
|
+
# hash maps a pattern to the attribute character
|
26
|
+
|
27
|
+
WORD_PAIR_MAP = {}
|
28
|
+
|
29
|
+
##
|
30
|
+
# This maps HTML tags to the corresponding attribute char
|
31
|
+
|
32
|
+
HTML_TAGS = {}
|
33
|
+
|
34
|
+
##
|
35
|
+
# And this maps _special_ sequences to a name. A special sequence is
|
36
|
+
# something like a WikiWord
|
37
|
+
|
38
|
+
SPECIAL = {}
|
39
|
+
|
40
|
+
##
|
41
|
+
# Return an attribute object with the given turn_on and turn_off bits set
|
42
|
+
|
43
|
+
def attribute(turn_on, turn_off)
|
44
|
+
RDoc::Markup::AttrChanger.new turn_on, turn_off
|
45
|
+
end
|
46
|
+
|
47
|
+
def change_attribute(current, new)
|
48
|
+
diff = current ^ new
|
49
|
+
attribute(new & diff, current & diff)
|
50
|
+
end
|
51
|
+
|
52
|
+
def changed_attribute_by_name(current_set, new_set)
|
53
|
+
current = new = 0
|
54
|
+
current_set.each do |name|
|
55
|
+
current |= RDoc::Markup::Attribute.bitmap_for(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
new_set.each do |name|
|
59
|
+
new |= RDoc::Markup::Attribute.bitmap_for(name)
|
60
|
+
end
|
61
|
+
|
62
|
+
change_attribute(current, new)
|
63
|
+
end
|
64
|
+
|
65
|
+
def copy_string(start_pos, end_pos)
|
66
|
+
res = @str[start_pos...end_pos]
|
67
|
+
res.gsub!(/\000/, '')
|
68
|
+
res
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Map attributes like <b>text</b>to the sequence
|
73
|
+
# \001\002<char>\001\003<char>, where <char> is a per-attribute specific
|
74
|
+
# character
|
75
|
+
|
76
|
+
def convert_attrs(str, attrs)
|
77
|
+
# first do matching ones
|
78
|
+
tags = MATCHING_WORD_PAIRS.keys.join("")
|
79
|
+
|
80
|
+
re = /(^|\W)([#{tags}])([#:\\]?[\w.\/-]+?\S?)\2(\W|$)/
|
81
|
+
|
82
|
+
1 while str.gsub!(re) do
|
83
|
+
attr = MATCHING_WORD_PAIRS[$2]
|
84
|
+
attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr)
|
85
|
+
$1 + NULL * $2.length + $3 + NULL * $2.length + $4
|
86
|
+
end
|
87
|
+
|
88
|
+
# then non-matching
|
89
|
+
unless WORD_PAIR_MAP.empty? then
|
90
|
+
WORD_PAIR_MAP.each do |regexp, attr|
|
91
|
+
str.gsub!(regexp) {
|
92
|
+
attrs.set_attrs($`.length + $1.length, $2.length, attr)
|
93
|
+
NULL * $1.length + $2 + NULL * $3.length
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def convert_html(str, attrs)
|
100
|
+
tags = HTML_TAGS.keys.join '|'
|
101
|
+
|
102
|
+
1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) {
|
103
|
+
attr = HTML_TAGS[$1.downcase]
|
104
|
+
html_length = $1.length + 2
|
105
|
+
seq = NULL * html_length
|
106
|
+
attrs.set_attrs($`.length + html_length, $2.length, attr)
|
107
|
+
seq + $2 + seq + NULL
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
def convert_specials(str, attrs)
|
112
|
+
unless SPECIAL.empty?
|
113
|
+
SPECIAL.each do |regexp, attr|
|
114
|
+
str.scan(regexp) do
|
115
|
+
attrs.set_attrs($`.length, $&.length,
|
116
|
+
attr | RDoc::Markup::Attribute::SPECIAL)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# A \ in front of a character that would normally be processed turns off
|
124
|
+
# processing. We do this by turning \< into <#{PROTECT}
|
125
|
+
|
126
|
+
PROTECTABLE = %w[<\\]
|
127
|
+
|
128
|
+
def mask_protected_sequences
|
129
|
+
protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])")
|
130
|
+
@str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}")
|
131
|
+
end
|
132
|
+
|
133
|
+
def unmask_protected_sequences
|
134
|
+
@str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000")
|
135
|
+
end
|
136
|
+
|
137
|
+
def initialize
|
138
|
+
add_word_pair("*", "*", :BOLD)
|
139
|
+
add_word_pair("_", "_", :EM)
|
140
|
+
add_word_pair("+", "+", :TT)
|
141
|
+
|
142
|
+
add_html("em", :EM)
|
143
|
+
add_html("i", :EM)
|
144
|
+
add_html("b", :BOLD)
|
145
|
+
add_html("tt", :TT)
|
146
|
+
add_html("code", :TT)
|
147
|
+
|
148
|
+
add_special(/<!--(.*?)-->/, :COMMENT)
|
149
|
+
end
|
150
|
+
|
151
|
+
def add_word_pair(start, stop, name)
|
152
|
+
raise ArgumentError, "Word flags may not start with '<'" if
|
153
|
+
start[0,1] == '<'
|
154
|
+
|
155
|
+
bitmap = RDoc::Markup::Attribute.bitmap_for name
|
156
|
+
|
157
|
+
if start == stop then
|
158
|
+
MATCHING_WORD_PAIRS[start] = bitmap
|
159
|
+
else
|
160
|
+
pattern = /(#{Regexp.escape start})(\S+)(#{Regexp.escape stop})/
|
161
|
+
WORD_PAIR_MAP[pattern] = bitmap
|
162
|
+
end
|
163
|
+
|
164
|
+
PROTECTABLE << start[0,1]
|
165
|
+
PROTECTABLE.uniq!
|
166
|
+
end
|
167
|
+
|
168
|
+
def add_html(tag, name)
|
169
|
+
HTML_TAGS[tag.downcase] = RDoc::Markup::Attribute.bitmap_for name
|
170
|
+
end
|
171
|
+
|
172
|
+
def add_special(pattern, name)
|
173
|
+
SPECIAL[pattern] = RDoc::Markup::Attribute.bitmap_for name
|
174
|
+
end
|
175
|
+
|
176
|
+
def flow(str)
|
177
|
+
@str = str
|
178
|
+
|
179
|
+
puts("Before flow, str='#{@str.dump}'") if $DEBUG_RDOC
|
180
|
+
mask_protected_sequences
|
181
|
+
|
182
|
+
@attrs = RDoc::Markup::AttrSpan.new @str.length
|
183
|
+
|
184
|
+
puts("After protecting, str='#{@str.dump}'") if $DEBUG_RDOC
|
185
|
+
|
186
|
+
convert_attrs(@str, @attrs)
|
187
|
+
convert_html(@str, @attrs)
|
188
|
+
convert_specials(str, @attrs)
|
189
|
+
|
190
|
+
unmask_protected_sequences
|
191
|
+
|
192
|
+
puts("After flow, str='#{@str.dump}'") if $DEBUG_RDOC
|
193
|
+
|
194
|
+
return split_into_flow
|
195
|
+
end
|
196
|
+
|
197
|
+
def display_attributes
|
198
|
+
puts
|
199
|
+
puts @str.tr(NULL, "!")
|
200
|
+
bit = 1
|
201
|
+
16.times do |bno|
|
202
|
+
line = ""
|
203
|
+
@str.length.times do |i|
|
204
|
+
if (@attrs[i] & bit) == 0
|
205
|
+
line << " "
|
206
|
+
else
|
207
|
+
if bno.zero?
|
208
|
+
line << "S"
|
209
|
+
else
|
210
|
+
line << ("%d" % (bno+1))
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
puts(line) unless line =~ /^ *$/
|
215
|
+
bit <<= 1
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def split_into_flow
|
220
|
+
display_attributes if $DEBUG_RDOC
|
221
|
+
|
222
|
+
res = []
|
223
|
+
current_attr = 0
|
224
|
+
str = ""
|
225
|
+
|
226
|
+
str_len = @str.length
|
227
|
+
|
228
|
+
# skip leading invisible text
|
229
|
+
i = 0
|
230
|
+
i += 1 while i < str_len and @str[i].chr == "\0"
|
231
|
+
start_pos = i
|
232
|
+
|
233
|
+
# then scan the string, chunking it on attribute changes
|
234
|
+
while i < str_len
|
235
|
+
new_attr = @attrs[i]
|
236
|
+
if new_attr != current_attr
|
237
|
+
if i > start_pos
|
238
|
+
res << copy_string(start_pos, i)
|
239
|
+
start_pos = i
|
240
|
+
end
|
241
|
+
|
242
|
+
res << change_attribute(current_attr, new_attr)
|
243
|
+
current_attr = new_attr
|
244
|
+
|
245
|
+
if (current_attr & RDoc::Markup::Attribute::SPECIAL) != 0 then
|
246
|
+
i += 1 while
|
247
|
+
i < str_len and (@attrs[i] & RDoc::Markup::Attribute::SPECIAL) != 0
|
248
|
+
|
249
|
+
res << RDoc::Markup::Special.new(current_attr,
|
250
|
+
copy_string(start_pos, i))
|
251
|
+
start_pos = i
|
252
|
+
next
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# move on, skipping any invisible characters
|
257
|
+
begin
|
258
|
+
i += 1
|
259
|
+
end while i < str_len and @str[i].chr == "\0"
|
260
|
+
end
|
261
|
+
|
262
|
+
# tidy up trailing text
|
263
|
+
if start_pos < str_len
|
264
|
+
res << copy_string(start_pos, str_len)
|
265
|
+
end
|
266
|
+
|
267
|
+
# and reset to all attributes off
|
268
|
+
res << change_attribute(current_attr, 0) if current_attr != 0
|
269
|
+
|
270
|
+
return res
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
|
@@ -0,0 +1,337 @@
|
|
1
|
+
require 'rdoc/markup'
|
2
|
+
require 'rdoc/markup/lines'
|
3
|
+
|
4
|
+
class RDoc::Markup
|
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
|
+
######
|
15
|
+
# This is a simple factory system that lets us associate fragement
|
16
|
+
# types (a string) with a subclass of fragment
|
17
|
+
|
18
|
+
TYPE_MAP = {}
|
19
|
+
|
20
|
+
def self.type_name(name)
|
21
|
+
TYPE_MAP[name] = self
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.for(line)
|
25
|
+
klass = TYPE_MAP[line.type] ||
|
26
|
+
raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'")
|
27
|
+
return klass.new(line.level, line.param, line.flag, line.text)
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(level, param, type, txt)
|
31
|
+
@level = level
|
32
|
+
@param = param
|
33
|
+
@type = type
|
34
|
+
@txt = ""
|
35
|
+
add_text(txt) if txt
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_text(txt)
|
39
|
+
@txt << " " if @txt.length > 0
|
40
|
+
@txt << txt.tr_s("\n ", " ").strip
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"L#@level: #{self.class.name.split('::')[-1]}\n#@txt"
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# A paragraph is a fragment which gets wrapped to fit. We remove all
|
51
|
+
# newlines when we're created, and have them put back on output.
|
52
|
+
|
53
|
+
class Paragraph < Fragment
|
54
|
+
type_name :PARAGRAPH
|
55
|
+
end
|
56
|
+
|
57
|
+
class BlankLine < Paragraph
|
58
|
+
type_name :BLANK
|
59
|
+
end
|
60
|
+
|
61
|
+
class Heading < Paragraph
|
62
|
+
type_name :HEADING
|
63
|
+
|
64
|
+
def head_level
|
65
|
+
@param.to_i
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# A List is a fragment with some kind of label
|
71
|
+
|
72
|
+
class ListBase < Paragraph
|
73
|
+
LIST_TYPES = [
|
74
|
+
:BULLET,
|
75
|
+
:NUMBER,
|
76
|
+
:UPPERALPHA,
|
77
|
+
:LOWERALPHA,
|
78
|
+
:LABELED,
|
79
|
+
:NOTE,
|
80
|
+
]
|
81
|
+
end
|
82
|
+
|
83
|
+
class ListItem < ListBase
|
84
|
+
type_name :LIST
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
text = if [:NOTE, :LABELED].include? type then
|
88
|
+
"#{@param}: #{@txt}"
|
89
|
+
else
|
90
|
+
@txt
|
91
|
+
end
|
92
|
+
|
93
|
+
"L#@level: #{type} #{self.class.name.split('::')[-1]}\n#{text}"
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
class ListStart < ListBase
|
99
|
+
def initialize(level, param, type)
|
100
|
+
super(level, param, type, nil)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class ListEnd < ListBase
|
105
|
+
def initialize(level, type)
|
106
|
+
super(level, "", type, nil)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Verbatim code contains lines that don't get wrapped.
|
112
|
+
|
113
|
+
class Verbatim < Fragment
|
114
|
+
type_name :VERBATIM
|
115
|
+
|
116
|
+
def add_text(txt)
|
117
|
+
@txt << txt.chomp << "\n"
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# A horizontal rule
|
124
|
+
|
125
|
+
class Rule < Fragment
|
126
|
+
type_name :RULE
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Collect groups of lines together. Each group will end up containing a flow
|
131
|
+
# of text.
|
132
|
+
|
133
|
+
class LineCollection
|
134
|
+
|
135
|
+
def initialize
|
136
|
+
@fragments = []
|
137
|
+
end
|
138
|
+
|
139
|
+
def add(fragment)
|
140
|
+
@fragments << fragment
|
141
|
+
end
|
142
|
+
|
143
|
+
def each(&b)
|
144
|
+
@fragments.each(&b)
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_a # :nodoc:
|
148
|
+
@fragments.map {|fragment| fragment.to_s}
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Factory for different fragment types
|
153
|
+
|
154
|
+
def fragment_for(*args)
|
155
|
+
Fragment.for(*args)
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Tidy up at the end
|
160
|
+
|
161
|
+
def normalize
|
162
|
+
change_verbatim_blank_lines
|
163
|
+
add_list_start_and_ends
|
164
|
+
add_list_breaks
|
165
|
+
tidy_blank_lines
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_s
|
169
|
+
@fragments.join("\n----\n")
|
170
|
+
end
|
171
|
+
|
172
|
+
def accept(am, visitor)
|
173
|
+
visitor.start_accepting
|
174
|
+
|
175
|
+
@fragments.each do |fragment|
|
176
|
+
case fragment
|
177
|
+
when Verbatim
|
178
|
+
visitor.accept_verbatim(am, fragment)
|
179
|
+
when Rule
|
180
|
+
visitor.accept_rule(am, fragment)
|
181
|
+
when ListStart
|
182
|
+
visitor.accept_list_start(am, fragment)
|
183
|
+
when ListEnd
|
184
|
+
visitor.accept_list_end(am, fragment)
|
185
|
+
when ListItem
|
186
|
+
visitor.accept_list_item(am, fragment)
|
187
|
+
when BlankLine
|
188
|
+
visitor.accept_blank_line(am, fragment)
|
189
|
+
when Heading
|
190
|
+
visitor.accept_heading(am, fragment)
|
191
|
+
when Paragraph
|
192
|
+
visitor.accept_paragraph(am, fragment)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
visitor.end_accepting
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
# If you have:
|
202
|
+
#
|
203
|
+
# normal paragraph text.
|
204
|
+
#
|
205
|
+
# this is code
|
206
|
+
#
|
207
|
+
# and more code
|
208
|
+
#
|
209
|
+
# You'll end up with the fragments Paragraph, BlankLine, Verbatim,
|
210
|
+
# BlankLine, Verbatim, BlankLine, etc.
|
211
|
+
#
|
212
|
+
# The BlankLine in the middle of the verbatim chunk needs to be changed to
|
213
|
+
# a real verbatim newline, and the two verbatim blocks merged
|
214
|
+
|
215
|
+
def change_verbatim_blank_lines
|
216
|
+
frag_block = nil
|
217
|
+
blank_count = 0
|
218
|
+
@fragments.each_with_index do |frag, i|
|
219
|
+
if frag_block.nil?
|
220
|
+
frag_block = frag if Verbatim === frag
|
221
|
+
else
|
222
|
+
case frag
|
223
|
+
when Verbatim
|
224
|
+
blank_count.times { frag_block.add_text("\n") }
|
225
|
+
blank_count = 0
|
226
|
+
frag_block.add_text(frag.txt)
|
227
|
+
@fragments[i] = nil # remove out current fragment
|
228
|
+
when BlankLine
|
229
|
+
if frag_block
|
230
|
+
blank_count += 1
|
231
|
+
@fragments[i] = nil
|
232
|
+
end
|
233
|
+
else
|
234
|
+
frag_block = nil
|
235
|
+
blank_count = 0
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
@fragments.compact!
|
240
|
+
end
|
241
|
+
|
242
|
+
##
|
243
|
+
# List nesting is implicit given the level of indentation. Make it
|
244
|
+
# explicit, just to make life a tad easier for the output processors
|
245
|
+
|
246
|
+
def add_list_start_and_ends
|
247
|
+
level = 0
|
248
|
+
res = []
|
249
|
+
type_stack = []
|
250
|
+
|
251
|
+
@fragments.each do |fragment|
|
252
|
+
# $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}"
|
253
|
+
new_level = fragment.level
|
254
|
+
while (level < new_level)
|
255
|
+
level += 1
|
256
|
+
type = fragment.type
|
257
|
+
res << ListStart.new(level, fragment.param, type) if type
|
258
|
+
type_stack.push type
|
259
|
+
# $stderr.puts "Start: #{level}"
|
260
|
+
end
|
261
|
+
|
262
|
+
while level > new_level
|
263
|
+
type = type_stack.pop
|
264
|
+
res << ListEnd.new(level, type) if type
|
265
|
+
level -= 1
|
266
|
+
# $stderr.puts "End: #{level}, #{type}"
|
267
|
+
end
|
268
|
+
|
269
|
+
res << fragment
|
270
|
+
level = fragment.level
|
271
|
+
end
|
272
|
+
level.downto(1) do |i|
|
273
|
+
type = type_stack.pop
|
274
|
+
res << ListEnd.new(i, type) if type
|
275
|
+
end
|
276
|
+
|
277
|
+
@fragments = res
|
278
|
+
end
|
279
|
+
|
280
|
+
##
|
281
|
+
# Inserts start/ends between list entries at the same level that have
|
282
|
+
# different element types
|
283
|
+
|
284
|
+
def add_list_breaks
|
285
|
+
res = @fragments
|
286
|
+
|
287
|
+
@fragments = []
|
288
|
+
list_stack = []
|
289
|
+
|
290
|
+
res.each do |fragment|
|
291
|
+
case fragment
|
292
|
+
when ListStart
|
293
|
+
list_stack.push fragment
|
294
|
+
when ListEnd
|
295
|
+
start = list_stack.pop
|
296
|
+
fragment.type = start.type
|
297
|
+
when ListItem
|
298
|
+
l = list_stack.last
|
299
|
+
if fragment.type != l.type
|
300
|
+
@fragments << ListEnd.new(l.level, l.type)
|
301
|
+
start = ListStart.new(l.level, fragment.param, fragment.type)
|
302
|
+
@fragments << start
|
303
|
+
list_stack.pop
|
304
|
+
list_stack.push start
|
305
|
+
end
|
306
|
+
else
|
307
|
+
;
|
308
|
+
end
|
309
|
+
@fragments << fragment
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
##
|
314
|
+
# Tidy up the blank lines:
|
315
|
+
# * change Blank/ListEnd into ListEnd/Blank
|
316
|
+
# * remove blank lines at the front
|
317
|
+
|
318
|
+
def tidy_blank_lines
|
319
|
+
(@fragments.size - 1).times do |i|
|
320
|
+
if BlankLine === @fragments[i] and ListEnd === @fragments[i+1] then
|
321
|
+
@fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i]
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# remove leading blanks
|
326
|
+
@fragments.each_with_index do |f, i|
|
327
|
+
break unless f.kind_of? BlankLine
|
328
|
+
@fragments[i] = nil
|
329
|
+
end
|
330
|
+
|
331
|
+
@fragments.compact!
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|