asciidoctor 0.0.7 → 0.0.9
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/Gemfile +2 -0
- data/README.asciidoc +35 -26
- data/Rakefile +9 -6
- data/asciidoctor.gemspec +27 -8
- data/bin/asciidoctor +1 -1
- data/lib/asciidoctor.rb +351 -63
- data/lib/asciidoctor/abstract_block.rb +218 -0
- data/lib/asciidoctor/abstract_node.rb +249 -0
- data/lib/asciidoctor/attribute_list.rb +211 -0
- data/lib/asciidoctor/backends/base_template.rb +99 -0
- data/lib/asciidoctor/backends/docbook45.rb +510 -0
- data/lib/asciidoctor/backends/html5.rb +585 -0
- data/lib/asciidoctor/block.rb +27 -254
- data/lib/asciidoctor/callouts.rb +117 -0
- data/lib/asciidoctor/debug.rb +7 -4
- data/lib/asciidoctor/document.rb +229 -77
- data/lib/asciidoctor/inline.rb +29 -0
- data/lib/asciidoctor/lexer.rb +1330 -502
- data/lib/asciidoctor/list_item.rb +33 -34
- data/lib/asciidoctor/reader.rb +305 -142
- data/lib/asciidoctor/renderer.rb +115 -19
- data/lib/asciidoctor/section.rb +100 -189
- data/lib/asciidoctor/substituters.rb +468 -0
- data/lib/asciidoctor/table.rb +499 -0
- data/lib/asciidoctor/version.rb +1 -1
- data/test/attributes_test.rb +301 -87
- data/test/blocks_test.rb +568 -0
- data/test/document_test.rb +221 -24
- data/test/fixtures/dot.gif +0 -0
- data/test/fixtures/encoding.asciidoc +1 -0
- data/test/fixtures/include-file.asciidoc +1 -0
- data/test/fixtures/tip.gif +0 -0
- data/test/headers_test.rb +411 -43
- data/test/lexer_test.rb +265 -45
- data/test/links_test.rb +144 -3
- data/test/lists_test.rb +2252 -74
- data/test/paragraphs_test.rb +21 -30
- data/test/preamble_test.rb +24 -0
- data/test/reader_test.rb +248 -12
- data/test/renderer_test.rb +22 -0
- data/test/substitutions_test.rb +414 -0
- data/test/tables_test.rb +484 -0
- data/test/test_helper.rb +70 -6
- data/test/text_test.rb +30 -6
- metadata +64 -10
- data/lib/asciidoctor/render_templates.rb +0 -317
- data/lib/asciidoctor/string.rb +0 -12
@@ -1,26 +1,21 @@
|
|
1
1
|
# Public: Methods for managing items for AsciiDoc olists, ulist, and dlists.
|
2
|
-
class Asciidoctor::ListItem
|
3
|
-
# Public: Get the Array of Blocks from the list item's continuation.
|
4
|
-
attr_reader :blocks
|
2
|
+
class Asciidoctor::ListItem < Asciidoctor::AbstractBlock
|
5
3
|
|
6
|
-
# Public: Get/Set the String list item
|
7
|
-
attr_accessor :
|
8
|
-
|
9
|
-
# Public: Get/Set the Integer list level (for nesting list elements).
|
10
|
-
attr_accessor :level
|
4
|
+
# Public: Get/Set the String used to mark this list item
|
5
|
+
attr_accessor :marker
|
11
6
|
|
12
7
|
# Public: Initialize an Asciidoctor::ListItem object.
|
13
8
|
#
|
14
9
|
# parent - The parent list block for this list item
|
15
|
-
# text - the String text (default
|
16
|
-
def initialize(parent, text=
|
17
|
-
|
10
|
+
# text - the String text (default nil)
|
11
|
+
def initialize(parent, text = nil)
|
12
|
+
super(parent, :list_item)
|
18
13
|
@text = text
|
19
|
-
@
|
14
|
+
@level = parent.level
|
20
15
|
end
|
21
16
|
|
22
|
-
def text
|
23
|
-
|
17
|
+
def text?
|
18
|
+
!@text.to_s.empty?
|
24
19
|
end
|
25
20
|
|
26
21
|
def text
|
@@ -28,35 +23,35 @@ class Asciidoctor::ListItem
|
|
28
23
|
::Asciidoctor::Block.new(self, nil, [@text]).content
|
29
24
|
end
|
30
25
|
|
31
|
-
def document
|
32
|
-
@parent.document
|
33
|
-
end
|
34
|
-
|
35
26
|
def content
|
36
|
-
|
37
|
-
if !blocks.empty?
|
38
|
-
blocks.map{|block| block.render}.join
|
39
|
-
else
|
40
|
-
nil
|
41
|
-
end
|
27
|
+
blocks? ? blocks.map {|b| b.render }.join : nil
|
42
28
|
end
|
43
29
|
|
44
30
|
# Public: Fold the first paragraph block into the text
|
45
|
-
|
46
|
-
|
31
|
+
#
|
32
|
+
# Here are the rules for when a folding occurs:
|
33
|
+
#
|
34
|
+
# Given: this list item has at least one block
|
35
|
+
# When: the first block is a paragraph that's not connected by a list continuation
|
36
|
+
# Or: the first block is an indented paragraph that's adjacent (wrapped line)
|
37
|
+
# Or: the first block is an indented paragraph that's not connected by a list continuation
|
38
|
+
# Then: then drop the first block and fold it's content (buffer) into the list text
|
39
|
+
#
|
40
|
+
# Returns nothing
|
41
|
+
def fold_first(continuation_connects_first_block = false, content_adjacent = false)
|
47
42
|
if !blocks.empty? && blocks.first.is_a?(Asciidoctor::Block) &&
|
48
|
-
(blocks.first.context == :paragraph
|
43
|
+
((blocks.first.context == :paragraph && !continuation_connects_first_block) ||
|
44
|
+
((content_adjacent || !continuation_connects_first_block) && blocks.first.context == :literal &&
|
45
|
+
blocks.first.attr('options', []).include?('listparagraph')))
|
46
|
+
|
49
47
|
block = blocks.shift
|
50
|
-
|
48
|
+
unless @text.to_s.empty?
|
51
49
|
block.buffer.unshift(@text)
|
52
50
|
end
|
53
51
|
|
54
|
-
|
55
|
-
@text = block.buffer.map {|l| l.lstrip}.join("\n")
|
56
|
-
else
|
57
|
-
@text = block.buffer.join("\n")
|
58
|
-
end
|
52
|
+
@text = block.buffer.join("\n")
|
59
53
|
end
|
54
|
+
nil
|
60
55
|
end
|
61
56
|
|
62
57
|
def splain(parent_level = 0)
|
@@ -71,7 +66,7 @@ class Asciidoctor::ListItem
|
|
71
66
|
@blocks.each_with_index do |block, i|
|
72
67
|
Asciidoctor.puts_indented(parent_level, "v" * (60 - parent_level*2))
|
73
68
|
Asciidoctor.puts_indented(parent_level, "Block ##{i} is a #{block.class}")
|
74
|
-
Asciidoctor.puts_indented(parent_level, "Name is #{block.
|
69
|
+
Asciidoctor.puts_indented(parent_level, "Name is #{block.title rescue 'n/a'}")
|
75
70
|
Asciidoctor.puts_indented(parent_level, "=" * 40)
|
76
71
|
block.splain(parent_level) if block.respond_to? :splain
|
77
72
|
Asciidoctor.puts_indented(parent_level, "^" * (60 - parent_level*2))
|
@@ -79,4 +74,8 @@ class Asciidoctor::ListItem
|
|
79
74
|
end
|
80
75
|
nil
|
81
76
|
end
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
"#{super.to_s} - #@context [text:#@text, blocks:#{(@blocks || []).size}]"
|
80
|
+
end
|
82
81
|
end
|
data/lib/asciidoctor/reader.rb
CHANGED
@@ -9,155 +9,55 @@ class Asciidoctor::Reader
|
|
9
9
|
# Public: Get the String Array of lines parsed from the source
|
10
10
|
attr_reader :lines
|
11
11
|
|
12
|
-
# Public: Get the Hash of attributes
|
13
|
-
attr_reader :attributes
|
14
|
-
|
15
|
-
attr_reader :references
|
16
|
-
|
17
|
-
# Public: Convert a string to a legal attribute name.
|
18
|
-
#
|
19
|
-
# name - The String holding the Asciidoc attribute name.
|
20
|
-
#
|
21
|
-
# Returns a String with the legal name.
|
22
|
-
#
|
23
|
-
# Examples
|
24
|
-
#
|
25
|
-
# sanitize_attribute_name('Foo Bar')
|
26
|
-
# => 'foobar'
|
27
|
-
#
|
28
|
-
# sanitize_attribute_name('foo')
|
29
|
-
# => 'foo'
|
30
|
-
#
|
31
|
-
# sanitize_attribute_name('Foo 3 #-Billy')
|
32
|
-
# => 'foo3-billy'
|
33
|
-
def sanitize_attribute_name(name)
|
34
|
-
name.gsub(/[^\w\-_]/, '').downcase
|
35
|
-
end
|
36
|
-
|
37
12
|
# Public: Initialize the Reader object.
|
38
13
|
#
|
39
|
-
# data
|
40
|
-
#
|
41
|
-
#
|
14
|
+
# data - The Array of Strings holding the Asciidoc source document. The
|
15
|
+
# original instance of this Array is not modified
|
16
|
+
# document - The document with which this reader is associated. Used to access
|
17
|
+
# document attributes
|
18
|
+
# overrides - A Hash of attributes that were passed to the Document and should
|
19
|
+
# prevent attribute assignments or removals of matching keys found in
|
20
|
+
# the document
|
21
|
+
# block - A block that can be used to retrieve external Asciidoc
|
22
|
+
# data to include in this document.
|
42
23
|
#
|
43
24
|
# Examples
|
44
25
|
#
|
45
26
|
# data = File.readlines(filename)
|
46
|
-
# reader = Asciidoctor::Reader.new
|
47
|
-
def initialize(data = [],
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
if inc = line.match(include_regexp)
|
58
|
-
if block_given?
|
59
|
-
raw_source << yield(inc[1])
|
60
|
-
else
|
61
|
-
raw_source.concat(File.readlines(inc[1]))
|
62
|
-
end
|
63
|
-
else
|
64
|
-
raw_source << line
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
ifdef_regexp = /^(ifdef|ifndef)::([^\[]+)\[\]/
|
69
|
-
endif_regexp = /^endif::/
|
70
|
-
defattr_regexp = /^:([^:!]+):\s*(.*)\s*$/
|
71
|
-
delete_attr_regexp = /^:([^:]+)!:\s*$/
|
72
|
-
conditional_regexp = /^\s*\{([^\?]+)\?\s*([^\}]+)\s*\}/
|
73
|
-
|
74
|
-
skip_to = nil
|
75
|
-
continuing_value = nil
|
76
|
-
continuing_key = nil
|
77
|
-
@lines = []
|
78
|
-
raw_source.each do |line|
|
79
|
-
if skip_to
|
80
|
-
skip_to = nil if line.match(skip_to)
|
81
|
-
elsif continuing_value
|
82
|
-
close_continue = false
|
83
|
-
# Lines that start with whitespace and end with a '+' are
|
84
|
-
# a continuation, so gobble them up into `value`
|
85
|
-
if match = line.match(/\s+(.+)\s+\+\s*$/)
|
86
|
-
continuing_value += ' ' + match[1]
|
87
|
-
elsif match = line.match(/\s+(.+)/)
|
88
|
-
# If this continued line doesn't end with a +, then this
|
89
|
-
# is the end of the continuation, no matter what the next
|
90
|
-
# line does.
|
91
|
-
continuing_value += ' ' + match[1]
|
92
|
-
close_continue = true
|
93
|
-
else
|
94
|
-
# If this line doesn't start with whitespace, then it's
|
95
|
-
# not a valid continuation line, so push it back for processing
|
96
|
-
close_continue = true
|
97
|
-
raw_source.unshift(line)
|
98
|
-
end
|
99
|
-
if close_continue
|
100
|
-
@attributes[continuing_key] = continuing_value
|
101
|
-
continuing_key = nil
|
102
|
-
continuing_value = nil
|
103
|
-
end
|
104
|
-
elsif match = line.match(ifdef_regexp)
|
105
|
-
attr = match[2]
|
106
|
-
skip = case match[1]
|
107
|
-
when 'ifdef'; !@attributes.has_key?(attr)
|
108
|
-
when 'ifndef'; @attributes.has_key?(attr)
|
109
|
-
end
|
110
|
-
skip_to = /^endif::#{attr}\[\]\s*\n/ if skip
|
111
|
-
elsif match = line.match(defattr_regexp)
|
112
|
-
key = sanitize_attribute_name(match[1])
|
113
|
-
value = match[2]
|
114
|
-
if match = value.match(Asciidoctor::REGEXP[:attr_continue])
|
115
|
-
# attribute value continuation line; grab lines until we run out
|
116
|
-
# of continuation lines
|
117
|
-
continuing_key = key
|
118
|
-
continuing_value = match[1] # strip off the spaces and +
|
119
|
-
Asciidoctor.debug "continuing key: #{continuing_key} with partial value: '#{continuing_value}'"
|
120
|
-
else
|
121
|
-
@attributes[key] = value
|
122
|
-
Asciidoctor.debug "Defines[#{key}] is '#{value}'"
|
123
|
-
end
|
124
|
-
elsif match = line.match(delete_attr_regexp)
|
125
|
-
key = sanitize_attribute_name(match[1])
|
126
|
-
@attributes.delete(key)
|
127
|
-
elsif !line.match(endif_regexp)
|
128
|
-
while match = line.match(conditional_regexp)
|
129
|
-
value = @attributes.has_key?(match[1]) ? match[2] : ''
|
130
|
-
line.sub!(conditional_regexp, value)
|
131
|
-
end
|
132
|
-
# leave line comments in as they play a role in flow (such as a list divider)
|
133
|
-
@lines << line
|
134
|
-
end
|
27
|
+
# reader = Asciidoctor::Reader.new data
|
28
|
+
def initialize(data = [], document = nil, overrides = nil, &block)
|
29
|
+
# if document is nil, we assume this is a preprocessed string
|
30
|
+
if document.nil?
|
31
|
+
@lines = data.is_a?(String) ? data.lines.entries : data.dup
|
32
|
+
elsif !data.empty?
|
33
|
+
@overrides = overrides || {}
|
34
|
+
@document = document
|
35
|
+
process(data.is_a?(String) ? data.lines.entries : data, &block)
|
36
|
+
else
|
37
|
+
@lines = []
|
135
38
|
end
|
136
39
|
|
137
|
-
#
|
138
|
-
|
139
|
-
@lines.each do |line|
|
140
|
-
if biblio = line.match(REGEXP[:biblio])
|
141
|
-
@references[biblio[1]] = "[#{biblio[1]}]"
|
142
|
-
end
|
143
|
-
end
|
40
|
+
# just in case we got some nils floating at the end of our lines after reading a funky document
|
41
|
+
@lines.pop until @lines.empty? || !@lines.last.nil?
|
144
42
|
|
145
|
-
#Asciidoctor.debug "About to leave Reader#init, and references is #{@references.inspect}"
|
146
43
|
@source = @lines.join
|
147
|
-
Asciidoctor.debug "Leaving Reader#init, and I have #{@lines.count} lines"
|
148
|
-
Asciidoctor.debug "Also, has_lines? is #{self.has_lines?}"
|
149
44
|
end
|
150
45
|
|
151
46
|
# Public: Check whether there are any lines left to read.
|
152
47
|
#
|
153
|
-
# Returns true if
|
48
|
+
# Returns true if !@lines.empty? is true, or false otherwise.
|
154
49
|
def has_lines?
|
155
|
-
|
50
|
+
!@lines.empty?
|
156
51
|
end
|
157
52
|
|
158
|
-
#
|
53
|
+
# Public: Check whether this reader is empty (contains no lines)
|
159
54
|
#
|
160
|
-
# Returns
|
55
|
+
# Returns true if @lines.empty? is true, otherwise false.
|
56
|
+
def empty?
|
57
|
+
@lines.empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Private: Strip off leading blank lines in the Array of lines.
|
161
61
|
#
|
162
62
|
# Examples
|
163
63
|
#
|
@@ -165,23 +65,89 @@ class Asciidoctor::Reader
|
|
165
65
|
# => ["\n", "\t\n", "Foo\n", "Bar\n", "\n"]
|
166
66
|
#
|
167
67
|
# skip_blank
|
168
|
-
# =>
|
68
|
+
# => 2
|
169
69
|
#
|
170
70
|
# @lines
|
171
71
|
# => ["Foo\n", "Bar\n"]
|
72
|
+
#
|
73
|
+
# Returns an Integer of the number of lines skipped
|
172
74
|
def skip_blank
|
173
|
-
|
75
|
+
skipped = 0
|
76
|
+
while has_lines? && @lines.first.strip.empty?
|
174
77
|
@lines.shift
|
78
|
+
skipped += 1
|
175
79
|
end
|
176
80
|
|
177
|
-
|
81
|
+
skipped
|
82
|
+
end
|
83
|
+
# Create alias of skip_blank named skip_blank_lines for readability
|
84
|
+
# TODO likely want to drop the original method name
|
85
|
+
alias :skip_blank_lines :skip_blank
|
86
|
+
|
87
|
+
# Public: Consume consecutive lines containing line- or block-level comments.
|
88
|
+
#
|
89
|
+
# Returns the Array of lines that were consumed
|
90
|
+
#
|
91
|
+
# Examples
|
92
|
+
# @lines
|
93
|
+
# => ["// foo\n", "////\n", "foo bar\n", "////\n", "actual text\n"]
|
94
|
+
#
|
95
|
+
# comment_lines = consume_comments
|
96
|
+
# => ["// foo\n", "////\n", "foo bar\n", "////\n"]
|
97
|
+
#
|
98
|
+
# @lines
|
99
|
+
# => ["actual text\n"]
|
100
|
+
def consume_comments(opts = {})
|
101
|
+
comment_lines = []
|
102
|
+
while !@lines.empty?
|
103
|
+
next_line = peek_line
|
104
|
+
if opts[:include_blanks] && next_line.strip.empty?
|
105
|
+
comment_lines << get_line
|
106
|
+
elsif match = next_line.match(REGEXP[:comment_blk])
|
107
|
+
comment_lines << get_line
|
108
|
+
comment_lines.push(*(grab_lines_until(:terminator => match[0], :preserve_last_line => true)))
|
109
|
+
comment_lines << get_line
|
110
|
+
elsif next_line.match(REGEXP[:comment])
|
111
|
+
comment_lines << get_line
|
112
|
+
else
|
113
|
+
break
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
comment_lines
|
118
|
+
end
|
119
|
+
|
120
|
+
# Public: Consume consecutive lines containing line comments.
|
121
|
+
#
|
122
|
+
# Returns the Array of lines that were consumed
|
123
|
+
#
|
124
|
+
# Examples
|
125
|
+
# @lines
|
126
|
+
# => ["// foo\n", "bar\n"]
|
127
|
+
#
|
128
|
+
# comment_lines = consume_comments
|
129
|
+
# => ["// foo\n"]
|
130
|
+
#
|
131
|
+
# @lines
|
132
|
+
# => ["bar\n"]
|
133
|
+
def consume_line_comments
|
134
|
+
comment_lines = []
|
135
|
+
while !@lines.empty?
|
136
|
+
if peek_line.match(REGEXP[:comment])
|
137
|
+
comment_lines << get_line
|
138
|
+
else
|
139
|
+
break
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
comment_lines
|
178
144
|
end
|
179
145
|
|
180
146
|
# Skip the next line if it's a list continuation character
|
181
147
|
#
|
182
148
|
# Returns nil
|
183
149
|
def skip_list_continuation
|
184
|
-
if
|
150
|
+
if has_lines? && @lines.first.chomp == '+'
|
185
151
|
@lines.shift
|
186
152
|
end
|
187
153
|
|
@@ -195,6 +161,9 @@ class Asciidoctor::Reader
|
|
195
161
|
def get_line
|
196
162
|
@lines.shift
|
197
163
|
end
|
164
|
+
# QUESTION what about advance?
|
165
|
+
# Create an alias of get_line named next_line for readability
|
166
|
+
alias :next_line :get_line
|
198
167
|
|
199
168
|
# Public: Get the next line of source data. Does not consume the line returned.
|
200
169
|
#
|
@@ -204,11 +173,21 @@ class Asciidoctor::Reader
|
|
204
173
|
@lines.first.dup if @lines.first
|
205
174
|
end
|
206
175
|
|
207
|
-
# Public: Push
|
176
|
+
# Public: Push Array of string `lines` onto queue of source data lines, unless `lines` has no non-nil values.
|
208
177
|
#
|
209
178
|
# Returns nil
|
210
|
-
def unshift(
|
211
|
-
@lines.unshift(
|
179
|
+
def unshift(*new_lines)
|
180
|
+
@lines.unshift(*new_lines) if !new_lines.empty?
|
181
|
+
nil
|
182
|
+
end
|
183
|
+
|
184
|
+
# Public: Chomp the String on the last line if this reader contains at least one line
|
185
|
+
#
|
186
|
+
# Delegates to chomp!
|
187
|
+
#
|
188
|
+
# Returns nil
|
189
|
+
def chomp_last!
|
190
|
+
@lines.last.chomp! unless @lines.empty?
|
212
191
|
nil
|
213
192
|
end
|
214
193
|
|
@@ -219,9 +198,14 @@ class Asciidoctor::Reader
|
|
219
198
|
# options - an optional Hash of processing options:
|
220
199
|
# * :break_on_blank_lines may be used to specify to break on
|
221
200
|
# blank lines
|
201
|
+
# * :skip_first_line may be used to tell the reader to advance
|
202
|
+
# beyond the first line before beginning the scan
|
222
203
|
# * :preserve_last_line may be used to specify that the String
|
223
204
|
# causing the method to stop processing lines should be
|
224
205
|
# pushed back onto the `lines` Array.
|
206
|
+
# * :grab_last_line may be used to specify that the String
|
207
|
+
# causing the method to stop processing lines should be
|
208
|
+
# included in the lines being returned
|
225
209
|
#
|
226
210
|
# Returns the Array of lines forming the next segment.
|
227
211
|
#
|
@@ -236,18 +220,197 @@ class Asciidoctor::Reader
|
|
236
220
|
def grab_lines_until(options = {}, &block)
|
237
221
|
buffer = []
|
238
222
|
|
223
|
+
finis = false
|
224
|
+
get_line if options[:skip_first_line]
|
225
|
+
# save options to locals for minor optimization
|
226
|
+
terminator = options[:terminator]
|
227
|
+
terminator.chomp! if terminator
|
228
|
+
break_on_blank_lines = options[:break_on_blank_lines]
|
229
|
+
break_on_list_continuation = options[:break_on_list_continuation]
|
239
230
|
while (this_line = self.get_line)
|
240
|
-
Asciidoctor.debug "
|
241
|
-
finis
|
242
|
-
finis
|
231
|
+
Asciidoctor.debug { "Reader processing line: '#{this_line}'" }
|
232
|
+
finis = true if terminator && this_line.chomp == terminator
|
233
|
+
finis = true if !finis && break_on_blank_lines && this_line.strip.empty?
|
234
|
+
finis = true if !finis && break_on_list_continuation && this_line.chomp == LIST_CONTINUATION
|
235
|
+
finis = true if !finis && block && yield(this_line)
|
243
236
|
if finis
|
244
237
|
self.unshift(this_line) if options[:preserve_last_line]
|
238
|
+
buffer << this_line if options[:grab_last_line]
|
245
239
|
break
|
246
240
|
end
|
247
241
|
|
248
|
-
|
242
|
+
if options[:skip_line_comments] && this_line.match(REGEXP[:comment])
|
243
|
+
# skip it
|
244
|
+
else
|
245
|
+
buffer << this_line
|
246
|
+
end
|
249
247
|
end
|
248
|
+
|
250
249
|
buffer
|
251
250
|
end
|
252
251
|
|
252
|
+
# Public: Convert a string to a legal attribute name.
|
253
|
+
#
|
254
|
+
# name - The String holding the Asciidoc attribute name.
|
255
|
+
#
|
256
|
+
# Returns a String with the legal name.
|
257
|
+
#
|
258
|
+
# Examples
|
259
|
+
#
|
260
|
+
# sanitize_attribute_name('Foo Bar')
|
261
|
+
# => 'foobar'
|
262
|
+
#
|
263
|
+
# sanitize_attribute_name('foo')
|
264
|
+
# => 'foo'
|
265
|
+
#
|
266
|
+
# sanitize_attribute_name('Foo 3 #-Billy')
|
267
|
+
# => 'foo3-billy'
|
268
|
+
def sanitize_attribute_name(name)
|
269
|
+
name.gsub(/[^\w\-]/, '').downcase
|
270
|
+
end
|
271
|
+
|
272
|
+
# Private: Process raw input, used for the outermost reader.
|
273
|
+
def process(data, &block)
|
274
|
+
raw_source = []
|
275
|
+
include_depth = @document.attr('include-depth', 0).to_i
|
276
|
+
|
277
|
+
data.each do |line|
|
278
|
+
if inc = line.match(REGEXP[:include_macro])
|
279
|
+
if inc[0].start_with? '\\'
|
280
|
+
raw_source << line[1..-1]
|
281
|
+
# if running in SafeMode::SECURE or greater, don't process
|
282
|
+
# this directive (or should we swallow it?)
|
283
|
+
elsif @document.safe >= SafeMode::SECURE
|
284
|
+
raw_source << line
|
285
|
+
# assume that if a block is given, the developer wants
|
286
|
+
# to handle when and how to process the include, even
|
287
|
+
# if the include-depth attribute is 0
|
288
|
+
elsif block_given?
|
289
|
+
raw_source.concat yield(inc[1])
|
290
|
+
elsif include_depth > 0
|
291
|
+
raw_source.concat File.readlines(@document.normalize_asset_path(inc[1], 'include file'))
|
292
|
+
else
|
293
|
+
raw_source << line
|
294
|
+
end
|
295
|
+
else
|
296
|
+
raw_source << line
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
skip_to = nil
|
301
|
+
continuing_value = nil
|
302
|
+
continuing_key = nil
|
303
|
+
@lines = []
|
304
|
+
|
305
|
+
raw_source.each do |line|
|
306
|
+
if skip_to
|
307
|
+
skip_to = nil if line.match(skip_to)
|
308
|
+
elsif continuing_value
|
309
|
+
close_continue = false
|
310
|
+
# Lines that start with whitespace and end with a '+' are
|
311
|
+
# a continuation, so gobble them up into `value`
|
312
|
+
if line.match(REGEXP[:attr_continue])
|
313
|
+
continuing_value += ' ' + $1
|
314
|
+
# An empty line ends a continuation
|
315
|
+
elsif line.strip.empty?
|
316
|
+
raw_source.unshift(line)
|
317
|
+
close_continue = true
|
318
|
+
else
|
319
|
+
# If this continued line isn't empty and doesn't end with a +, then
|
320
|
+
# this is the end of the continuation, no matter what the next line
|
321
|
+
# does.
|
322
|
+
continuing_value += ' ' + line.strip
|
323
|
+
close_continue = true
|
324
|
+
end
|
325
|
+
if close_continue
|
326
|
+
unless attribute_overridden? continuing_key
|
327
|
+
@document.attributes[continuing_key] = apply_attribute_value_subs(continuing_value)
|
328
|
+
end
|
329
|
+
continuing_key = nil
|
330
|
+
continuing_value = nil
|
331
|
+
end
|
332
|
+
elsif line.match(REGEXP[:ifdef_macro])
|
333
|
+
attr = $2
|
334
|
+
skip = case $1
|
335
|
+
when 'ifdef'; !@document.attributes.has_key?(attr)
|
336
|
+
when 'ifndef'; @document.attributes.has_key?(attr)
|
337
|
+
end
|
338
|
+
skip_to = /^endif::#{attr}\[\]\s*\n/ if skip
|
339
|
+
elsif line.match(REGEXP[:attr_assign])
|
340
|
+
key = sanitize_attribute_name($1)
|
341
|
+
value = $2
|
342
|
+
if value.match(REGEXP[:attr_continue])
|
343
|
+
# attribute value continuation line; grab lines until we run out
|
344
|
+
# of continuation lines
|
345
|
+
continuing_key = key
|
346
|
+
continuing_value = $1 # strip off the spaces and +
|
347
|
+
else
|
348
|
+
unless attribute_overridden? key
|
349
|
+
@document.attributes[key] = apply_attribute_value_subs(value)
|
350
|
+
if key == 'backend'
|
351
|
+
@document.update_backend_attributes()
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
elsif line.match(REGEXP[:attr_delete])
|
356
|
+
key = sanitize_attribute_name($1)
|
357
|
+
unless attribute_overridden? key
|
358
|
+
@document.attributes.delete(key)
|
359
|
+
end
|
360
|
+
elsif !line.match(REGEXP[:endif_macro])
|
361
|
+
while line.match(REGEXP[:attr_conditional])
|
362
|
+
value = @document.attributes.has_key?($1) ? $2 : ''
|
363
|
+
line.sub!(conditional_regexp, value)
|
364
|
+
end
|
365
|
+
# leave line comments in as they play a role in flow (such as a list divider)
|
366
|
+
@lines << line
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Process bibliography references, so they're available when text
|
371
|
+
# before the reference is being rendered.
|
372
|
+
# FIXME we don't have support for bibliography lists yet, so disable for now
|
373
|
+
# plus, this should be done while we are walking lines above
|
374
|
+
#@lines.each do |line|
|
375
|
+
# if biblio = line.match(REGEXP[:biblio])
|
376
|
+
# @document.register(:ids, biblio[1])
|
377
|
+
# end
|
378
|
+
#end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Internal: Determine if the attribute has been overridden in the document options
|
382
|
+
#
|
383
|
+
# key - The attribute key to check
|
384
|
+
#
|
385
|
+
# Returns true if the attribute has been overridden, false otherwise
|
386
|
+
def attribute_overridden?(key)
|
387
|
+
@overrides.has_key?(key) || @overrides.has_key?(key + '!')
|
388
|
+
end
|
389
|
+
|
390
|
+
# Internal: Apply substitutions to the attribute value
|
391
|
+
#
|
392
|
+
# If the value is an inline passthrough macro (e.g., pass:[text]), then
|
393
|
+
# apply the substitutions defined on the macro to the text. Otherwise,
|
394
|
+
# apply the verbatim substitutions to the value.
|
395
|
+
#
|
396
|
+
# value - The String attribute value on which to perform substitutions
|
397
|
+
#
|
398
|
+
# Returns The String value with substitutions performed.
|
399
|
+
def apply_attribute_value_subs(value)
|
400
|
+
if value.match(REGEXP[:pass_macro_basic])
|
401
|
+
# copy match for Ruby 1.8.7 compat
|
402
|
+
m = $~
|
403
|
+
subs = []
|
404
|
+
if !m[1].empty?
|
405
|
+
subs = @document.resolve_subs(m[1])
|
406
|
+
end
|
407
|
+
if !subs.empty?
|
408
|
+
@document.apply_subs(m[2], subs)
|
409
|
+
else
|
410
|
+
m[2]
|
411
|
+
end
|
412
|
+
else
|
413
|
+
@document.apply_header_subs(value)
|
414
|
+
end
|
415
|
+
end
|
253
416
|
end
|