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?('&gt;') ? '&gt;' : (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?('&gt;') + 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('&gt;').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')