haml 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +117 -5
- data/FAQ.md +7 -17
- data/MIT-LICENSE +1 -1
- data/README.md +85 -42
- data/REFERENCE.md +181 -86
- data/Rakefile +47 -51
- data/lib/haml/attribute_builder.rb +163 -0
- data/lib/haml/attribute_compiler.rb +215 -0
- data/lib/haml/attribute_parser.rb +144 -0
- data/lib/haml/buffer.rb +38 -128
- data/lib/haml/compiler.rb +88 -295
- data/lib/haml/engine.rb +25 -41
- data/lib/haml/error.rb +3 -0
- data/lib/haml/escapable.rb +49 -0
- data/lib/haml/exec.rb +33 -19
- data/lib/haml/filters.rb +20 -24
- data/lib/haml/generator.rb +41 -0
- data/lib/haml/helpers/action_view_extensions.rb +3 -2
- data/lib/haml/helpers/action_view_mods.rb +44 -66
- data/lib/haml/helpers/action_view_xss_mods.rb +1 -0
- data/lib/haml/helpers/safe_erubi_template.rb +27 -0
- data/lib/haml/helpers/safe_erubis_template.rb +16 -4
- data/lib/haml/helpers/xss_mods.rb +18 -12
- data/lib/haml/helpers.rb +122 -58
- data/lib/haml/options.rb +39 -46
- data/lib/haml/parser.rb +278 -217
- data/lib/haml/{template/plugin.rb → plugin.rb} +8 -15
- data/lib/haml/railtie.rb +21 -11
- data/lib/haml/sass_rails_filter.rb +17 -4
- data/lib/haml/template/options.rb +12 -2
- data/lib/haml/template.rb +12 -6
- data/lib/haml/temple_engine.rb +120 -0
- data/lib/haml/temple_line_counter.rb +29 -0
- data/lib/haml/util.rb +80 -199
- data/lib/haml/version.rb +2 -1
- data/lib/haml.rb +2 -1
- data/test/attribute_parser_test.rb +101 -0
- data/test/engine_test.rb +306 -176
- data/test/filters_test.rb +32 -19
- data/test/gemfiles/Gemfile.rails-4.0.x +11 -0
- data/test/gemfiles/Gemfile.rails-4.0.x.lock +87 -0
- data/test/gemfiles/Gemfile.rails-4.1.x +5 -0
- data/test/gemfiles/Gemfile.rails-4.2.x +5 -0
- data/test/gemfiles/Gemfile.rails-5.0.x +4 -0
- data/test/helper_test.rb +282 -96
- data/test/options_test.rb +22 -0
- data/test/parser_test.rb +71 -4
- data/test/results/bemit.xhtml +4 -0
- data/test/results/eval_suppressed.xhtml +4 -4
- data/test/results/helpers.xhtml +43 -41
- data/test/results/helpful.xhtml +6 -3
- data/test/results/just_stuff.xhtml +21 -20
- data/test/results/list.xhtml +9 -9
- data/test/results/nuke_inner_whitespace.xhtml +22 -22
- data/test/results/nuke_outer_whitespace.xhtml +84 -92
- data/test/results/original_engine.xhtml +17 -17
- data/test/results/partial_layout.xhtml +4 -3
- data/test/results/partial_layout_erb.xhtml +4 -3
- data/test/results/partials.xhtml +11 -10
- data/test/results/silent_script.xhtml +63 -63
- data/test/results/standard.xhtml +156 -159
- data/test/results/tag_parsing.xhtml +19 -19
- data/test/results/very_basic.xhtml +2 -2
- data/test/results/whitespace_handling.xhtml +56 -50
- data/test/template_test.rb +44 -53
- data/test/template_test_helper.rb +38 -0
- data/test/templates/_text_area_helper.html.haml +4 -0
- data/test/templates/bemit.haml +3 -0
- data/test/templates/just_stuff.haml +1 -0
- data/test/templates/partial_layout_erb.erb +1 -1
- data/test/templates/standard_ugly.haml +1 -0
- data/test/templates/with_bom.haml +1 -0
- data/test/temple_line_counter_test.rb +40 -0
- data/test/test_helper.rb +26 -12
- data/test/util_test.rb +6 -47
- metadata +88 -106
- data/lib/haml/helpers/rails_323_textarea_fix.rb +0 -24
- data/test/gemfiles/Gemfile.rails-3.0.x +0 -5
- data/test/gemfiles/Gemfile.rails-3.1.x +0 -6
- data/test/gemfiles/Gemfile.rails-3.2.x +0 -5
- data/test/gemfiles/Gemfile.rails-master +0 -4
- data/test/templates/_av_partial_1_ugly.haml +0 -9
- data/test/templates/_av_partial_2_ugly.haml +0 -5
- data/test/templates/action_view_ugly.haml +0 -47
- data/test/templates/standard_ugly.haml +0 -43
    
        data/lib/haml/util.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: false
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            begin
         | 
