haml 5.2.2 → 6.1.1

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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/workflows/test.yml +13 -14
  4. data/.gitignore +16 -16
  5. data/.yardopts +0 -3
  6. data/CHANGELOG.md +116 -3
  7. data/Gemfile +18 -11
  8. data/MIT-LICENSE +1 -1
  9. data/README.md +17 -23
  10. data/REFERENCE.md +69 -145
  11. data/Rakefile +48 -81
  12. data/bin/bench +66 -0
  13. data/bin/console +11 -0
  14. data/bin/ruby +3 -0
  15. data/bin/setup +7 -0
  16. data/bin/stackprof +27 -0
  17. data/bin/test +24 -0
  18. data/exe/haml +6 -0
  19. data/ext/haml/extconf.rb +10 -0
  20. data/ext/haml/haml.c +537 -0
  21. data/ext/haml/hescape.c +108 -0
  22. data/ext/haml/hescape.h +20 -0
  23. data/haml.gemspec +39 -37
  24. data/lib/haml/ambles.rb +20 -0
  25. data/lib/haml/attribute_builder.rb +134 -179
  26. data/lib/haml/attribute_compiler.rb +85 -194
  27. data/lib/haml/attribute_parser.rb +92 -126
  28. data/lib/haml/cli.rb +154 -0
  29. data/lib/haml/compiler/children_compiler.rb +155 -0
  30. data/lib/haml/compiler/comment_compiler.rb +51 -0
  31. data/lib/haml/compiler/doctype_compiler.rb +46 -0
  32. data/lib/haml/compiler/script_compiler.rb +114 -0
  33. data/lib/haml/compiler/silent_script_compiler.rb +24 -0
  34. data/lib/haml/compiler/tag_compiler.rb +76 -0
  35. data/lib/haml/compiler.rb +63 -296
  36. data/lib/haml/dynamic_merger.rb +67 -0
  37. data/lib/haml/engine.rb +48 -227
  38. data/lib/haml/error.rb +5 -4
  39. data/lib/haml/escape.rb +13 -0
  40. data/lib/haml/escape_any.rb +21 -0
  41. data/lib/haml/filters/base.rb +12 -0
  42. data/lib/haml/filters/cdata.rb +20 -0
  43. data/lib/haml/filters/coffee.rb +17 -0
  44. data/lib/haml/filters/css.rb +33 -0
  45. data/lib/haml/filters/erb.rb +10 -0
  46. data/lib/haml/filters/escaped.rb +22 -0
  47. data/lib/haml/filters/javascript.rb +33 -0
  48. data/lib/haml/filters/less.rb +20 -0
  49. data/lib/haml/filters/markdown.rb +11 -0
  50. data/lib/haml/filters/plain.rb +29 -0
  51. data/lib/haml/filters/preserve.rb +22 -0
  52. data/lib/haml/filters/ruby.rb +10 -0
  53. data/lib/haml/filters/sass.rb +15 -0
  54. data/lib/haml/filters/scss.rb +15 -0
  55. data/lib/haml/filters/text_base.rb +25 -0
  56. data/lib/haml/filters/tilt_base.rb +59 -0
  57. data/lib/haml/filters.rb +54 -378
  58. data/lib/haml/force_escape.rb +29 -0
  59. data/lib/haml/helpers.rb +3 -697
  60. data/lib/haml/html.rb +22 -0
  61. data/lib/haml/identity.rb +13 -0
  62. data/lib/haml/object_ref.rb +35 -0
  63. data/lib/haml/parser.rb +157 -22
  64. data/lib/haml/rails_helpers.rb +53 -0
  65. data/lib/haml/rails_template.rb +57 -0
  66. data/lib/haml/railtie.rb +3 -46
  67. data/lib/haml/ruby_expression.rb +32 -0
  68. data/lib/haml/string_splitter.rb +140 -0
  69. data/lib/haml/template.rb +15 -34
  70. data/lib/haml/temple_line_counter.rb +2 -1
  71. data/lib/haml/util.rb +18 -15
  72. data/lib/haml/version.rb +1 -2
  73. data/lib/haml/whitespace.rb +8 -0
  74. data/lib/haml.rb +8 -20
  75. metadata +211 -55
  76. data/.gitmodules +0 -3
  77. data/TODO +0 -24
  78. data/benchmark.rb +0 -70
  79. data/bin/haml +0 -9
  80. data/lib/haml/.gitattributes +0 -1
  81. data/lib/haml/buffer.rb +0 -182
  82. data/lib/haml/escapable.rb +0 -77
  83. data/lib/haml/exec.rb +0 -347
  84. data/lib/haml/generator.rb +0 -42
  85. data/lib/haml/helpers/action_view_extensions.rb +0 -60
  86. data/lib/haml/helpers/action_view_mods.rb +0 -132
  87. data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
  88. data/lib/haml/helpers/safe_erubi_template.rb +0 -20
  89. data/lib/haml/helpers/safe_erubis_template.rb +0 -33
  90. data/lib/haml/helpers/xss_mods.rb +0 -114
  91. data/lib/haml/options.rb +0 -273
  92. data/lib/haml/plugin.rb +0 -54
  93. data/lib/haml/sass_rails_filter.rb +0 -47
  94. data/lib/haml/template/options.rb +0 -27
  95. data/lib/haml/temple_engine.rb +0 -124
  96. data/yard/default/.gitignore +0 -1
  97. data/yard/default/fulldoc/html/css/common.sass +0 -15
  98. data/yard/default/layout/html/footer.erb +0 -12
