Module: ERB::Linter::Converter

Extended by:
Converter
Included in:
Converter
Defined in:
lib/erb/linter/converter.rb

Constant Summary collapse

ATTR_NAME =
%r{[^\r\n\t\f\v= '"<>]*[^\r\n\t\f\v= '"<>/]}
UNQUOTED_VALUE =
ATTR_NAME
UNQUOTED_ATTR =
%r{#{ATTR_NAME}=#{UNQUOTED_VALUE}}
SINGLE_QUOTE_ATTR =
%r{(?:#{ATTR_NAME}='[^']*')}
DOUBLE_QUOTE_ATTR =
%r{(?:#{ATTR_NAME}="[^"]*")}
ERB_TAG =
%r{<%.*?%>}
HTML_ATTR =
%r{\s+#{SINGLE_QUOTE_ATTR}|\s+#{DOUBLE_QUOTE_ATTR}|\s+#{UNQUOTED_ATTR}|\s+#{ATTR_NAME}}
HTML_TAG =
%r{(<\w+)((?:#{HTML_ATTR})*)(\s*)(/>|>)}m

Instance Method Summary collapse

Instance Method Details

#erb2html(source) ⇒ Object

(credit goes to the Deface gem for the original implementation)



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/erb/linter/converter.rb', line 23

def erb2html(source)
  source = +source

  # encode all erb tags so that the HTML looks valid
  erb_tags = {}
  source.gsub!(ERB_TAG) do |tag|
    uid = ["ERB_", SecureRandom.uuid].join.delete('-')
    erb_tags[uid] = tag
    uid
  end

  erb_tags_matcher = Regexp.union(erb_tags.keys)

  # transform/escape all the erb-attributes first
  source.gsub!(HTML_TAG).each do |match|
    line = Regexp.last_match.to_a[1..-1]
    tag = [line[0], line[2]+line[3]]

    # scan each attribute into an array
    attr_scanner = StringScanner.new(line[1])
    attributes = []
    attributes << (attr_scanner.scan(HTML_ATTR) or raise "Can't scan: #{attr_scanner.string}") until attr_scanner.eos?
    # attributes.compact!

    i = -1
    attributes.map! do |attribute|
      if attribute.match?(erb_tags_matcher)
        space, attribute = attribute.scan(/\A(\s+)(.*)\z/m).flatten
        attribute.gsub!(erb_tags_matcher, erb_tags)

        attribute =
          case attribute
          when /\A#{ERB_TAG}\z/
            %{data-erb-#{i += 1}="#{CGI.escapeHTML(attribute)}"}
          when /\A#{ATTR_NAME}=/
            name, value = attribute.split("=", 2)
            quote = '"'
            if value.match(/\A['"]/)
              quote = value[0]
              value = value[1...-1]
            end
            %{data-erb-#{name}=#{quote}#{CGI.escapeHTML(value)}#{quote}}
          else
            raise "Don't know how to process attribute: #{attribute.inspect}"
          end

        "#{space}#{attribute}"
      else
        attribute
      end
    end

    [tag[0], *attributes, tag[1]].join
  end

  # restore the encoded erb tags
  source.gsub!(erb_tags_matcher, erb_tags)

  # replaces all <% %> not inside opening html tags
  replacements = [
    { %r{<%\s*end\s*-?%>} => "</erb>" },
    { %r{(^\s*)<%(\s*(?:else|elsif\b.*)\s*)-?%>} => "\\1</erb>\n\\1<erb silent erb-code-start\\2erb-code-end>" },
    { "<%=" => "<erb loud erb-code-start" },
    { "<%" => "<erb silent erb-code-start" },
    { "-%>" => "erb-code-end>" },
    { "%>" => "erb-code-end>" },
  ]

  replacements.each{ |h| h.each { |replace, with| source.gsub! replace, with } }

  source.scan(/(erb-code-start)((?:(?!erb-code-end)[\s\S])*)(erb-code-end)/).each do |match|
    source.sub!("#{match[0]}#{match[1]}#{match[2]}") do |_m|
      code = match[1]

      # is nil when the parsing is broken, meaning it's an open expression
      if Ripper.sexp(code).nil?
        "erb-code=\"#{CGI.escapeHTML(code.gsub("\n", "&#10;"))}\""
      else
        "erb-code=\"#{CGI.escapeHTML(code.gsub("\n", "&#10;"))}\"></erb"
      end
    end
  end

  source
end