| 2 4 | 
             
              require 'erubis/tiny'
         | 
| 3 5 | 
             
            rescue LoadError
         | 
| @@ -12,38 +14,6 @@ module Haml | |
| 12 14 | 
             
              module Util
         | 
| 13 15 | 
             
                extend self
         | 
| 14 16 |  | 
| 15 | 
            -
                # Computes the powerset of the given array.
         | 
| 16 | 
            -
                # This is the set of all subsets of the array.
         | 
| 17 | 
            -
                #
         | 
| 18 | 
            -
                # @example
         | 
| 19 | 
            -
                #   powerset([1, 2, 3]) #=>
         | 
| 20 | 
            -
                #     Set[Set[], Set[1], Set[2], Set[3], Set[1, 2], Set[2, 3], Set[1, 3], Set[1, 2, 3]]
         | 
| 21 | 
            -
                # @param arr [Enumerable]
         | 
| 22 | 
            -
                # @return [Set<Set>] The subsets of `arr`
         | 
| 23 | 
            -
                def powerset(arr)
         | 
| 24 | 
            -
                  arr.inject([Set.new].to_set) do |powerset, el|
         | 
| 25 | 
            -
                    new_powerset = Set.new
         | 
| 26 | 
            -
                    powerset.each do |subset|
         | 
| 27 | 
            -
                      new_powerset << subset
         | 
| 28 | 
            -
                      new_powerset << subset + [el]
         | 
| 29 | 
            -
                    end
         | 
| 30 | 
            -
                    new_powerset
         | 
| 31 | 
            -
                  end
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                # Returns information about the caller of the previous method.
         | 
| 35 | 
            -
                #
         | 
| 36 | 
            -
                # @param entry [String] An entry in the `#caller` list, or a similarly formatted string
         | 
| 37 | 
            -
                # @return [[String, Fixnum, (String, nil)]] An array containing the filename, line, and method name of the caller.
         | 
| 38 | 
            -
                #   The method name may be nil
         | 
| 39 | 
            -
                def caller_info(entry = caller[1])
         | 