data/haml.gemspec CHANGED
@@ -1,45 +1,47 @@
1
- ($LOAD_PATH << File.expand_path("../lib", __FILE__)).uniq!
2
- require "haml/version"
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'haml/version'
3
5
 
4
6
  Gem::Specification.new do |spec|
5
- spec.name = 'haml'
6
- spec.summary = "An elegant, structured (X)HTML/XML templating engine."
7
- spec.version = Haml::VERSION
8
- spec.authors = ['Natalie Weizenbaum', 'Hampton Catlin', 'Norman Clarke', 'Akira Matsuda']
9
- spec.email = ['haml@googlegroups.com', 'ronnie@dio.jp']
7
+ spec.name = 'haml'
8
+ spec.version = Haml::VERSION
9
+ spec.authors = ['Natalie Weizenbaum', 'Hampton Catlin', 'Norman Clarke', 'Akira Matsuda', 'Takashi Kokubun']
10
+ spec.email = ['haml@googlegroups.com', 'ronnie@dio.jp']
10
11
 
11
- spec.executables = ['haml']
12
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
13
- f.match(%r{\Atest/})
14
- end
15
- spec.homepage = 'http://haml.info/'
16
- spec.license = "MIT"
17
- spec.metadata = {
18
- "bug_tracker_uri" => "https://github.com/haml/haml/issues",
19
- "changelog_uri" => "https://github.com/haml/haml/blob/main/CHANGELOG.md",
20
- "documentation_uri" => "http://haml.info/docs.html",
21
- "homepage_uri" => "http://haml.info",
22
- "mailing_list_uri" => "https://groups.google.com/forum/?fromgroups#!forum/haml",
23
- "source_code_uri" => "https://github.com/haml/haml"
24
- }
25
-
26
- spec.required_ruby_version = '>= 2.0.0'
12
+ spec.summary = %q{An elegant, structured (X)HTML/XML templating engine.}
13
+ spec.description = %q{An elegant, structured (X)HTML/XML templating engine.}
14
+ spec.homepage = 'https://haml.info'
15
+ spec.license = 'MIT'
27
16
 
28
- spec.add_dependency 'temple', '>= 0.8.0'
29
- spec.add_dependency 'tilt'
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|sample|benchmark)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
30
21
 
31
- spec.add_development_dependency 'rails', '>= 4.0.0'
32
- spec.add_development_dependency 'rbench'
33
- spec.add_development_dependency 'minitest', '>= 4.0'
34
- spec.add_development_dependency 'nokogiri'
35
- spec.add_development_dependency 'simplecov'
22
+ if /java/ === RUBY_PLATFORM
23
+ spec.platform = 'java'
24
+ else
25
+ spec.extensions = ['ext/haml/extconf.rb']
26
+ spec.required_ruby_version = '>= 2.1.0'
27
+ end
36
28
 
37
- spec.description = <<-END
38
- Haml (HTML Abstraction Markup Language) is a layer on top of HTML or XML that's
39
- designed to express the structure of documents in a non-repetitive, elegant, and
40
- easy way by using indentation rather than closing tags and allowing Ruby to be
41
- embedded with ease. It was originally envisioned as a plugin for Ruby on Rails,
42
- but it can function as a stand-alone templating engine.
43
- END
29
+ spec.add_dependency 'temple', '>= 0.8.2'
30
+ spec.add_dependency 'thor'
31
+ spec.add_dependency 'tilt'
44
32
 
