hamlit 2.11.1 → 2.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 315b433bd4a2af17f13b1a8ff54787c65b25beabcbdb1274dd86b29248e75f60
4
- data.tar.gz: cd02934b413dd1cf6891f35f5cb1ee8731d68e0f82eb300bf92679b7574ec598
3
+ metadata.gz: d8a587e57cbdc02c90d18591926efb8bf4fd990d60c31969501418a1eed0d2d9
4
+ data.tar.gz: 7f97e39c33bad82a1cc5b4056b8ac0765a2939f2a9c0a6fecfd156daefa98fa3
5
5
  SHA512:
6
- metadata.gz: 128daeb6d3f1d34fd33718b90b20b75117cda4147e8783d039b2daf0e019d836ac580d78fd3e01561f53f4348e442d3a04169761048cc2105ef1e3cae5c5c131
7
- data.tar.gz: 0c11bc8dce803aa8d2adfdc78c7b91274678371adb7d30a8a36d997b60ada32320349712cd39c1feec7d965f5e1bbf6b6ab878a3769d4e20ee245afd1772bf79
6
+ metadata.gz: dba41938cf6697aeef29f404178de0ccf46cb438402510e989af63593e5ea853627f3f0594fd0359e4b10c68b3c53a7ae64b9c3e2d2f717707e5996006b49721
7
+ data.tar.gz: 61ce345a747e0fffee535ecab7caee9b0320858f0243b405643115b97cef012039ed9d72766f5bbd4f1b0cde8fa2ffca7b7cea74a8b9ce2a875106c680f5274d
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. This
4
4
  project adheres to [Semantic Versioning](http://semver.org/). This change log is based upon
5
5
  [keep-a-changelog](https://github.com/olivierlacan/keep-a-changelog).
6
6
 
7
+ ## [2.12.0](https://github.com/k0kubun/hamlit/compare/v2.11.1...v2.12.0) - 2020-09-30
8
+
9
+ ### Changed
10
+
11
+ - Class names are no longer ordered alphabetically
12
+ *Thanks to @aliismayilov*
13
+ - This is compatible with [Haml 5.2](https://github.com/haml/haml/blob/v5.2.0/CHANGELOG.md#52)
14
+
7
15
  ## [2.11.1](https://github.com/k0kubun/hamlit/compare/v2.11.0...v2.11.1) - 2020-08-25
8
16
 
9
17
  ### Fixed
data/Gemfile CHANGED
@@ -8,10 +8,6 @@ end
8
8
  # Specify your gem's dependencies in hamlit.gemspec
9
9
  gemspec
10
10
 
11
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2.2')
12
- gem 'rack', '< 2'
13
- end
14
-
15
11
  gem 'benchmark-ips', '2.3.0'
16
12
  gem 'maxitest'
17
13
  gem 'pry'
@@ -152,7 +152,6 @@ hamlit_build_multi_class(VALUE escape_attrs, VALUE values)
152
152
  }
153
153
  }
154
154
 
155
- rb_ary_sort_bang(buf);
156
155
  rb_funcall(buf, id_uniq_bang, 0);
157
156
 
158
157
  return escape_attribute(escape_attrs, rb_ary_join(buf, str_space()));
@@ -47,7 +47,7 @@ module Hamlit::AttributeBuilder
47
47
  when value.is_a?(String)
48
48
  # noop
49
49
  when value.is_a?(Array)
50
- value = value.flatten.select { |v| v }.map(&:to_s).sort.uniq.join(' ')
50
+ value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ')
51
51
  when value
52
52
  value = value.to_s
53
53
  else
@@ -67,7 +67,7 @@ module Hamlit::AttributeBuilder
67
67
  classes << value.to_s
68
68
  end
69
69
  end
70
- escape_html(escape_attrs, classes.map(&:to_s).sort.uniq.join(' '))
70
+ escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
71
71
  end
72
72
 
73
73
  def build_data(escape_attrs, quote, *hashes)
@@ -17,7 +17,7 @@ module Hamlit
17
17
  if node.value[:object_ref] != :nil || !Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
18
18
  return runtime_compile(node)
19
19
  end
20
- node.value[:attributes_hashes].each do |attribute_str|
20
+ [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
21
21
  hash = AttributeParser.parse(attribute_str)
22
22
  return runtime_compile(node) unless hash
23
23
  hashes << hash
@@ -28,11 +28,11 @@ module Hamlit
28
28
  private
29
29
 
30
30
  def runtime_compile(node)
31
- attrs = node.value[:attributes_hashes]
31
+ attrs = []
32
32
  attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {}
33
33
 
34
34
  args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect].push(node.value[:object_ref]) + attrs
35
- [:html, :attrs, [:dynamic, "::Hamlit::AttributeBuilder.build(#{args.join(', ')})"]]
35
+ [:html, :attrs, [:dynamic, "::Hamlit::AttributeBuilder.build(#{args.join(', ')}, #{node.value[:dynamic_attributes].to_literal})"]]
36
36
  end
37
37
 
38
38
  def static_compile(static_hash, dynamic_hashes)
@@ -39,7 +39,7 @@ module Hamlit
39
39
  when :script, :silent_script
40
40
  @lineno += 1
41
41
  when :tag
42
- node.value[:attributes_hashes].each do |attribute_hash|
42
+ [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_hash|
43
43
  @lineno += attribute_hash.count("\n")
44
44
  end
45
45
  @lineno += 1 if node.children.empty? && node.value[:parse]
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hamlit
4
+ module HamlAttributeBuilder
5
+ # https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
6
+ INVALID_ATTRIBUTE_NAME_REGEX = /[ \0"'>\/=]/
7
+
8
+ class << self
9
+ def build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
10
+ # @TODO this is an absolutely ridiculous amount of arguments. At least
11
+ # some of this needs to be moved into an instance method.
12
+ join_char = hyphenate_data_attrs ? '-' : '_'
13
+
14
+ attributes.each do |key, value|
15
+ if value.is_a?(Hash)
16
+ data_attributes = attributes.delete(key)
17
+ data_attributes = flatten_data_attributes(data_attributes, '', join_char)
18
+ data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
19
+ verify_attribute_names!(data_attributes.keys)
20
+ attributes = data_attributes.merge(attributes)
21
+ end
22
+ end
23
+
24
+ result = attributes.collect do |attr, value|
25
+ next if value.nil?
26
+
27
+ value = filter_and_join(value, ' ') if attr == 'class'
28
+ value = filter_and_join(value, '_') if attr == 'id'
29
+
30
+ if value == true
31
+ next " #{attr}" if is_html
32
+ next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
33
+ elsif value == false
34
+ next
35
+ end
36
+
37
+ value =
38
+ if escape_attrs == :once
39
+ Hamlit::HamlHelpers.escape_once_without_haml_xss(value.to_s)
40
+ elsif escape_attrs
41
+ Hamlit::HamlHelpers.html_escape_without_haml_xss(value.to_s)
42
+ else
43
+ value.to_s
44
+ end
45
+ " #{attr}=#{attr_wrapper}#{value}#{attr_wrapper}"
46
+ end
47
+ result.compact!
48
+ result.sort!
49
+ result.join
50
+ end
51
+
52
+ # @return [String, nil]
53
+ def filter_and_join(value, separator)
54
+ return '' if (value.respond_to?(:empty?) && value.empty?)
55
+
56
+ if value.is_a?(Array)
57
+ value = value.flatten
58
+ value.map! {|item| item ? item.to_s : nil}
59
+ value.compact!
60
+ value = value.join(separator)
61
+ else
62
+ value = value ? value.to_s : nil
63
+ end
64
+ !value.nil? && !value.empty? && value
65
+ end
66
+
67
+ # Merges two attribute hashes.
68
+ # This is the same as `to.merge!(from)`,
69
+ # except that it merges id, class, and data attributes.
70
+ #
71
+ # ids are concatenated with `"_"`,
72
+ # and classes are concatenated with `" "`.
73
+ # data hashes are simply merged.
74
+ #
75
+ # Destructively modifies `to`.
76
+ #
77
+ # @param to [{String => String,Hash}] The attribute hash to merge into
78
+ # @param from [{String => Object}] The attribute hash to merge from
79
+ # @return [{String => String,Hash}] `to`, after being merged
80
+ def merge_attributes!(to, from)
81
+ from.keys.each do |key|
82
+ to[key] = merge_value(key, to[key], from[key])
83
+ end
84
+ to
85
+ end
86
+
87
+ # Merge multiple values to one attribute value. No destructive operation.
88
+ #
89
+ # @param key [String]
90
+ # @param values [Array<Object>]
91
+ # @return [String,Hash]
92
+ def merge_values(key, *values)
93
+ values.inject(nil) do |to, from|
94
+ merge_value(key, to, from)
95
+ end
96
+ end
97
+
98
+ def verify_attribute_names!(attribute_names)
99
+ attribute_names.each do |attribute_name|
100
+ if attribute_name =~ INVALID_ATTRIBUTE_NAME_REGEX
101
+ raise InvalidAttributeNameError.new("Invalid attribute name '#{attribute_name}' was rendered")
102
+ end
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # Merge a couple of values to one attribute value. No destructive operation.
109
+ #
110
+ # @param to [String,Hash,nil]
111
+ # @param from [Object]
112
+ # @return [String,Hash]
113
+ def merge_value(key, to, from)
114
+ if from.kind_of?(Hash) || to.kind_of?(Hash)
115
+ from = { nil => from } if !from.is_a?(Hash)
116
+ to = { nil => to } if !to.is_a?(Hash)
117
+ to.merge(from)
118
+ elsif key == 'id'
119
+ merged_id = filter_and_join(from, '_')
120
+ if to && merged_id
121
+ merged_id = "#{to}_#{merged_id}"
122
+ elsif to || merged_id
123
+ merged_id ||= to
124
+ end
125
+ merged_id
126
+ elsif key == 'class'
127
+ merged_class = filter_and_join(from, ' ')
128
+ if to && merged_class
129
+ merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
130
+ elsif to || merged_class
131
+ merged_class ||= to
132
+ end
133
+ merged_class
134
+ else
135
+ from
136
+ end
137
+ end
138
+
139
+ def build_data_keys(data_hash, hyphenate, attr_name="data")
140
+ Hash[data_hash.map do |name, value|
141
+ if name == nil
142
+ [attr_name, value]
143
+ elsif hyphenate
144
+ ["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
145
+ else
146
+ ["#{attr_name}-#{name}", value]
147
+ end
148
+ end]
149
+ end
150
+
151
+ def flatten_data_attributes(data, key, join_char, seen = [])
152
+ return {key => data} unless data.is_a?(Hash)
153
+
154
+ return {key => nil} if seen.include? data.object_id
155
+ seen << data.object_id
156
+
157
+ data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
158
+ joined = key == '' ? k : [key, k].join(join_char)
159
+ hash.merge! flatten_data_attributes(v, joined, join_char, seen)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -617,6 +617,9 @@ MESSAGE
617
617
  text.gsub(HTML_ESCAPE_REGEX, HTML_ESCAPE)
618
618
  end
619
619
 
620
+ # Always escape text regardless of html_safe?
621
+ alias_method :html_escape_without_haml_xss, :html_escape
622
+
620
623
  HTML_ESCAPE_ONCE_REGEX = /[\"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
621
624
 
622
625
  # Escapes HTML entities in `text`, but without escaping an ampersand
@@ -629,6 +632,9 @@ MESSAGE
629
632
  text.gsub(HTML_ESCAPE_ONCE_REGEX, HTML_ESCAPE)
630
633
  end
631
634
 
635
+ # Always escape text once regardless of html_safe?
636
+ alias_method :escape_once_without_haml_xss, :escape_once
637
+
632
638
  # Returns whether or not the current template is a Haml template.
633
639
  #
634
640
  # This function, unlike other {Haml::Helpers} functions,
@@ -1,6 +1,7 @@
1
1
  require 'strscan'
2
2
  require 'hamlit/parser/haml_util'
3
3
  require 'hamlit/parser/haml_error'
4
+ require 'hamlit/parser/haml_attribute_builder'
4
5
 
5
6
  module Hamlit
6
7
  class HamlParser
@@ -206,6 +207,31 @@ module Hamlit
206
207
  end
207
208
  end
208
209
 
210
+ # @param [String] new - Hash literal including dynamic values.
211
+ # @param [String] old - Hash literal including dynamic values or Ruby literal of multiple Hashes which MUST be interpreted as method's last arguments.
212
+ DynamicAttributes = Struct.new(:new, :old) do
213
+ undef :old=
214
+ def old=(value)
215
+ unless value =~ /\A{.*}\z/m
216
+ raise ArgumentError.new('Old attributes must start with "{" and end with "}"')
217
+ end
218
+ self[:old] = value
219
+ end
220
+
221
+ # This will be a literal for Haml::Buffer#attributes's last argument, `attributes_hashes`.
222
+ def to_literal
223
+ [new, stripped_old].compact.join(', ')
224
+ end
225
+
226
+ private
227
+
228
+ # For `%foo{ { foo: 1 }, bar: 2 }`, :old is "{ { foo: 1 }, bar: 2 }" and this method returns " { foo: 1 }, bar: 2 " for last argument.
229
+ def stripped_old
230
+ return nil if old.nil?
231
+ old.sub!(/\A{/, '').sub!(/}\z/m, '')
232
+ end
233
+ end
234
+
209
235
  # Processes and deals with lowering indentation.
210
236
  def process_indent(line)
211
237
  return unless line.tabs <= @template_tabs && @template_tabs > 0
@@ -403,22 +429,20 @@ module Hamlit
403
429
  end
404
430
 
405
431
  attributes = ::Hamlit::HamlParser.parse_class_and_id(attributes)
406
- attributes_list = []
432
+ dynamic_attributes = DynamicAttributes.new
407
433
 
408
434
  if attributes_hashes[:new]
409
435
  static_attributes, attributes_hash = attributes_hashes[:new]
410
- ::Hamlit::HamlBuffer.merge_attrs(attributes, static_attributes) if static_attributes
411
- attributes_list << attributes_hash
436
+ HamlAttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
437
+ dynamic_attributes.new = attributes_hash
412
438
  end
413
439
 
414
440
  if attributes_hashes[:old]
415
441
  static_attributes = parse_static_hash(attributes_hashes[:old])
416
- ::Hamlit::HamlBuffer.merge_attrs(attributes, static_attributes) if static_attributes
417
- attributes_list << attributes_hashes[:old] unless static_attributes || @options.suppress_eval
442
+ HamlAttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
443
+ dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
418
444
  end
419
445
 
420
- attributes_list.compact!
421
-
422
446
  raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
423
447
  raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
424
448
  raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
@@ -433,7 +457,7 @@ module Hamlit
433
457
  line = handle_ruby_multiline(line) if parse
434
458
 
435
459
  ParseNode.new(:tag, line.index + 1, :name => tag_name, :attributes => attributes,
436
- :attributes_hashes => attributes_list, :self_closing => self_closing,
460
+ :dynamic_attributes => dynamic_attributes, :self_closing => self_closing,
437
461
  :nuke_inner_whitespace => nuke_inner_whitespace,
438
462
  :nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
439
463
  :escape_html => escape_html, :preserve_tag => preserve_tag,
@@ -641,7 +665,6 @@ module Hamlit
641
665
  raise e
642
666
  end
643
667
 
644
- attributes_hash = attributes_hash[1...-1] if attributes_hash
645
668
  return attributes_hash, rest, last_line
646
669
  end
647
670
 
@@ -6,12 +6,15 @@ module Hamlit
6
6
  # to work with Rails' XSS protection methods.
7
7
  module XssMods
8
8
  def self.included(base)
9
- %w[html_escape find_and_preserve preserve list_of surround
10
- precede succeed capture_haml haml_concat haml_internal_concat haml_indent
11
- escape_once].each do |name|
9
+ %w[find_and_preserve preserve list_of surround
10
+ precede succeed capture_haml haml_concat haml_internal_concat haml_indent].each do |name|
12
11
  base.send(:alias_method, "#{name}_without_haml_xss", name)
13
12
  base.send(:alias_method, name, "#{name}_with_haml_xss")
14
13
  end
14
+ # Those two always have _without_haml_xss
15
+ %w[html_escape escape_once].each do |name|
16
+ base.send(:alias_method, name, "#{name}_with_haml_xss")
17
+ end
15
18
  end
16
19
 
17
20
  # Don't escape text that's already safe,
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Hamlit
3
- VERSION = '2.11.1'
3
+ VERSION = '2.12.0'
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hamlit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.1
4
+ version: 2.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takashi Kokubun
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-26 00:00:00.000000000 Z
11
+ date: 2020-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: temple
@@ -350,6 +350,7 @@ files:
350
350
  - lib/hamlit/parser.rb
351
351
  - lib/hamlit/parser/MIT-LICENSE
352
352
  - lib/hamlit/parser/README.md
353
+ - lib/hamlit/parser/haml_attribute_builder.rb
353
354
  - lib/hamlit/parser/haml_buffer.rb
354
355
  - lib/hamlit/parser/haml_compiler.rb
355
356
  - lib/hamlit/parser/haml_error.rb