| 40 | 
            -
                  info = entry.scan(/^(.*?):(-?.*?)(?::.*`(.+)')?$/).first
         | 
| 41 | 
            -
                  info[1] = info[1].to_i
         | 
| 42 | 
            -
                  # This is added by Rubinius to designate a block, but we don't care about it.
         | 
| 43 | 
            -
                  info[2].sub!(/ \{\}\Z/, '') if info[2]
         | 
| 44 | 
            -
                  info
         | 
| 45 | 
            -
                end
         | 
| 46 | 
            -
             | 
| 47 17 | 
             
                # Silence all output to STDERR within a block.
         | 
| 48 18 | 
             
                #
         | 
| 49 19 | 
             
                # @yield A block in which no output will be printed to STDERR
         | 
| @@ -54,19 +24,6 @@ module Haml | |
| 54 24 | 
             
                  $stderr = the_real_stderr
         | 
| 55 25 | 
             
                end
         | 
| 56 26 |  | 
| 57 | 
            -
                # Returns an ActionView::Template* class.
         | 
| 58 | 
            -
                # In pre-3.0 versions of Rails, most of these classes
         | 
| 59 | 
            -
                # were of the form `ActionView::TemplateFoo`,
         | 
| 60 | 
            -
                # while afterwards they were of the form `ActionView;:Template::Foo`.
         | 
| 61 | 
            -
                #
         | 
| 62 | 
            -
                # @param name [#to_s] The name of the class to get.
         | 
| 63 | 
            -
                #   For example, `:Error` will return `ActionView::TemplateError`
         | 
| 64 | 
            -
                #   or `ActionView::Template::Error`.
         | 
| 65 | 
            -
                def av_template_class(name)
         | 
| 66 | 
            -
                  return ActionView.const_get("Template#{name}") if ActionView.const_defined?("Template#{name}")
         | 
| 67 | 
            -
                  return ActionView::Template.const_get(name.to_s)
         | 
| 68 | 
            -
                end
         | 
| 69 | 
            -
             | 
| 70 27 | 
             
                ## Rails XSS Safety
         | 
| 71 28 |  | 
| 72 29 | 
             
                # Whether or not ActionView's XSS protection is available and enabled,
         | 
| @@ -82,6 +39,9 @@ module Haml | |
| 82 39 | 
             
                # With older versions of the Rails XSS-safety mechanism,
         | 
| 83 40 | 
             
                # this destructively modifies the HTML-safety of `text`.
         | 
| 84 41 | 
             
                #
         | 
| 42 | 
            +
                # It only works if you are using ActiveSupport or the parameter `text`
         | 
| 43 | 
            +
                # implements the #html_safe method.
         | 
| 44 | 
            +
                #
         | 
| 85 45 | 
             
                # @param text [String, nil]
         | 
| 86 46 | 
             
                # @return [String, nil] `text`, marked as HTML-safe
         | 
| 87 47 | 
             
                def html_safe(text)
         | 
| @@ -89,174 +49,89 @@ module Haml | |
| 89 49 | 
             
                  text.html_safe
         | 
| 90 50 | 
             
                end
         | 
| 91 51 |  | 
| 92 | 
            -
                # Checks that the encoding of a string is valid | 
| 52 | 
            +
                # Checks that the encoding of a string is valid
         | 
| 93 53 | 
             
                # and cleans up potential encoding gotchas like the UTF-8 BOM.
         | 
| 94 54 | 
             
                # If it's not, yields an error string describing the invalid character
         | 
| 95 | 
            -
                # and the line on which it  | 
| 55 | 
            +
                # and the line on which it occurs.
         | 
| 96 56 | 
             
                #
         | 
| 97 57 | 
             
                # @param str [String] The string of which to check the encoding
         | 
| 98 58 | 
             
                # @yield [msg] A block in which an encoding error can be raised.
         | 
| 99 59 | 
             
                #   Only yields if there is an encoding error
         | 
| 100 60 | 
             
                # @yieldparam msg [String] The error message to be raised
         | 
| 101 61 | 
             
                # @return [String] `str`, potentially with encoding gotchas like BOMs removed
         | 
| 102 | 
            -
                 | 
| 103 | 
            -
                   | 
| 104 | 
            -
                     | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
                       | 
| 112 | 
            -
                        return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding.name)), '')
         | 
| 113 | 
            -
                      else
         | 
| 114 | 
            -
                        return str
         | 
| 115 | 
            -
                      end
         | 
| 62 | 
            +
                def check_encoding(str)
         | 
| 63 | 
            +
                  if str.valid_encoding?
         | 
| 64 | 
            +
                    # Get rid of the Unicode BOM if possible
         | 
| 65 | 
            +
                    # Shortcut for UTF-8 which might be the majority case
         | 
| 66 | 
            +
                    if str.encoding == Encoding::UTF_8
         | 
| 67 | 
            +
                      return str.gsub(/\A\uFEFF/, '')
         | 
| 68 | 
            +
                    elsif str.encoding.name =~ /^UTF-(16|32)(BE|LE)?$/
         | 
| 69 | 
            +
                      return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding)), '')
         | 
| 70 | 
            +
                    else
         | 
| 71 | 
            +
                      return str
         | 
| 116 72 | 
             
                    end
         | 
| 73 | 
            +
                  end
         | 
| 117 74 |  | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 75 | 
            +
                  encoding = str.encoding
         | 
| 76 | 
            +
                  newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding(Encoding::ASCII_8BIT))
         | 
| 77 | 
            +
                  str.force_encoding(Encoding::ASCII_8BIT).split(newlines).each_with_index do |line, i|
         | 
| 78 | 
            +
                    begin
         | 
| 79 | 
            +
                      line.encode(encoding)
         | 
| 80 | 
            +
                    rescue Encoding::UndefinedConversionError => e
         | 
| 81 | 
            +
                      yield <<MSG.rstrip, i + 1
         | 
| 125 82 | 
             
            Invalid #{encoding.name} character #{e.error_char.dump}
         | 
| 126 83 | 
             
            MSG
         | 
| 127 | 
            -
                      end
         | 
| 128 84 | 
             
                    end
         | 
| 129 | 
            -
                    return str
         | 
| 130 85 | 
             
                  end
         | 
| 86 | 
            +
                  return str
         | 
| 131 87 | 
             
                end
         | 
| 132 88 |  | 
| 133 | 
            -
                 | 
| 134 | 
            -
             | 
| 135 | 
            -
                  # at the beginning of the template and uses that encoding if it exists.
         | 
| 136 | 
            -
                  #
         | 
| 137 | 
            -
                  # The Haml encoding rules are simple.
         | 
| 138 | 
            -
                  # If a `-# coding:` comment exists,
         | 
| 139 | 
            -
                  # we assume that that's the original encoding of the document.
         | 
| 140 | 
            -
                  # Otherwise, we use whatever encoding Ruby has.
         | 
| 141 | 
            -
                  #
         | 
| 142 | 
            -
                  # Haml uses the same rules for parsing coding comments as Ruby.
         | 
| 143 | 
            -
                  # This means that it can understand Emacs-style comments
         | 
| 144 | 
            -
                  # (e.g. `-*- encoding: "utf-8" -*-`),
         | 
| 145 | 
            -
                  # and also that it cannot understand non-ASCII-compatible encodings
         | 
| 146 | 
            -
                  # such as `UTF-16` and `UTF-32`.
         | 
| 147 | 
            -
                  #
         | 
| 148 | 
            -
                  # @param str [String] The Haml template of which to check the encoding
         | 
| 149 | 
            -
                  # @yield [msg] A block in which an encoding error can be raised.
         | 
| 150 | 
            -
                  #   Only yields if there is an encoding error
         | 
| 151 | 
            -
                  # @yieldparam msg [String] The error message to be raised
         | 
| 152 | 
            -
                  # @return [String] The original string encoded properly
         | 
| 153 | 
            -
                  # @raise [ArgumentError] if the document declares an unknown encoding
         | 
| 154 | 
            -
                  def check_haml_encoding(str, &block)
         | 
| 155 | 
            -
                    check_encoding(str, &block)
         | 
| 156 | 
            -
                  end
         | 
| 157 | 
            -
                else
         | 
| 158 | 
            -
                  def check_haml_encoding(str, &block)
         | 
| 159 | 
            -
                    str = str.dup if str.frozen?
         | 
| 160 | 
            -
             | 
| 161 | 
            -
                    bom, encoding = parse_haml_magic_comment(str)
         | 
| 162 | 
            -
                    if encoding; str.force_encoding(encoding)
         | 
| 163 | 
            -
                    elsif bom; str.force_encoding("UTF-8")
         | 
| 164 | 
            -
                    end
         | 
| 165 | 
            -
             | 
| 166 | 
            -
                    return check_encoding(str, &block)
         | 
| 167 | 
            -
                  end
         | 
| 168 | 
            -
                end
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                if RUBY_VERSION < "1.9.2"
         | 
| 171 | 
            -
                  def inspect_obj(obj)
         | 
| 172 | 
            -
                    return obj.inspect
         | 
| 173 | 
            -
                  end
         | 
| 174 | 
            -
                else
         | 
| 175 | 
            -
                  # Like `Object#inspect`, but preserves non-ASCII characters rather than escaping them under Ruby 1.9.2.
         | 
| 176 | 
            -
                  # This is necessary so that the precompiled Haml template can be `#encode`d into `@options[:encoding]`
         | 
| 177 | 
            -
                  # before being evaluated.
         | 
| 178 | 
            -
                  #
         | 
| 179 | 
            -
                  # @param obj {Object}
         | 
| 180 | 
            -
                  # @return {String}
         | 
| 181 | 
            -
                  def inspect_obj(obj)
         | 
| 182 | 
            -
                    return ':' + inspect_obj(obj.to_s) if obj.is_a?(Symbol)
         | 
| 183 | 
            -
                    return obj.inspect unless obj.is_a?(String)
         | 
| 184 | 
            -
                    '"' + obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]} + '"'
         | 