33
+ spec.add_development_dependency 'benchmark_driver'
34
+ spec.add_development_dependency 'bundler'
35
+ spec.add_development_dependency 'coffee-script'
36
+ spec.add_development_dependency 'erubi'
37
+ spec.add_development_dependency 'haml', '>= 5'
38
+ spec.add_development_dependency 'less'
39
+ spec.add_development_dependency 'minitest-reporters', '~> 1.1'
40
+ spec.add_development_dependency 'rails', '>= 4.0'
41
+ spec.add_development_dependency 'rake'
42
+ spec.add_development_dependency 'rake-compiler'
43
+ spec.add_development_dependency 'sass'
44
+ spec.add_development_dependency 'slim'
45
+ spec.add_development_dependency 'string_template'
46
+ spec.add_development_dependency 'unindent'
45
47
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ module Haml
3
+ class Ambles < Temple::Filter
4
+ define_options :preamble, :postamble
5
+
6
+ def initialize(*)
7
+ super
8
+ @preamble = options[:preamble]
9
+ @postamble = options[:postamble]
10
+ end
11
+
12
+ def call(ast)
13
+ ret = [:multi]
14
+ ret << [:static, @preamble] if @preamble
15
+ ret << ast
16
+ ret << [:static, @postamble] if @postamble
17
+ ret
18
+ end
19
+ end
20
+ end
@@ -1,219 +1,174 @@
1
1
  # frozen_string_literal: true
2
-
3
- module Haml
4
- module AttributeBuilder
5
- # https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
6
- INVALID_ATTRIBUTE_NAME_REGEX = /[ \0"'>\/=]/
7
-
2
+ require 'haml/object_ref'
3
+
4
+ module Haml::AttributeBuilder
5
+ BOOLEAN_ATTRIBUTES = %w[disabled readonly multiple checked autobuffer
6
+ autoplay controls loop selected hidden scoped async
7
+ defer reversed ismap seamless muted required
8
+ autofocus novalidate formnovalidate open pubdate
9
+ itemscope allowfullscreen default inert sortable
10
+ truespeed typemustmatch download].freeze
11
+
12
+ # For JRuby, TruffleRuby, and Wasm, fallback to Ruby implementation.
13
+ if /java|wasm/ === RUBY_PLATFORM || RUBY_ENGINE == 'truffleruby'
8
14
  class << self
9
- def build(class_id, obj_ref, is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, *attributes_hashes)
10
- attributes = class_id
11
- attributes_hashes.each do |old|
12
- result = {}
13
- old.each { |k, v| result[k.to_s] = v }
14
- merge_attributes!(attributes, result)
15
+ def build(escape_attrs, quote, format, boolean_attributes, object_ref, *hashes)
16
+ hashes << Haml::ObjectRef.parse(object_ref) if object_ref
17
+ buf = []
18
+ hash = merge_all_attrs(hashes)
19
+
20
+ keys = hash.keys.sort!
21
+ keys.each do |key|
22
+ case key
23
+ when 'id'.freeze
24
+ buf << " id=#{quote}#{build_id(escape_attrs, *hash[key])}#{quote}"
25
+ when 'class'.freeze
26
+ buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}"
27
+ when 'data'.freeze
28
+ buf << build_data(escape_attrs, quote, *hash[key])
29
+ when *boolean_attributes, /\Adata-/
30
+ build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
31
+ else
32
+ buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
33
+ end
15
34
  end
16
- merge_attributes!(attributes, parse_object_ref(obj_ref)) if obj_ref
17
- build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes)
35
+ buf.join
18
36
  end
19
37
 
20
- def build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
21
- # @TODO this is an absolutely ridiculous amount of arguments. At least
22
- # some of this needs to be moved into an instance method.
23
- join_char = hyphenate_data_attrs ? '-' : '_'
38
+ def build_id(escape_attrs, *values)
39
+ escape_html(escape_attrs, values.flatten.select { |v| v }.join('_'))
40
+ end
24
41
 
25
- attributes.each do |key, value|
26
- if value.is_a?(Hash)
27
- data_attributes = attributes.delete(key)
28
- data_attributes = flatten_data_attributes(data_attributes, '', join_char)
29
- data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
30
- verify_attribute_names!(data_attributes.keys)
31
- attributes = data_attributes.merge(attributes)
42
+ def build_class(escape_attrs, *values)
43
+ if values.size == 1
44
+ value = values.first
45
+ case
46
+ when value.is_a?(String)
47
+ # noop
48
+ when value.is_a?(Array)
49
+ value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ')
50
+ when value
51
+ value = value.to_s
52
+ else
53
+ return ''
32
54
  end
55
+ return escape_html(escape_attrs, value)
33
56
  end
34
57
 
