asciidoctor 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of asciidoctor might be problematic. Click here for more details.
- data/README.asciidoc +156 -0
- data/asciidoctor.gemspec +5 -4
- data/lib/asciidoctor.rb +46 -34
- data/lib/asciidoctor/block.rb +59 -42
- data/lib/asciidoctor/document.rb +56 -20
- data/lib/asciidoctor/lexer.rb +204 -107
- data/lib/asciidoctor/list_item.rb +47 -14
- data/lib/asciidoctor/reader.rb +29 -14
- data/lib/asciidoctor/render_templates.rb +162 -64
- data/lib/asciidoctor/renderer.rb +3 -3
- data/lib/asciidoctor/section.rb +32 -6
- data/lib/asciidoctor/version.rb +1 -1
- data/test/attributes_test.rb +44 -4
- data/test/document_test.rb +21 -2
- data/test/headers_test.rb +72 -13
- data/test/lexer_test.rb +54 -0
- data/test/lists_test.rb +406 -0
- data/test/paragraphs_test.rb +103 -3
- data/test/preamble_test.rb +88 -0
- data/test/test_helper.rb +6 -5
- data/test/text_test.rb +2 -1
- metadata +7 -5
- data/README.md +0 -154
- data/test/list_elements_test.rb +0 -55
data/lib/asciidoctor/document.rb
CHANGED
@@ -4,14 +4,14 @@ class Asciidoctor::Document
|
|
4
4
|
|
5
5
|
include Asciidoctor
|
6
6
|
|
7
|
-
# Public: Get the Hash of
|
8
|
-
attr_reader :
|
7
|
+
# Public: Get the Hash of attributes
|
8
|
+
attr_reader :attributes
|
9
9
|
|
10
10
|
# Public: Get the Hash of document references
|
11
11
|
attr_reader :references
|
12
12
|
|
13
|
-
#
|
14
|
-
attr_reader :header
|
13
|
+
# The section level 0 element
|
14
|
+
attr_reader :header
|
15
15
|
|
16
16
|
# Public: Get the Array of elements (really Blocks or Sections) for the document
|
17
17
|
attr_reader :elements
|
@@ -29,13 +29,25 @@ class Asciidoctor::Document
|
|
29
29
|
def initialize(data, options = {}, &block)
|
30
30
|
@elements = []
|
31
31
|
@options = options
|
32
|
+
@options[:header_footer] = @options.fetch(:header_footer, true)
|
32
33
|
|
33
|
-
@
|
34
|
+
@attributes = {}
|
35
|
+
@attributes['sectids'] = nil
|
36
|
+
|
37
|
+
@reader = Reader.new(data, @attributes, &block)
|
34
38
|
|
35
39
|
# pseudo-delegation :)
|
36
|
-
|
40
|
+
#@attributes = @reader.attributes
|
37
41
|
@references = @reader.references
|
38
42
|
|
43
|
+
# dynamic intrinstic attribute values
|
44
|
+
@attributes['doctype'] ||= DEFAULT_DOCTYPE
|
45
|
+
now = Time.new
|
46
|
+
@attributes['localdate'] ||= now.strftime('%Y-%m-%d')
|
47
|
+
@attributes['localtime'] ||= now.strftime('%H:%m:%S %Z')
|
48
|
+
@attributes['localdatetime'] ||= [@attributes['localdate'], @attributes['localtime']].join(' ')
|
49
|
+
@attributes['asciidoctor-version'] = VERSION
|
50
|
+
|
39
51
|
# Now parse @lines into elements
|
40
52
|
while @reader.has_lines?
|
41
53
|
@reader.skip_blank
|
@@ -48,11 +60,11 @@ class Asciidoctor::Document
|
|
48
60
|
Asciidoctor.debug el
|
49
61
|
end
|
50
62
|
|
63
|
+
# split off the level 0 section, if present
|
51
64
|
root = @elements.first
|
52
|
-
# Try to find a @header from the Section blocks we have (if any).
|
53
65
|
if root.is_a?(Section) && root.level == 0
|
54
66
|
@header = @elements.shift
|
55
|
-
@elements = @header.blocks
|
67
|
+
@elements = @header.blocks
|
56
68
|
@header.clear_blocks
|
57
69
|
end
|
58
70
|
|
@@ -63,21 +75,43 @@ class Asciidoctor::Document
|
|
63
75
|
@reader.source if @reader
|
64
76
|
end
|
65
77
|
|
66
|
-
|
78
|
+
def attr(name, default = nil)
|
79
|
+
default.nil? ? @attributes[name.to_s] : @attributes.fetch(name.to_s, default)
|
80
|
+
#default.nil? ? @attributes[name.to_s.tr('_', '-')] : @attributes.fetch(name.to_s.tr('_', '-'), default)
|
81
|
+
end
|
82
|
+
|
83
|
+
def attr?(name)
|
84
|
+
@attributes.has_key? name.to_s
|
85
|
+
#@attributes.has_key? name.to_s.tr('_', '-')
|
86
|
+
end
|
87
|
+
|
88
|
+
def level
|
89
|
+
0
|
90
|
+
end
|
91
|
+
|
92
|
+
# The title explicitly defined in the document attributes
|
67
93
|
def title
|
68
|
-
|
94
|
+
@attributes['title']
|
95
|
+
end
|
96
|
+
|
97
|
+
# We need to be able to return some semblance of a title
|
98
|
+
def doctitle
|
99
|
+
# cached value
|
100
|
+
return @doctitle if @doctitle
|
69
101
|
|
70
102
|
if @header
|
71
|
-
@
|
103
|
+
@doctitle = @header.title
|
72
104
|
elsif @elements.first
|
73
|
-
@
|
74
|
-
# Blocks don't have a :name method, but Sections do
|
75
|
-
@title ||= @elements.first.name if @elements.first.respond_to? :name
|
105
|
+
@doctitle = @elements.first.title
|
76
106
|
end
|
77
107
|
|
78
|
-
@
|
108
|
+
@doctitle
|
109
|
+
end
|
110
|
+
alias :name :doctitle
|
111
|
+
|
112
|
+
def notitle
|
113
|
+
@attributes.has_key? 'notitle'
|
79
114
|
end
|
80
|
-
alias :name :title
|
81
115
|
|
82
116
|
def splain
|
83
117
|
if @header
|
@@ -110,11 +144,13 @@ class Asciidoctor::Document
|
|
110
144
|
@renderer = Renderer.new(render_options)
|
111
145
|
end
|
112
146
|
|
113
|
-
# Public: Render the Asciidoc document using
|
114
|
-
#
|
147
|
+
# Public: Render the Asciidoc document using the templates
|
148
|
+
# loaded by Renderer. If a :template_dir is not specified,
|
149
|
+
# or a template is missing, the renderer will fall back to
|
150
|
+
# using the appropriate built-in template.
|
115
151
|
def render(options = {})
|
116
152
|
r = renderer(options)
|
117
|
-
|
153
|
+
@options.merge(options)[:header_footer] ? r.render('document', self) : content
|
118
154
|
end
|
119
155
|
|
120
156
|
def content
|
@@ -123,7 +159,7 @@ class Asciidoctor::Document
|
|
123
159
|
Asciidoctor::debug "Rendering element: #{element}"
|
124
160
|
html_pieces << element.render
|
125
161
|
end
|
126
|
-
html_pieces.join
|
162
|
+
html_pieces.join
|
127
163
|
end
|
128
164
|
|
129
165
|
end
|
data/lib/asciidoctor/lexer.rb
CHANGED
@@ -28,6 +28,7 @@ class Asciidoctor::Lexer
|
|
28
28
|
reader.skip_blank
|
29
29
|
|
30
30
|
return nil unless reader.has_lines?
|
31
|
+
context = parent.is_a?(Block) ? parent.context : nil
|
31
32
|
|
32
33
|
# NOTE: An anchor looks like this:
|
33
34
|
# [[foo]]
|
@@ -45,6 +46,11 @@ class Asciidoctor::Lexer
|
|
45
46
|
anchor = nil
|
46
47
|
end
|
47
48
|
|
49
|
+
# skip a list continuation character if we're processing a list
|
50
|
+
if LIST_CONTEXTS.include?(context)
|
51
|
+
reader.skip_list_continuation
|
52
|
+
end
|
53
|
+
|
48
54
|
Asciidoctor.debug "/"*64
|
49
55
|
Asciidoctor.debug "#{File.basename(__FILE__)}:#{__LINE__} -> #{__method__} - First two lines are:"
|
50
56
|
Asciidoctor.debug reader.peek_line
|
@@ -56,8 +62,9 @@ class Asciidoctor::Lexer
|
|
56
62
|
block = nil
|
57
63
|
title = nil
|
58
64
|
caption = nil
|
59
|
-
source_type = nil
|
60
65
|
buffer = []
|
66
|
+
attributes = {}
|
67
|
+
context = parent.is_a?(Block) ? parent.context : nil
|
61
68
|
while reader.has_lines? && block.nil?
|
62
69
|
buffer.clear
|
63
70
|
this_line = reader.get_line
|
@@ -65,35 +72,36 @@ class Asciidoctor::Lexer
|
|
65
72
|
|
66
73
|
if this_line.match(REGEXP[:comment_blk])
|
67
74
|
Reader.new(reader.grab_lines_until {|line| line.match( REGEXP[:comment_blk] ) })
|
68
|
-
next
|
69
75
|
|
70
76
|
elsif this_line.match(REGEXP[:comment])
|
71
|
-
next
|
72
|
-
|
73
|
-
elsif match = this_line.match(REGEXP[:title])
|
74
|
-
title = match[1]
|
75
77
|
reader.skip_blank
|
76
78
|
|
77
|
-
elsif match = this_line.match(REGEXP[:
|
78
|
-
|
79
|
+
elsif match = this_line.match(REGEXP[:attr_list_blk])
|
80
|
+
collect_attributes(match[1], attributes)
|
79
81
|
reader.skip_blank
|
80
82
|
|
81
|
-
elsif match = this_line.match(REGEXP[:caption])
|
82
|
-
caption = match[1]
|
83
|
-
|
84
83
|
elsif is_section_heading?(this_line, next_line)
|
85
84
|
# If we've come to a new section, then we've found the end of this
|
86
85
|
# current block. Likewise if we'd found an unassigned anchor, push
|
87
|
-
#
|
88
|
-
#
|
89
|
-
# only match with double brackets - [[foo]], but what's stored in
|
90
|
-
# `anchor` at this point is only the `foo` part that was stripped out
|
91
|
-
# after matching. TODO: Need a way to test this.
|
86
|
+
#
|
87
|
+
# FIXME when slurping up next section, give back trailing anchor to following section
|
92
88
|
reader.unshift(this_line)
|
93
|
-
reader.unshift(anchor) unless anchor.nil?
|
94
89
|
Asciidoctor.debug "#{__method__}: SENDING to next_section with lines[0] = #{reader.peek_line}"
|
95
90
|
block = next_section(reader, parent)
|
96
91
|
|
92
|
+
elsif match = this_line.match(REGEXP[:title])
|
93
|
+
title = match[1]
|
94
|
+
reader.skip_blank
|
95
|
+
|
96
|
+
elsif match = this_line.match(REGEXP[:image_blk])
|
97
|
+
collect_attributes(match[2], attributes, ['alt', 'width', 'height'])
|
98
|
+
block = Block.new(parent, :image)
|
99
|
+
# FIXME this seems kind of one-off here
|
100
|
+
target = block.sub_attributes(match[1])
|
101
|
+
attributes['target'] = target
|
102
|
+
attributes['alt'] ||= File.basename(target, File.extname(target))
|
103
|
+
reader.skip_blank
|
104
|
+
|
97
105
|
elsif this_line.match(REGEXP[:oblock])
|
98
106
|
# oblock is surrounded by '--' lines and has zero or more blocks inside
|
99
107
|
buffer = Reader.new(reader.grab_lines_until { |line| line.match(REGEXP[:oblock]) })
|
@@ -109,12 +117,25 @@ class Asciidoctor::Lexer
|
|
109
117
|
block.blocks << new_block unless new_block.nil?
|
110
118
|
end
|
111
119
|
|
120
|
+
# needs to come before list detection
|
121
|
+
elsif this_line.match(REGEXP[:sidebar_blk])
|
122
|
+
# sidebar is surrounded by '****' (4 or more '*' chars) lines
|
123
|
+
# FIXME violates DRY because it's a duplication of quote parsing
|
124
|
+
block = Block.new(parent, :sidebar)
|
125
|
+
buffer = Reader.new(reader.grab_lines_until {|line| line.match( REGEXP[:sidebar_blk] ) })
|
126
|
+
|
127
|
+
while buffer.has_lines?
|
128
|
+
new_block = next_block(buffer, block)
|
129
|
+
block.blocks << new_block unless new_block.nil?
|
130
|
+
end
|
131
|
+
|
112
132
|
elsif list_type = [:olist, :colist].detect{|l| this_line.match( REGEXP[l] )}
|
113
133
|
items = []
|
114
134
|
Asciidoctor.debug "Creating block of type: #{list_type}"
|
115
135
|
block = Block.new(parent, list_type)
|
136
|
+
attributes['style'] ||= 'arabic'
|
116
137
|
while !this_line.nil? && match = this_line.match(REGEXP[list_type])
|
117
|
-
item = ListItem.new
|
138
|
+
item = ListItem.new(block)
|
118
139
|
|
119
140
|
reader.unshift match[2].lstrip.sub(/^\./, '\.')
|
120
141
|
item_segment = Reader.new(list_item_segment(reader, :alt_ending => REGEXP[list_type]))
|
@@ -123,11 +144,7 @@ class Asciidoctor::Lexer
|
|
123
144
|
item.blocks << new_block unless new_block.nil?
|
124
145
|
end
|
125
146
|
|
126
|
-
|
127
|
-
item.blocks.first.is_a?(Block) &&
|
128
|
-
(item.blocks.first.context == :paragraph || item.blocks.first.context == :literal)
|
129
|
-
item.content = item.blocks.shift.buffer.map{|l| l.strip}.join("\n")
|
130
|
-
end
|
147
|
+
item.fold_first
|
131
148
|
|
132
149
|
items << item
|
133
150
|
|
@@ -140,74 +157,87 @@ class Asciidoctor::Lexer
|
|
140
157
|
block.buffer = items
|
141
158
|
|
142
159
|
elsif match = this_line.match(REGEXP[:ulist])
|
143
|
-
|
144
160
|
reader.unshift(this_line)
|
145
161
|
block = build_ulist(reader, parent)
|
146
162
|
|
147
163
|
elsif match = this_line.match(REGEXP[:dlist])
|
164
|
+
# TODO build_dlist method?
|
148
165
|
pairs = []
|
149
166
|
block = Block.new(parent, :dlist)
|
167
|
+
# allows us to capture until we find a labeled item using the same delimiter (::, :::, :::: or ;;)
|
168
|
+
sibling_matcher = REGEXP[:dlist_siblings][match[3]]
|
150
169
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
dt = ListItem.new( $` + $' )
|
156
|
-
dt.anchor = anchor[1]
|
157
|
-
else
|
158
|
-
dt = ListItem.new( match[1] )
|
159
|
-
end
|
160
|
-
dd = ListItem.new
|
161
|
-
# workaround eg. git-config OPTIONS --get-colorbool
|
162
|
-
reader.get_line if reader.has_lines? && reader.peek_line.strip.empty?
|
170
|
+
begin
|
171
|
+
dt = ListItem.new(block, match[2])
|
172
|
+
dt.anchor = match[1] unless match[1].nil?
|
173
|
+
dd = ListItem.new(block, match[5])
|
163
174
|
|
164
|
-
dd_segment = Reader.new(list_item_segment(reader, :alt_ending =>
|
175
|
+
dd_segment = Reader.new(list_item_segment(reader, :alt_ending => sibling_matcher))
|
165
176
|
while dd_segment.has_lines?
|
166
177
|
new_block = next_block(dd_segment, block)
|
167
178
|
dd.blocks << new_block unless new_block.nil?
|
168
179
|
end
|
169
180
|
|
170
|
-
|
171
|
-
dd.blocks.first.is_a?(Block) &&
|
172
|
-
(dd.blocks.first.context == :paragraph || dd.blocks.first.context == :literal)
|
173
|
-
dd.content = dd.blocks.shift.buffer.map{|l| l.strip}.join("\n")
|
174
|
-
end
|
181
|
+
dd.fold_first
|
175
182
|
|
176
183
|
pairs << [dt, dd]
|
177
184
|
|
185
|
+
# this skip_blank might be redundant
|
178
186
|
reader.skip_blank
|
179
|
-
|
180
187
|
this_line = reader.get_line
|
181
|
-
end
|
188
|
+
end while !this_line.nil? && match = this_line.match(sibling_matcher)
|
189
|
+
|
182
190
|
reader.unshift(this_line) unless this_line.nil?
|
183
191
|
block.buffer = pairs
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
buffer = reader.grab_lines_until {|line| line.match( REGEXP[
|
197
|
-
block = Block.new(parent, block_type, buffer)
|
198
|
-
|
199
|
-
elsif this_line.match( REGEXP[:quote] )
|
200
|
-
block = Block.new(parent, :quote)
|
201
|
-
buffer = Reader.new(reader.grab_lines_until {|line| line.match( REGEXP[:quote] ) })
|
192
|
+
|
193
|
+
# FIXME violates DRY because it's a duplication of other block parsing
|
194
|
+
elsif this_line.match(REGEXP[:example])
|
195
|
+
# example is surrounded by lines with 4 or more '=' chars
|
196
|
+
rekey_positional_attributes(attributes, ['style'])
|
197
|
+
if admonition_style = ADMONITION_STYLES.detect {|s| attributes['style'] == s}
|
198
|
+
block = Block.new(parent, :admonition)
|
199
|
+
attributes['name'] = admonition_style.downcase
|
200
|
+
attributes['caption'] ||= admonition_style.capitalize
|
201
|
+
else
|
202
|
+
block = Block.new(parent, :example)
|
203
|
+
end
|
204
|
+
buffer = Reader.new(reader.grab_lines_until {|line| line.match( REGEXP[:example] ) })
|
202
205
|
|
203
206
|
while buffer.has_lines?
|
204
207
|
new_block = next_block(buffer, block)
|
205
208
|
block.blocks << new_block unless new_block.nil?
|
206
209
|
end
|
207
210
|
|
211
|
+
# FIXME violates DRY w/ non-delimited block listing
|
212
|
+
elsif this_line.match(REGEXP[:listing])
|
213
|
+
rekey_positional_attributes(attributes, ['style', 'language', 'linenums'])
|
214
|
+
buffer = reader.grab_lines_until {|line| line.match( REGEXP[:listing] )}
|
215
|
+
buffer.last.chomp! unless buffer.empty?
|
216
|
+
block = Block.new(parent, :listing, buffer)
|
217
|
+
|
218
|
+
elsif this_line.match(REGEXP[:quote])
|
219
|
+
# multi-line verse or quote is surrounded by a block delimiter
|
220
|
+
rekey_positional_attributes(attributes, ['style', 'attribution', 'citetitle'])
|
221
|
+
quote_context = (attributes['style'] == 'verse' ? :verse : :quote)
|
222
|
+
buffer = Reader.new(reader.grab_lines_until {|line| line.match( REGEXP[:quote] ) })
|
223
|
+
|
224
|
+
# only quote can have other section elements (as as section block)
|
225
|
+
section_body = (quote_context == :quote)
|
226
|
+
|
227
|
+
if section_body
|
228
|
+
block = Block.new(parent, quote_context)
|
229
|
+
while buffer.has_lines?
|
230
|
+
new_block = next_block(buffer, block)
|
231
|
+
block.blocks << new_block unless new_block.nil?
|
232
|
+
end
|
233
|
+
else
|
234
|
+
block = Block.new(parent, quote_context, buffer.lines)
|
235
|
+
end
|
236
|
+
|
208
237
|
elsif this_line.match(REGEXP[:lit_blk])
|
209
238
|
# example is surrounded by '....' (4 or more '.' chars) lines
|
210
239
|
buffer = reader.grab_lines_until {|line| line.match( REGEXP[:lit_blk] ) }
|
240
|
+
buffer.last.chomp! unless buffer.empty?
|
211
241
|
block = Block.new(parent, :literal, buffer)
|
212
242
|
|
213
243
|
elsif this_line.match(REGEXP[:lit_par])
|
@@ -216,41 +246,80 @@ class Asciidoctor::Lexer
|
|
216
246
|
|
217
247
|
# So we need to actually include this one in the grab_lines group
|
218
248
|
reader.unshift this_line
|
219
|
-
buffer = reader.grab_lines_until(:preserve_last_line => true) {|line|
|
249
|
+
buffer = reader.grab_lines_until(:preserve_last_line => true) {|line|
|
250
|
+
(context == :dlist && line.match(REGEXP[:dlist])) || !line.match(REGEXP[:lit_par])
|
251
|
+
}
|
252
|
+
|
253
|
+
# trim off the indentation that put us in this literal paragraph
|
254
|
+
if !buffer.empty? && match = buffer.first.match(/^([[:blank:]]+)/)
|
255
|
+
offset = match[1].length
|
256
|
+
buffer = buffer.map {|l| l.slice(offset..-1)}
|
257
|
+
buffer.last.chomp!
|
258
|
+
end
|
220
259
|
|
221
260
|
block = Block.new(parent, :literal, buffer)
|
222
261
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
262
|
+
## these switches based on style need to come immediately before the else ##
|
263
|
+
|
264
|
+
elsif attributes[0] == 'source'
|
265
|
+
rekey_positional_attributes(attributes, ['style', 'language', 'linenums'])
|
266
|
+
reader.unshift(this_line)
|
267
|
+
buffer = reader.grab_lines_until(:break_on_blank_lines => true)
|
268
|
+
buffer.last.chomp! unless buffer.empty?
|
269
|
+
block = Block.new(parent, :listing, buffer)
|
270
|
+
|
271
|
+
elsif admonition_style = ADMONITION_STYLES.detect{|s| attributes[0] == s}
|
272
|
+
# an admonition preceded by [*TYPE*] and lasts until a blank line
|
273
|
+
reader.unshift(this_line)
|
274
|
+
buffer = reader.grab_lines_until(:break_on_blank_lines => true)
|
275
|
+
block = Block.new(parent, :admonition, buffer)
|
276
|
+
attributes['style'] = admonition_style
|
277
|
+
attributes['name'] = admonition_style.downcase
|
278
|
+
attributes['caption'] ||= admonition_style.capitalize
|
279
|
+
|
280
|
+
elsif quote_context = [:quote, :verse].detect{|s| attributes[0] == s.to_s}
|
281
|
+
# single-paragraph verse or quote is preceded by [verse] or [quote], respectively, and lasts until a blank line
|
282
|
+
rekey_positional_attributes(attributes, ['style', 'attribution', 'citetitle'])
|
283
|
+
reader.unshift(this_line)
|
284
|
+
buffer = reader.grab_lines_until(:break_on_blank_lines => true)
|
285
|
+
block = Block.new(parent, quote_context, buffer)
|
227
286
|
|
228
287
|
else
|
229
288
|
# paragraph is contiguous nonblank/noncontinuation lines
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
289
|
+
reader.unshift this_line
|
290
|
+
buffer = reader.grab_lines_until(:break_on_blank_lines => true, :preserve_last_line => true) {|line|
|
291
|
+
(context == :dlist && line.match(REGEXP[:dlist])) ||
|
292
|
+
([:ulist, :olist, :dlist].include?(context) && line.chomp == LIST_CONTINUATION) ||
|
293
|
+
line.match(REGEXP[:oblock])
|
294
|
+
}
|
295
|
+
|
296
|
+
if LIST_CONTEXTS.include?(context)
|
297
|
+
reader.skip_list_continuation
|
237
298
|
end
|
238
299
|
|
239
|
-
if buffer.
|
300
|
+
if !buffer.empty? && admonition = buffer.first.match(Regexp.new('^(' + ADMONITION_STYLES.join('|') + '):\s+'))
|
240
301
|
buffer[0] = admonition.post_match
|
241
|
-
block = Block.new(parent, :
|
242
|
-
|
243
|
-
|
302
|
+
block = Block.new(parent, :admonition, buffer)
|
303
|
+
attributes['style'] = admonition[1]
|
304
|
+
attributes['name'] = admonition[1].downcase
|
305
|
+
attributes['caption'] ||= admonition[1].capitalize
|
244
306
|
else
|
307
|
+
buffer.last.chomp! unless buffer.empty?
|
245
308
|
Asciidoctor.debug "Proud parent #{parent} getting a new paragraph with buffer: #{buffer}"
|
246
309
|
block = Block.new(parent, :paragraph, buffer)
|
247
310
|
end
|
248
311
|
end
|
249
312
|
end
|
250
313
|
|
251
|
-
|
252
|
-
block
|
253
|
-
|
314
|
+
# when looking for nested content, a series of
|
315
|
+
# line comments or a comment block could leave us
|
316
|
+
# without a block
|
317
|
+
if !block.nil?
|
318
|
+
block.anchor ||= (anchor || attributes['id'])
|
319
|
+
block.title ||= title
|
320
|
+
block.caption ||= caption
|
321
|
+
block.update_attributes(attributes)
|
322
|
+
end
|
254
323
|
|
255
324
|
block
|
256
325
|
end
|
@@ -352,7 +421,7 @@ class Asciidoctor::Lexer
|
|
352
421
|
|
353
422
|
Asciidoctor.debug "*"*40
|
354
423
|
Asciidoctor.debug "#{File.basename(__FILE__)}:#{__LINE__} -> #{__method__}: Returning this:"
|
355
|
-
Asciidoctor.debug segment.inspect
|
424
|
+
#Asciidoctor.debug segment.inspect
|
356
425
|
Asciidoctor.debug "*"*10
|
357
426
|
Asciidoctor.debug "Leaving #{__method__}: Top of reader queue is:"
|
358
427
|
Asciidoctor.debug reader.peek_line
|
@@ -383,11 +452,12 @@ class Asciidoctor::Lexer
|
|
383
452
|
|
384
453
|
level = match[1].length
|
385
454
|
|
386
|
-
list_item = ListItem.new
|
455
|
+
list_item = ListItem.new(block)
|
387
456
|
list_item.level = level
|
388
457
|
Asciidoctor.debug "#{__FILE__}:#{__LINE__}: Created ListItem #{list_item} with match[2]: #{match[2]} and level: #{list_item.level}"
|
389
458
|
|
390
|
-
#
|
459
|
+
# Restore first line of list item
|
460
|
+
# Also prevent bullet list text starting with . from being treated as a paragraph
|
391
461
|
# title or some other unseemly thing in list_item_segment. I think. (NOTE)
|
392
462
|
reader.unshift match[2].lstrip.sub(/^\./, '\.')
|
393
463
|
|
@@ -400,12 +470,7 @@ class Asciidoctor::Lexer
|
|
400
470
|
|
401
471
|
Asciidoctor.debug "\n\nlist_item has #{list_item.blocks.count} blocks, and first is a #{list_item.blocks.first.class} with context #{list_item.blocks.first.context rescue 'n/a'}\n\n"
|
402
472
|
|
403
|
-
|
404
|
-
if first_block.is_a?(Block) &&
|
405
|
-
(first_block.context == :paragraph || first_block.context == :literal)
|
406
|
-
list_item.content = first_block.buffer.map{|l| l.strip}.join("\n")
|
407
|
-
list_item.blocks.shift
|
408
|
-
end
|
473
|
+
list_item.fold_first
|
409
474
|
|
410
475
|
list_item
|
411
476
|
end
|
@@ -423,15 +488,18 @@ class Asciidoctor::Lexer
|
|
423
488
|
|
424
489
|
if first_item_level && first_item_level < this_item_level
|
425
490
|
# If this next :uline level is down one from the
|
426
|
-
# current Block's,
|
427
|
-
|
491
|
+
# current Block's, append it to content of the current list item
|
492
|
+
items.last.blocks << next_block(reader, block)
|
493
|
+
elsif first_item_level && first_item_level > this_item_level
|
494
|
+
break
|
428
495
|
else
|
429
496
|
list_item = build_ulist_item(reader, block, match)
|
430
497
|
# Set the base item level for this Block
|
431
498
|
first_item_level ||= list_item.level
|
432
499
|
end
|
433
500
|
|
434
|
-
items << list_item
|
501
|
+
items << list_item unless list_item.nil?
|
502
|
+
list_item = nil
|
435
503
|
|
436
504
|
reader.skip_blank
|
437
505
|
end
|
@@ -451,7 +519,7 @@ class Asciidoctor::Lexer
|
|
451
519
|
while this_line && match = this_line.match(REGEXP[list_type])
|
452
520
|
level = match[1].length
|
453
521
|
|
454
|
-
list_item = ListItem.new
|
522
|
+
list_item = ListItem.new(block)
|
455
523
|
list_item.level = level
|
456
524
|
Asciidoctor.debug "Created ListItem #{list_item} with match[2]: #{match[2]} and level: #{list_item.level}"
|
457
525
|
|
@@ -462,12 +530,7 @@ class Asciidoctor::Lexer
|
|
462
530
|
list_item.blocks << new_block unless new_block.nil?
|
463
531
|
end
|
464
532
|
|
465
|
-
|
466
|
-
if first_block.is_a?(Block) &&
|
467
|
-
(first_block.context == :paragraph || first_block.context == :literal)
|
468
|
-
list_item.content = first_block.buffer.map{|l| l.strip}.join("\n")
|
469
|
-
list_item.blocks.shift
|
470
|
-
end
|
533
|
+
list_item.fold_first
|
471
534
|
|
472
535
|
if items.any? && (level > items.last.level)
|
473
536
|
Asciidoctor.debug "--> Putting this new level #{level} ListItem under my pops, #{items.last} (level: #{items.last.level})"
|
@@ -490,6 +553,32 @@ class Asciidoctor::Lexer
|
|
490
553
|
block
|
491
554
|
end
|
492
555
|
|
556
|
+
def self.collect_attributes(attrs, attributes, posattrs = [])
|
557
|
+
# TODO walk be properly rather than using split
|
558
|
+
attrs.split(/\s*,\s*/).each_with_index do |entry, i|
|
559
|
+
key, val = entry.split(/\s*=\s*/)
|
560
|
+
if !val.nil?
|
561
|
+
val.gsub!(/^(['"])(.*)\1$/, '\2') unless val.nil?
|
562
|
+
attributes[key] = val
|
563
|
+
else
|
564
|
+
attributes[i] = key
|
565
|
+
# positional attribute has a known key
|
566
|
+
if posattrs.size >= (i + 1)
|
567
|
+
attributes[posattrs[i]] = key
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
def self.rekey_positional_attributes(attributes, posattrs)
|
574
|
+
posattrs.each_with_index do |key, i|
|
575
|
+
val = attributes[i]
|
576
|
+
if !val.nil?
|
577
|
+
attributes[key] = val
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
493
582
|
# Private: Get the Integer section level based on the characters
|
494
583
|
# used in the ASCII line under the section name.
|
495
584
|
#
|
@@ -517,7 +606,8 @@ class Asciidoctor::Lexer
|
|
517
606
|
def self.is_two_line_section_heading?(line1, line2)
|
518
607
|
!line1.nil? && !line2.nil? &&
|
519
608
|
line1.match(REGEXP[:name]) && line2.match(REGEXP[:line]) &&
|
520
|
-
(
|
609
|
+
# chomp so that a (non-visible) endline does not impact calculation
|
610
|
+
(line1.chomp.size - line2.chomp.size).abs <= 1
|
521
611
|
end
|
522
612
|
|
523
613
|
def self.is_section_heading?(line1, line2 = nil)
|
@@ -647,12 +737,6 @@ class Asciidoctor::Lexer
|
|
647
737
|
section_lines << this_line
|
648
738
|
section_lines << reader.get_line unless is_single_line_section_heading?(this_line)
|
649
739
|
end
|
650
|
-
elsif this_line.match(REGEXP[:listing])
|
651
|
-
section_lines << this_line
|
652
|
-
section_lines.concat reader.grab_lines_until {|line| line.match( REGEXP[:listing] ) }
|
653
|
-
# Also grab the last line, if there is one
|
654
|
-
this_line = reader.get_line
|
655
|
-
section_lines << this_line unless this_line.nil?
|
656
740
|
else
|
657
741
|
section_lines << this_line
|
658
742
|
end
|
@@ -669,6 +753,19 @@ class Asciidoctor::Lexer
|
|
669
753
|
end
|
670
754
|
end
|
671
755
|
|
756
|
+
# detect preamble and push it into a block
|
757
|
+
# QUESTION make this an operation on Section?
|
758
|
+
if section.level == 0
|
759
|
+
blocks = section.blocks.take_while {|b| !b.is_a? Section}
|
760
|
+
if !blocks.empty?
|
761
|
+
# QUESTION Should we propagate the buffer?
|
762
|
+
#preamble = Block.new(section, :preamble, blocks.reduce {|a, b| a.buffer + b.buffer})
|
763
|
+
preamble = Block.new(section, :preamble)
|
764
|
+
blocks.each { preamble << section.delete_at(0) }
|
765
|
+
section.insert(0, preamble)
|
766
|
+
end
|
767
|
+
end
|
768
|
+
|
672
769
|
section
|
673
770
|
end
|
674
771
|
|