| 185 | 
            -
                  end
         | 
| 186 | 
            -
                end
         | 
| 187 | 
            -
             | 
| 188 | 
            -
                ## Static Method Stuff
         | 
| 189 | 
            -
             | 
| 190 | 
            -
                # The context in which the ERB for \{#def\_static\_method} will be run.
         | 
| 191 | 
            -
                class StaticConditionalContext
         | 
| 192 | 
            -
                  # @param set [#include?] The set of variables that are defined for this context.
         | 
| 193 | 
            -
                  def initialize(set)
         | 
| 194 | 
            -
                    @set = set
         | 
| 195 | 
            -
                  end
         | 
| 196 | 
            -
             | 
| 197 | 
            -
                  # Checks whether or not a variable is defined for this context.
         | 
| 198 | 
            -
                  #
         | 
| 199 | 
            -
                  # @param name [Symbol] The name of the variable
         | 
| 200 | 
            -
                  # @return [Boolean]
         | 
| 201 | 
            -
                  def method_missing(name, *args, &block)
         | 
| 202 | 
            -
                    super unless args.empty? && block.nil?
         | 
| 203 | 
            -
                    @set.include?(name)
         | 
| 204 | 
            -
                  end
         | 
| 205 | 
            -
                end
         | 
| 206 | 
            -
             | 
| 207 | 
            -
                # This is used for methods in {Haml::Buffer} that need to be very fast,
         | 
| 208 | 
            -
                # and take a lot of boolean parameters
         | 
| 209 | 
            -
                # that are known at compile-time.
         | 