35
- result = attributes.collect do |attr, value|
36
- next if value.nil?
37
-
38
- value = filter_and_join(value, ' ') if attr == 'class'
39
- value = filter_and_join(value, '_') if attr == 'id'
40
-
41
- if value == true
42
- next " #{attr}" if is_html
43
- next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
44
- elsif value == false
45
- next
58
+ classes = []
59
+ values.each do |value|
60
+ case
61
+ when value.is_a?(String)
62
+ classes += value.split(' ')
63
+ when value.is_a?(Array)
64
+ classes += value.select { |v| v }
65
+ when value
66
+ classes << value.to_s
46
67
  end
47
-
48
- value =
49
- if escape_attrs == :once
50
- Haml::Helpers.escape_once_without_haml_xss(value.to_s)
51
- elsif escape_attrs
52
- Haml::Helpers.html_escape_without_haml_xss(value.to_s)
53
- else
54
- value.to_s
55
- end
56
- " #{attr}=#{attr_wrapper}#{value}#{attr_wrapper}"
57
68
  end
58
- result.compact!
59
- result.sort!
60
- result.join
69
+ escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
61
70
  end
62
71
 
63
- # @return [String, nil]
64
- def filter_and_join(value, separator)
65
- return '' if (value.respond_to?(:empty?) && value.empty?)
66
-
67
- if value.is_a?(Array)
68
- value = value.flatten
69
- value.map! {|item| item ? item.to_s : nil}
70
- value.compact!
71
- value = value.join(separator)
72
- else
73
- value = value ? value.to_s : nil
74
- end
75
- !value.nil? && !value.empty? && value
72
+ def build_data(escape_attrs, quote, *hashes)
73
+ build_data_attribute(:data, escape_attrs, quote, *hashes)
76
74
  end
77
75
 
78
- # Merges two attribute hashes.
79
- # This is the same as `to.merge!(from)`,
80
- # except that it merges id, class, and data attributes.
81
- #
82
- # ids are concatenated with `"_"`,
83
- # and classes are concatenated with `" "`.
84
- # data hashes are simply merged.
85
- #
86
- # Destructively modifies `to`.
87
- #
88
- # @param to [{String => String,Hash}] The attribute hash to merge into
89
- # @param from [{String => Object}] The attribute hash to merge from
90
- # @return [{String => String,Hash}] `to`, after being merged
91
- def merge_attributes!(to, from)
92
- from.keys.each do |key|
93
- to[key] = merge_value(key, to[key], from[key])
94
- end
95
- to
76
+ def build_aria(escape_attrs, quote, *hashes)
77
+ build_data_attribute(:aria, escape_attrs, quote, *hashes)
96
78
  end
97
79
 
98
- # Merge multiple values to one attribute value. No destructive operation.
99
- #
100
- # @param key [String]
101
- # @param values [Array<Object>]
102
- # @return [String,Hash]
103
- def merge_values(key, *values)
104
- values.inject(nil) do |to, from|
105
- merge_value(key, to, from)
106
- end
107
- end
80
+ private
108
81
 
109
- def verify_attribute_names!(attribute_names)
110
- attribute_names.each do |attribute_name|
111
- if attribute_name =~ INVALID_ATTRIBUTE_NAME_REGEX
112
- raise InvalidAttributeNameError.new("Invalid attribute name '#{attribute_name}' was rendered")
82
+ def build_data_attribute(key, escape_attrs, quote, *hashes)
83
+ attrs = []
84
+ if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) }
85
+ data_value = merge_all_attrs(hashes)
86
+ else
87
+ data_value = hashes.last
88
+ end
89
+ hash = flatten_attributes(key => data_value)
90
+
91
+ hash.sort_by(&:first).each do |key, value|
92
+ case value
93
+ when true
94
+ attrs << " #{key}"
95
+ when nil, false
96
+ # noop
97
+ else
98
+ attrs << " #{key}=#{quote}#{escape_html(escape_attrs, value.to_s)}#{quote}"
113
99
  end
114
100
  end
101
+ attrs.join
115
102
  end
116
103
 
117
- private
104
+ def flatten_attributes(attributes)
105
+ flattened = {}
118
106
 
