lib/asciidoctor/substituters.rb in asciidoctor-0.1.2 vs lib/asciidoctor/substituters.rb in asciidoctor-0.1.3
- old
+ new
@@ -38,12 +38,13 @@
return lines if subs.empty?
multiline = lines.is_a?(Array)
text = multiline ? lines.join : lines
- passthroughs = subs.include?(:macros)
- text = extract_passthroughs(text) if passthroughs
+ if (has_passthroughs = subs.include?(:macros))
+ text = extract_passthroughs(text)
+ end
subs.each {|type|
case type
when :specialcharacters
text = sub_specialcharacters(text)
@@ -61,11 +62,11 @@
text = sub_post_replacements(text)
else
puts "asciidoctor: WARNING: unknown substitution type #{type}"
end
}
- text = restore_passthroughs(text) if passthroughs
+ text = restore_passthroughs(text) if has_passthroughs
multiline ? text.lines.entries : text
end
# Public: Apply normal substitutions.
@@ -163,11 +164,11 @@
end
# TODO move unescaping closing square bracket to an operation
@passthroughs << {:text => m[2] || m[4].gsub('\]', ']'), :subs => subs}
index = @passthroughs.size - 1
- "\x0#{index}\x0"
+ "\e#{index}\e"
} unless !(result.include?('+++') || result.include?('$$') || result.include?('pass:'))
result.gsub!(REGEXP[:pass_lit]) {
# alias match for Ruby 1.8.7 compat
m = $~
@@ -177,11 +178,11 @@
next "#{m[1]}#{m[2][1..-1]}"
end
@passthroughs << {:text => m[3], :subs => [:specialcharacters], :literal => true}
index = @passthroughs.size - 1
- "#{m[1]}\x0#{index}\x0"
+ "#{m[1]}\e#{index}\e"
} unless !result.include?('`')
result
end
@@ -189,11 +190,11 @@
#
# text - The String text into which to restore the passthrough text
#
# returns The String text with the passthrough text restored
def restore_passthroughs(text)
- return text if @passthroughs.nil? || @passthroughs.empty? || !text.include?("\x0")
+ return text if @passthroughs.nil? || @passthroughs.empty? || !text.include?("\e")
text.gsub(REGEXP[:pass_placeholder]) {
pass = @passthroughs[$1.to_i];
text = apply_subs(pass[:text], pass.fetch(:subs, []))
pass[:literal] ? Inline.new(self, :quoted, text, :type => :monospaced).render : text
@@ -273,45 +274,59 @@
# NOTE it's necessary to perform this substitution line-by-line
# so that a missing key doesn't wipe out the whole block of data
def sub_attributes(data)
return data if data.nil? || data.empty?
+ string_data = data.is_a? String
# normalizes data type to an array (string becomes single-element array)
- lines = Array(data)
+ lines = string_data ? [data] : data
- result = lines.map {|line|
+ result = []
+ lines.each {|line|
reject = false
- subject = line.dup
- subject.gsub!(REGEXP[:attr_ref]) {
+ line = line.gsub(REGEXP[:attr_ref]) {
# alias match for Ruby 1.8.7 compat
m = $~
- key = m[2].downcase
- # escaped attribute
- if !$1.empty? || !$3.empty?
- "{#$2}"
- elsif m[2].start_with?('counter:')
- args = m[2].split(':')
- @document.counter(args[1], args[2])
- elsif m[2].start_with?('counter2:')
- args = m[2].split(':')
- @document.counter(args[1], args[2])
- ''
- elsif document.attributes.has_key? key
+ # escaped attribute, return unescaped
+ if !m[1].nil? || !m[4].nil?
+ "{#{m[2]}}"
+ elsif (directive = m[3])
+ offset = directive.length + 1
+ expr = m[2][offset..-1]
+ case directive
+ when 'set'
+ args = expr.split(':')
+ _, value = Lexer::store_attribute(args[0], args[1] || '', @document)
+ if value.nil?
+ reject = true
+ break '{undefined}'
+ end
+ ''
+ when 'counter', 'counter2'
+ args = expr.split(':')
+ val = @document.counter(args[0], args[1])
+ directive == 'counter2' ? '' : val
+ else
+ # if we get here, our attr_ref regex is too loose
+ puts "asciidoctor: WARNING: illegal attribute directive: #{m[2]}"
+ ''
+ end
+ elsif (key = m[2].downcase) && @document.attributes.has_key?(key)
@document.attributes[key]
elsif INTRINSICS.has_key? key
INTRINSICS[key]
else
- Debug.debug { "Missing attribute: #{m[2]}, line marked for removal" }
+ Debug.debug { "Missing attribute: #{key}, line marked for removal" }
reject = true
break '{undefined}'
end
- } if subject.include?('{')
+ } if line.include? '{'
- !reject ? subject : nil
- }.compact
+ result << line unless reject
+ }
- data.is_a?(String) ? result.join : result
+ string_data ? result.join : result
end
# Public: Substitute inline macros (e.g., links, images, etc)
#
# Replace inline macros, which may span multiple lines, in the provided text
@@ -331,12 +346,95 @@
found[:colon] = result.include?(':')
found[:at] = result.include?('@')
found[:macroish] = (found[:square_bracket] && found[:colon])
found[:macroish_short_form] = (found[:square_bracket] && found[:colon] && result.include?(':['))
found[:uri] = (found[:colon] && result.include?('://'))
- link_attrs = @document.attributes.has_key?('linkattrs')
+ use_link_attrs = @document.attributes.has_key?('linkattrs')
+ experimental = @document.attributes.has_key?('experimental')
+ if experimental
+ if found[:macroish_short_form] && (result.include?('kbd:') || result.include?('btn:'))
+ result.gsub!(REGEXP[:kbd_btn_macro]) {
+ # alias match for Ruby 1.8.7 compat
+ m = $~
+ # honor the escape
+ if (captured = m[0]).start_with? '\\'
+ next captured[1..-1]
+ end
+
+ if captured.start_with?('kbd')
+ keys = unescape_bracketed_text m[1]
+
+ if keys == '+'
+ keys = ['+']
+ else
+ # need to use closure to work around lack of negative lookbehind
+ keys = keys.split(REGEXP[:kbd_delim]).inject([]) {|c, key|
+ if key.end_with?('++')
+ c << key[0..-3].strip
+ c << '+'
+ else
+ c << key.strip
+ end
+ c
+ }
+ end
+ Inline.new(self, :kbd, nil, :attributes => {'keys' => keys}).render
+ elsif captured.start_with?('btn')
+ label = unescape_bracketed_text m[1]
+ Inline.new(self, :button, label).render
+ end
+ }
+ end
+
+ if found[:macroish] && result.include?('menu:')
+ result.gsub!(REGEXP[:menu_macro]) {
+ # alias match for Ruby 1.8.7 compat
+ m = $~
+ # honor the escape
+ if (captured = m[0]).start_with? '\\'
+ next captured[1..-1]
+ end
+
+ menu = m[1]
+ items = m[2]
+
+ if items.nil?
+ submenus = []
+ menuitem = nil
+ else
+ if (delim = items.include?('>') ? '>' : (items.include?(',') ? ',' : nil))
+ submenus = items.split(delim).map(&:strip)
+ menuitem = submenus.pop
+ else
+ submenus = []
+ menuitem = items.rstrip
+ end
+ end
+
+ Inline.new(self, :menu, nil, :attributes => {'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem}).render
+ }
+ end
+
+ if result.include?('"') && result.include?('>')
+ result.gsub!(REGEXP[:menu_inline_macro]) {
+ # alias match for Ruby 1.8.7 compat
+ m = $~
+ # honor the escape
+ if (captured = m[0]).start_with? '\\'
+ next captured[1..-1]
+ end
+
+ input = m[1]
+
+ menu, *submenus = input.split('>').map(&:strip)
+ menuitem = submenus.pop
+ Inline.new(self, :menu, nil, :attributes => {'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem}).render
+ }
+ end
+ end
+
if found[:macroish] && result.include?('image:')
# image:filename.png[Alt Text]
result.gsub!(REGEXP[:image_macro]) {
# alias match for Ruby 1.8.7 compat
m = $~
@@ -363,11 +461,11 @@
# honor the escape
if m[0].start_with? '\\'
next m[0][1..-1]
end
- terms = unescape_bracketed_text(m[1] || m[2]).split(REGEXP[:csv_delimiter])
+ terms = unescape_bracketed_text(m[1] || m[2]).split(',').map(&:strip)
document.register(:indexterms, [*terms])
Inline.new(self, :indexterm, text, :attributes => {'terms' => terms}).render
}
# indexterm2:[Tigers]
@@ -407,22 +505,31 @@
prefix = prefix[4..-1]
target = target[0..-5]
elsif prefix.start_with?('(') && target.end_with?(')')
target = target[0..-2]
suffix = ')'
+ elsif target.end_with?('):')
+ target = target[0..-3]
+ suffix = '):'
end
@document.register(:links, target)
attrs = nil
#text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
if !m[3].to_s.empty?
- if link_attrs && (m[3].start_with?('"') || m[3].include?(','))
- attrs = parse_attributes(sub_attributes(m[3].gsub('\]', ']')))
+ if use_link_attrs && (m[3].start_with?('"') || m[3].include?(','))
+ attrs = parse_attributes(sub_attributes(m[3].gsub('\]', ']')), [])
text = attrs[1]
else
text = sub_attributes(m[3].gsub('\]', ']'))
end
+
+ if text.end_with? '^'
+ text = text.chop
+ attrs ||= {}
+ attrs['window'] = '_blank' unless attrs.has_key?('window')
+ end
else
text = ''
end
"#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target, :attributes => attrs).render}#{suffix}"
@@ -442,12 +549,12 @@
mailto = m[0].start_with?('mailto:')
target = mailto ? "mailto:#{raw_target}" : raw_target
attrs = nil
#text = sub_attributes(m[2].gsub('\]', ']'))
- if link_attrs && (m[2].start_with?('"') || m[2].include?(','))
- attrs = parse_attributes(sub_attributes(m[2].gsub('\]', ']')))
+ if use_link_attrs && (m[2].start_with?('"') || m[2].include?(','))
+ attrs = parse_attributes(sub_attributes(m[2].gsub('\]', ']')), [])
text = attrs[1]
if mailto
if attrs.has_key? 2
target = "#{target}?subject=#{Helpers.encode_uri(attrs[2])}"
@@ -457,10 +564,17 @@
end
end
else
text = sub_attributes(m[2].gsub('\]', ']'))
end
+
+ if text.end_with? '^'
+ text = text.chop
+ attrs ||= {}
+ attrs['window'] = '_blank' unless attrs.has_key?('window')
+ end
+
# QUESTION should a mailto be registered as an e-mail address?
@document.register(:links, target)
Inline.new(self, :anchor, (!text.empty? ? text : raw_target), :type => :link, :target => target, :attributes => attrs).render
}
@@ -501,11 +615,11 @@
index = @document.counter('footnote-number')
@document.register(:footnotes, Document::Footnote.new(index, id, text))
type = nil
target = nil
else
- id, text = m[2].split(REGEXP[:csv_delimiter], 2)
+ id, text = m[2].split(',', 2).map(&:strip)
if !text.nil?
# hmmmm
text = restore_passthroughs(text)
index = @document.counter('footnote-number')
@document.register(:footnotes, Document::Footnote.new(index, id, text))
@@ -531,11 +645,11 @@
# honor the escape
if m[0].start_with? '\\'
next m[0][1..-1]
end
if !m[1].nil?
- id, reftext = m[1].split(REGEXP[:csv_delimiter], 2)
+ id, reftext = m[1].split(',', 2).map(&:strip)
id.sub!(REGEXP[:dbl_quoted], '\2')
reftext.sub!(REGEXP[:m_dbl_quoted], '\2') unless reftext.nil?
else
id = m[2]
reftext = !m[3].empty? ? m[3] : nil
@@ -563,10 +677,10 @@
m = $~
# honor the escape
if m[0].start_with? '\\'
next m[0][1..-1]
end
- id, reftext = m[1].split(REGEXP[:csv_delimiter])
+ id, reftext = m[1].split(',').map(&:strip)
id.sub!(REGEXP[:dbl_quoted], '\2')
if reftext.nil?
reftext = "[#{id}]"
else
reftext.sub!(REGEXP[:m_dbl_quoted], '\2')