| 210 | 
            -
                # Instead of passing the parameters in normally,
         | 
| 211 | 
            -
                # a separate method is defined for every possible combination of those parameters;
         | 
| 212 | 
            -
                # these are then called using \{#static\_method\_name}.
         | 
| 89 | 
            +
                # Like {\#check\_encoding}, but also checks for a Ruby-style `-# coding:` comment
         | 
| 90 | 
            +
                # at the beginning of the template and uses that encoding if it exists.
         | 
| 213 91 | 
             
                #
         | 
| 214 | 
            -
                #  | 
| 215 | 
            -
                #  | 
| 216 | 
            -
                #  | 
| 217 | 
            -
                #  | 
| 218 | 
            -
                #
         | 
| 219 | 
            -
                #     def_static_method(Foo, :my_static_method, [:foo, :bar], :baz, :bang, <<RUBY)
         | 
| 220 | 
            -
                #       <% if baz && bang %>
         | 
| 221 | 
            -
                #         return foo + bar
         | 
| 222 | 
            -
                #       <% elsif baz || bang %>
         | 
| 223 | 
            -
                #         return foo - bar
         | 
| 224 | 
            -
                #       <% else %>
         | 
| 225 | 
            -
                #         return 17
         | 
| 226 | 
            -
                #       <% end %>
         | 
| 227 | 
            -
                #     RUBY
         | 
| 92 | 
            +
                # The Haml encoding rules are simple.
         | 
| 93 | 
            +
                # If a `-# coding:` comment exists,
         | 
| 94 | 
            +
                # we assume that that's the original encoding of the document.
         | 
| 95 | 
            +
                # Otherwise, we use whatever encoding Ruby has.
         | 
| 228 96 | 
             
                #
         | 
| 229 | 
            -
                #  | 
| 97 | 
            +
                # Haml uses the same rules for parsing coding comments as Ruby.
         | 
| 98 | 
            +
                # This means that it can understand Emacs-style comments
         | 
| 99 | 
            +
                # (e.g. `-*- encoding: "utf-8" -*-`),
         | 
| 100 | 
            +
                # and also that it cannot understand non-ASCII-compatible encodings
         | 
| 101 | 
            +
                # such as `UTF-16` and `UTF-32`.
         | 
| 230 102 | 
             
                #
         | 
| 231 | 
            -
                # @ | 
| 232 | 
            -
                # @ | 
| 233 | 
            -
                #  | 
| 234 | 
            -
                # @ | 
| 235 | 
            -
                # | 
| 236 | 
            -
                # @ | 
| 237 | 
            -
                 | 
| 238 | 
            -
             | 
| 239 | 
            -
                  erb = vars.pop
         | 
| 240 | 
            -
                  info = caller_info
         | 
| 241 | 
            -
                  powerset(vars).each do |set|
         | 
| 242 | 
            -
                    context = StaticConditionalContext.new(set).instance_eval {binding}
         | 
| 243 | 
            -
                    method_content = (defined?(Erubis::TinyEruby) && Erubis::TinyEruby || ERB).new(erb).result(context)
         | 
| 103 | 
            +
                # @param str [String] The Haml template of which to check the encoding
         | 
| 104 | 
            +
                # @yield [msg] A block in which an encoding error can be raised.
         | 
| 105 | 
            +
                #   Only yields if there is an encoding error
         | 
| 106 | 
            +
                # @yieldparam msg [String] The error message to be raised
         | 
| 107 | 
            +
                # @return [String] The original string encoded properly
         | 
| 108 | 
            +
                # @raise [ArgumentError] if the document declares an unknown encoding
         | 
| 109 | 
            +
                def check_haml_encoding(str, &block)
         | 
| 110 | 
            +
                  str = str.dup if str.frozen?
         | 
| 244 111 |  | 
| 245 | 
            -
             | 
| 246 | 
            -
             | 
| 247 | 
            -
             | 
| 248 | 
            -
                      end
         | 
| 249 | 
            -
            METHOD
         | 
| 112 | 
            +
                  bom, encoding = parse_haml_magic_comment(str)
         | 
| 113 | 
            +
                  if encoding; str.force_encoding(encoding)
         | 
| 114 | 
            +
                  elsif bom; str.force_encoding(Encoding::UTF_8)
         | 
| 250 115 | 
             
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  return check_encoding(str, &block)
         | 
| 251 118 | 
             
                end
         | 
| 252 119 |  | 
| 253 | 
            -
                #  | 
| 120 | 
            +
                # Like `Object#inspect`, but preserves non-ASCII characters rather than escaping them.
         | 
| 121 | 
            +
                # This is necessary so that the precompiled Haml template can be `#encode`d into `@options[:encoding]`
         | 