119
- # Merge a couple of values to one attribute value. No destructive operation.
120
- #
121
- # @param to [String,Hash,nil]
122
- # @param from [Object]
123
- # @return [String,Hash]
124
- def merge_value(key, to, from)
125
- if from.kind_of?(Hash) || to.kind_of?(Hash)
126
- from = { nil => from } if !from.is_a?(Hash)
127
- to = { nil => to } if !to.is_a?(Hash)
128
- to.merge(from)
129
- elsif key == 'id'
130
- merged_id = filter_and_join(from, '_')
131
- if to && merged_id
132
- merged_id = "#{to}_#{merged_id}"
133
- elsif to || merged_id
134
- merged_id ||= to
135
- end
136
- merged_id
137
- elsif key == 'class'
138
- merged_class = filter_and_join(from, ' ')
139
- if to && merged_class
140
- merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
141
- elsif to || merged_class
142
- merged_class ||= to
107
+ attributes.each do |key, value|
108
+ case value
109
+ when attributes
110
+ when Hash
111
+ flatten_attributes(value).each do |k, v|
112
+ if k.nil?
113
+ flattened[key] = v
114
+ else
115
+ flattened["#{key}-#{k.to_s.gsub(/_/, '-')}"] = v
116
+ end
117
+ end
118
+ else
119
+ flattened[key] = value if value
143
120
  end
144
- merged_class
145
- else
146
- from
147
121
  end
122
+ flattened
148
123
  end
149
124
 
150
- def build_data_keys(data_hash, hyphenate, attr_name="data")
151
- Hash[data_hash.map do |name, value|
152
- if name == nil
153
- [attr_name, value]
154
- elsif hyphenate
155
- ["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
156
- else
157
- ["#{attr_name}-#{name}", value]
125
+ def merge_all_attrs(hashes)
126
+ merged = {}
127
+ hashes.each do |hash|
128
+ hash.each do |key, value|
129
+ key = key.to_s
130
+ case key
131
+ when 'id'.freeze, 'class'.freeze, 'data'.freeze
132
+ merged[key] ||= []
133
+ merged[key] << value
134
+ else
135
+ merged[key] = value
136
+ end
158
137
  end
159
- end]
160
- end
161
-
162
- def flatten_data_attributes(data, key, join_char, seen = [])
163
- return {key => data} unless data.is_a?(Hash)
164
-
165
- return {key => nil} if seen.include? data.object_id
166
- seen << data.object_id
167
-
168
- data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
169
- joined = key == '' ? k : [key, k].join(join_char)
170
- hash.merge! flatten_data_attributes(v, joined, join_char, seen)
171
138
  end
139
+ merged
172
140
  end
173
141
 
174
- # Takes an array of objects and uses the class and id of the first
175
- # one to create an attributes hash.
176
- # The second object, if present, is used as a prefix,
177
- # just like you can do with `dom_id()` and `dom_class()` in Rails
178
- def parse_object_ref(ref)
179
- prefix = ref[1]
180
- ref = ref[0]
181
- # Let's make sure the value isn't nil. If it is, return the default Hash.
182
- return {} if ref.nil?
183
- class_name =
184
- if ref.respond_to?(:haml_object_ref)
185
- ref.haml_object_ref
186
- else
187
- underscore(ref.class)
188
- end
189
- ref_id =
190
- if ref.respond_to?(:to_key)
191
- key = ref.to_key
192
- key.join('_') unless key.nil?
142
+ def build_boolean!(escape_attrs, quote, format, buf, key, value)
143
+ case value
144
+ when true
145
+ case format
146
+ when :xhtml
147
+ buf << " #{key}=#{quote}#{key}#{quote}"
193
148
  else
194
- ref.id
149
+ buf << " #{key}"
195
150
  end
196
- id = "#{class_name}_#{ref_id || 'new'}"
197
- if prefix
198
- class_name = "#{ prefix }_#{ class_name}"
199
- id = "#{ prefix }_#{ id }"
151
+ when false, nil
152
+ # omitted
153
+ else
154
+ buf << " #{key}=#{quote}#{escape_html(escape_attrs, value)}#{quote}"
200
155
  end
201
-
202
- { 'id'.freeze => id, 'class'.freeze => class_name }
203
156
  end
204
157
 
205
- # Changes a word from camel case to underscores.
206
- # Based on the method of the same name in Rails' Inflector,
207
- # but copied here so it'll run properly without Rails.
208
- def underscore(camel_cased_word)
209
- word = camel_cased_word.to_s.dup
210
- word.gsub!(/::/, '_')
211
- word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
212
- word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
213
- word.tr!('-', '_')
214
- word.downcase!
215
- word
158
+ def escape_html(escape_attrs, str)
159
+ if escape_attrs
160
+ Haml::Util.escape_html(str)
161
+ else
162
+ str
163
+ end
216
164
  end
217
165
  end
166
+ else
167
+ # Haml::AttributeBuilder.build
168
+ # Haml::AttributeBuilder.build_id
169
+ # Haml::AttributeBuilder.build_class
170
+ # Haml::AttributeBuilder.build_data
171
+ # Haml::AttributeBuilder.build_aria
172
+ require 'haml/haml'
218
173
  end
219
174
  end