| 122 | 
            +
                # before being evaluated.
         | 
| 254 123 | 
             
                #
         | 
| 255 | 
            -
                # @param  | 
| 256 | 
            -
                # @ | 
| 257 | 
            -
                 | 
| 258 | 
            -
             | 
| 259 | 
            -
                   | 
| 124 | 
            +
                # @param obj {Object}
         | 
| 125 | 
            +
                # @return {String}
         | 
| 126 | 
            +
                def inspect_obj(obj)
         | 
| 127 | 
            +
                  case obj
         | 
| 128 | 
            +
                  when String
         | 
| 129 | 
            +
                    %Q!"#{obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]}}"!
         | 
| 130 | 
            +
                  when Symbol
         | 
| 131 | 
            +
                    ":#{inspect_obj(obj.to_s)}"
         | 
| 132 | 
            +
                  else
         | 
| 133 | 
            +
                    obj.inspect
         | 
| 134 | 
            +
                  end
         | 
| 260 135 | 
             
                end
         | 
| 261 136 |  | 
| 262 137 | 
             
                # Scans through a string looking for the interoplation-opening `#{`
         | 
| @@ -271,7 +146,7 @@ METHOD | |
| 271 146 | 
             
                # @return [String] The text remaining in the scanner after all `#{`s have been processed
         | 
| 272 147 | 
             
                def handle_interpolation(str)
         | 
| 273 148 | 
             
                  scan = StringScanner.new(str)
         | 
| 274 | 
            -
                  yield scan while scan.scan(/(.*?)(\\*) | 
| 149 | 
            +
                  yield scan while scan.scan(/(.*?)(\\*)#([\{@$])/)
         | 
| 275 150 | 
             
                  scan.rest
         | 
| 276 151 | 
             
                end
         | 
| 277 152 |  | 
| @@ -283,10 +158,8 @@ METHOD | |
| 283 158 | 
             
                #     from                    to
         | 
| 284 159 | 
             
                #
         | 
| 285 160 | 
             
                # @param scanner [StringScanner] The string scanner to move
         | 
| 286 | 
            -
                # @param start [ | 
| 287 | 
            -
                # | 
| 288 | 
            -
                # @param finish [Character] The character closing the balanced pair.
         | 
| 289 | 
            -
                #   A `Fixnum` in 1.8, a `String` in 1.9
         | 
| 161 | 
            +
                # @param start [String] The character opening the balanced pair.
         | 
| 162 | 
            +
                # @param finish [String] The character closing the balanced pair.
         | 
| 290 163 | 
             
                # @param count [Fixnum] The number of opening characters matched
         | 
| 291 164 | 
             
                #   before calling this method
         | 
| 292 165 | 
             
                # @return [(String, String)] The string matched within the balanced pair
         | 
| @@ -322,20 +195,28 @@ METHOD | |
| 322 195 | 
             
                end
         | 
| 323 196 |  | 
| 324 197 | 
             
                def contains_interpolation?(str)
         | 
| 325 | 
            -
                  str | 
| 198 | 
            +
                  /#[\{$@]/ === str
         | 
| 326 199 | 
             
                end
         | 
| 327 200 |  | 
| 328 201 | 
             
                def unescape_interpolation(str, escape_html = nil)
         | 
| 329 202 | 
             
                  res = ''
         | 
| 330 203 | 
             
                  rest = Haml::Util.handle_interpolation str.dump do |scan|
         | 
| 331 204 | 
             
                    escapes = (scan[2].size - 1) / 2
         | 
| 205 | 
            +
                    char = scan[3] # '{', '@' or '$'
         | 
| 332 206 | 
             
                    res << scan.matched[0...-3 - escapes]
         | 
| 333 207 | 
             
                    if escapes % 2 == 1
         | 
| 334 | 
            -
                      res <<  | 
| 208 | 
            +
                      res << "\##{char}"
         | 
| 335 209 | 
             
                    else
         | 
| 336 | 
            -
                       | 
| 210 | 
            +
                      interpolated = if char == '{'
         | 
| 211 | 
            +
                        balance(scan, ?{, ?}, 1)[0][0...-1]
         | 
| 212 | 
            +
                      else
         | 
| 213 | 
            +
                        scan.scan(/\w+/)
         | 
| 214 | 
            +
                      end
         | 
| 215 | 
            +
                      content = eval('"' + interpolated + '"')
         | 
| 216 | 
            +
                      content.prepend(char) if char == '@' || char == '$'
         | 
| 337 217 | 
             
                      content = "Haml::Helpers.html_escape((#{content}))" if escape_html
         | 
| 338 | 
            -
             | 
| 218 | 
            +
             | 
| 219 | 
            +
                      res << "\#{#{content}}"
         | 
| 339 220 | 
             
                    end
         | 
| 340 221 | 
             
                  end
         | 
| 341 222 | 
             
                  res + rest
         | 
| @@ -350,7 +231,7 @@ METHOD | |
| 350 231 | 
             
                #   Whether the document begins with a UTF-8 BOM,
         | 
| 351 232 | 
             
                #   and the declared encoding of the document (or nil if none is declared)
         | 
| 352 233 | 
             
                def parse_haml_magic_comment(str)
         | 
| 353 | 
            -
                  scanner = StringScanner.new(str.dup.force_encoding( | 
| 234 | 
            +
                  scanner = StringScanner.new(str.dup.force_encoding(Encoding::ASCII_8BIT))
         | 
| 354 235 | 
             
                  bom = scanner.scan(/\xEF\xBB\xBF/n)
         | 
| 355 236 | 
             
                  return bom unless scanner.scan(/-\s*#\s*/n)
         | 
| 356 237 | 
             
                  if coding = try_parse_haml_emacs_magic_comment(scanner)
         | 
    
        data/lib/haml/version.rb
    CHANGED
    
    
    
        data/lib/haml.rb
    CHANGED
    
    | @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 | 
             
            require 'haml/version'
         | 
| 2 3 |  | 
| 3 4 | 
             
            # The module that contains everything Haml-related:
         | 
| @@ -20,4 +21,4 @@ end | |
| 20 21 |  | 
| 21 22 | 
             
            require 'haml/util'
         | 
| 22 23 | 
             
            require 'haml/engine'
         | 
| 23 | 
            -
            require 'haml/railtie' if defined?(Rails)
         | 
| 24 | 
            +
            require 'haml/railtie' if defined?(Rails::Railtie)
         | 
| @@ -0,0 +1,101 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class AttributeParserTeset < Haml::TestCase
         | 
| 4 | 
            +
              describe '.parse' do
         | 
| 5 | 
            +
                def assert_parse(expected, haml)
         | 
| 6 | 
            +
                  actual = Haml::AttributeParser.parse(haml)
         | 
| 7 | 
            +
                  if expected.nil?
         | 
| 8 | 
            +
                    assert_nil actual
         | 
| 9 | 
            +
                  else
         | 
| 10 | 
            +
                    assert_equal expected, actual
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                if Haml::AttributeParser.available?
         | 
| 15 | 
            +
                  it { assert_parse(nil, '') }
         | 
| 16 | 
            +
                  it { assert_parse({}, '{}') }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  describe 'invalid hash' do
         | 
| 19 | 
            +
                    it { assert_parse(nil, '{hash}') }
         | 
| 20 | 
            +
                    it { assert_parse(nil, '{hash, foo: bar}') }
         | 
| 21 | 
            +
                    it { assert_parse(nil, '{ {hash} }') }
         | 
| 22 | 
            +
                    it { assert_parse(nil, '{ { hash, foo: bar } }') }
         | 
| 23 | 
            +
                    it { assert_parse(nil, '{}.merge({})') }
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  describe 'non single hash' do
         | 
| 27 | 
            +
                    it { assert_parse(nil, '{ a: 0 };{}') }
         | 
| 28 | 
            +
                    it { assert_parse(nil, '{ a: 0 }[nil] = {}') }
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  describe 'dynamic key' do
         | 
| 32 | 
            +
                    it { assert_parse(nil, '{foo => bar}') }
         | 
| 33 | 
            +
                    it { assert_parse(nil, '{[] => bar}') }
         | 
| 34 | 
            +
                    it { assert_parse(nil, '{[1,2,3] => bar}') }
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  describe 'foo: bar' do
         | 
| 38 | 
            +
                    it { assert_parse({ '_' => '1' }, '{_:1,}') }
         | 
| 39 | 
            +
                    it { assert_parse({ 'foo' => 'bar' }, '{ foo:  bar }') }
         | 
| 40 | 
            +
                    it { assert_parse({ 'a' => 'b', 'c' => ':d' }, '{a: b, c: :d}') }
         | 
| 41 | 
            +
                    it { assert_parse({ 'a' => '[]', 'c' => '"d"' }, '{a: [], c: "d"}') }
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  describe ':foo => bar' do
         | 
| 45 | 
            +
                    it { assert_parse({ 'foo' => ':bar' }, '{  :foo   =>  :bar  }') }
         | 
| 46 | 
            +
                    it { assert_parse({ '_' => '"foo"' }, '{:_=>"foo"}') }
         | 
| 47 | 
            +
                    it { assert_parse({ 'a' => '[]', 'c' => '""', 'b' => '"#{3}"' }, '{:a => [], c: "", :b => "#{3}"}') }
         | 
| 48 | 
            +
                    it { assert_parse(nil, '{:"f#{o}o" => bar}') }
         | 
| 49 | 
            +
                    it { assert_parse(nil, '{:"#{f}oo" => bar}') }
         | 
| 50 | 
            +
                    it { assert_parse(nil, '{:"#{foo}" => bar}') }
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  describe '"foo" => bar' do
         | 
| 54 | 
            +
                    it { assert_parse({ 'foo' => '[1]' }, '{"foo"=>[1]}') }
         | 
| 55 | 
            +
                    it { assert_parse({ 'foo' => 'nya' }, "{ 'foo' => nya }") }
         | 
| 56 | 
            +
                    it { assert_parse({ 'foo' => 'bar' }, '{%q[foo] => bar }') }
         | 
| 57 | 
            +
                    it { assert_parse({ 'foo' => '[1]' }, ' { "foo"=>[1] } ') }
         | 
| 58 | 
            +
                    it { assert_parse({ 'foo' => 'nya' }, " {  'foo' => nya } ") }
         | 
| 59 | 
            +
                    it { assert_parse({ 'foo' => 'bar' }, ' { %q[foo] => bar } ') }
         | 
| 60 | 
            +
                    it { assert_parse(nil, '{"f#{o}o" => bar}') }
         | 
| 61 | 
            +
                    it { assert_parse(nil, '{"#{f}oo" => bar}') }
         | 
| 62 | 
            +
                    it { assert_parse(nil, '{"#{foo}" => bar}') }
         | 
| 63 | 
            +
                    it { assert_parse({ 'f#{o}o' => 'bar' }, '{ %q[f#{o}o] => bar }') }
         | 
| 64 | 
            +
                    it { assert_parse({ 'f#{o}o' => 'bar' }, '{ %q[f#{o}o] => bar,  }') }
         | 
| 65 | 
            +
                    it { assert_parse(nil, '%Q[f#{o}o] => bar ') }
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  describe 'multi lines' do
         | 
| 69 | 
            +
                    it { assert_parse({ 'a' => 'b', 'c' => 'd' }, "{a: b,\nc: d}") }
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  if RUBY_VERSION >= '2.2.0'
         | 
| 73 | 
            +
                    describe '"foo": bar' do
         | 
| 74 | 
            +
                      it { assert_parse({ 'foo' => '()' }, '{"foo":()}') }
         | 
| 75 | 
            +
                      it { assert_parse({ 'foo' => 'nya' }, " {'foo': nya} ") }
         | 
| 76 | 
            +
                      it { assert_parse({ 'foo' => '()' }, ' { "foo":() , }') }
         | 
| 77 | 
            +
                      it { assert_parse({ 'foo' => 'nya' }, " {  'foo': nya , }") }
         | 
| 78 | 
            +
                      it { assert_parse(nil, '{"f#{o}o": bar}') }
         | 
| 79 | 
            +
                      it { assert_parse(nil, '{"#{f}oo": bar}') }
         | 
| 80 | 
            +
                      it { assert_parse(nil, '{"#{foo}": bar}') }
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  describe 'nested array' do
         | 
| 85 | 
            +
                    it { assert_parse({ 'foo' => '[1,2,]' }, '{foo: [1,2,],}') }
         | 
| 86 | 
            +
                    it { assert_parse({ 'foo' => '[1,2,[3,4],5]' }, '{foo: [1,2,[3,4],5],}') }
         | 
| 87 | 
            +
                    it { assert_parse({ 'foo' => '[1,2,[3,4],5]', 'bar' => '[[1,2],]'}, '{foo: [1,2,[3,4],5],bar: [[1,2],],}') }
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  describe 'nested hash' do
         | 
| 91 | 
            +
                    it { assert_parse({ 'foo' => '{ }', 'bar' => '{}' }, '{foo: { }, bar: {}}') }
         | 
| 92 | 
            +
                    it { assert_parse({ 'foo' => '{ bar: baz, hoge: fuga, }' }, '{foo: { bar: baz, hoge: fuga, }, }') }
         | 
| 93 | 
            +
                    it { assert_parse({ 'data' => '{ confirm: true, disable: false }', 'hello' => '{ world: foo, }' }, '{data: { confirm: true, disable: false }, :hello => { world: foo, },}') }
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  describe 'nested method' do
         | 
| 97 | 
            +
                    it { assert_parse({ 'foo' => 'bar(a, b)', 'hoge' => 'piyo(a, b,)' }, '{ foo: bar(a, b), hoge: piyo(a, b,), }') }
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
            end
         |