haml 5.2.2 → 6.0.5

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +13 -9
  4. data/.gitignore +16 -16
  5. data/.yardopts +0 -3
  6. data/CHANGELOG.md +63 -3
  7. data/Gemfile +18 -11
  8. data/MIT-LICENSE +1 -1
  9. data/README.md +17 -23
  10. data/REFERENCE.md +32 -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 +126 -0
  30. data/lib/haml/compiler/comment_compiler.rb +39 -0
  31. data/lib/haml/compiler/doctype_compiler.rb +46 -0
  32. data/lib/haml/compiler/script_compiler.rb +116 -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 +44 -227
  38. data/lib/haml/error.rb +5 -4
  39. data/lib/haml/escapable.rb +6 -70
  40. data/lib/haml/filters/base.rb +12 -0
  41. data/lib/haml/filters/cdata.rb +20 -0
  42. data/lib/haml/filters/coffee.rb +17 -0
  43. data/lib/haml/filters/css.rb +33 -0
  44. data/lib/haml/filters/erb.rb +10 -0
  45. data/lib/haml/filters/escaped.rb +22 -0
  46. data/lib/haml/filters/javascript.rb +33 -0
  47. data/lib/haml/filters/less.rb +20 -0
  48. data/lib/haml/filters/markdown.rb +11 -0
  49. data/lib/haml/filters/plain.rb +29 -0
  50. data/lib/haml/filters/preserve.rb +22 -0
  51. data/lib/haml/filters/ruby.rb +10 -0
  52. data/lib/haml/filters/sass.rb +15 -0
  53. data/lib/haml/filters/scss.rb +15 -0
  54. data/lib/haml/filters/text_base.rb +25 -0
  55. data/lib/haml/filters/tilt_base.rb +49 -0
  56. data/lib/haml/filters.rb +54 -378
  57. data/lib/haml/force_escapable.rb +29 -0
  58. data/lib/haml/helpers.rb +3 -697
  59. data/lib/haml/html.rb +22 -0
  60. data/lib/haml/identity.rb +13 -0
  61. data/lib/haml/object_ref.rb +35 -0
  62. data/lib/haml/parser.rb +157 -22
  63. data/lib/haml/rails_helpers.rb +53 -0
  64. data/lib/haml/rails_template.rb +55 -0
  65. data/lib/haml/railtie.rb +3 -46
  66. data/lib/haml/ruby_expression.rb +32 -0
  67. data/lib/haml/string_splitter.rb +140 -0
  68. data/lib/haml/template.rb +15 -34
  69. data/lib/haml/temple_line_counter.rb +2 -1
  70. data/lib/haml/util.rb +17 -15
  71. data/lib/haml/version.rb +1 -2
  72. data/lib/haml.rb +8 -20
  73. metadata +208 -54
  74. data/.gitmodules +0 -3
  75. data/TODO +0 -24
  76. data/benchmark.rb +0 -70
  77. data/bin/haml +0 -9
  78. data/lib/haml/.gitattributes +0 -1
  79. data/lib/haml/buffer.rb +0 -182
  80. data/lib/haml/exec.rb +0 -347
  81. data/lib/haml/generator.rb +0 -42
  82. data/lib/haml/helpers/action_view_extensions.rb +0 -60
  83. data/lib/haml/helpers/action_view_mods.rb +0 -132
  84. data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
  85. data/lib/haml/helpers/safe_erubi_template.rb +0 -20
  86. data/lib/haml/helpers/safe_erubis_template.rb +0 -33
  87. data/lib/haml/helpers/xss_mods.rb +0 -114
  88. data/lib/haml/options.rb +0 -273
  89. data/lib/haml/plugin.rb +0 -54
  90. data/lib/haml/sass_rails_filter.rb +0 -47
  91. data/lib/haml/template/options.rb +0 -27
  92. data/lib/haml/temple_engine.rb +0 -124
  93. data/yard/default/.gitignore +0 -1
  94. data/yard/default/fulldoc/html/css/common.sass +0 -15
  95. 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,218 +1,173 @@
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
+ begin
13
+ # Haml::AttributeBuilder.build
14
+ # Haml::AttributeBuilder.build_id
15
+ # Haml::AttributeBuilder.build_class
16
+ # Haml::AttributeBuilder.build_data
17
+ # Haml::AttributeBuilder.build_aria
18
+ require 'haml/haml'
19
+ rescue LoadError
20
+ # For JRuby and Wasm, fallback to Ruby implementation when C extension is not available.
8
21
  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)
22
+ def build(escape_attrs, quote, format, boolean_attributes, object_ref, *hashes)
23
+ hashes << Haml::ObjectRef.parse(object_ref) if object_ref
24
+ buf = []
25
+ hash = merge_all_attrs(hashes)
26
+
27
+ keys = hash.keys.sort!
28
+ keys.each do |key|
29
+ case key
30
+ when 'id'.freeze
31
+ buf << " id=#{quote}#{build_id(escape_attrs, *hash[key])}#{quote}"
32
+ when 'class'.freeze
33
+ buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}"
34
+ when 'data'.freeze
35
+ buf << build_data(escape_attrs, quote, *hash[key])
36
+ when *boolean_attributes, /\Adata-/
37
+ build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
38
+ else
39
+ buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
40
+ end
15
41
  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)
42
+ buf.join
18
43
  end
19
44
 
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 ? '-' : '_'
45
+ def build_id(escape_attrs, *values)
46
+ escape_html(escape_attrs, values.flatten.select { |v| v }.join('_'))
47
+ end
24
48
 
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)
49
+ def build_class(escape_attrs, *values)
50
+ if values.size == 1
51
+ value = values.first
52
+ case
53
+ when value.is_a?(String)
54
+ # noop
55
+ when value.is_a?(Array)
56
+ value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ')
57
+ when value
58
+ value = value.to_s
59
+ else
60
+ return ''
32
61
  end
62
+ return escape_html(escape_attrs, value)
33
63
  end
34
64
 
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
65
+ classes = []
66
+ values.each do |value|
67
+ case
68
+ when value.is_a?(String)
69
+ classes += value.split(' ')
70
+ when value.is_a?(Array)
71
+ classes += value.select { |v| v }
72
+ when value
73
+ classes << value.to_s
46
74
  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
75
  end
58
- result.compact!
59
- result.sort!
60
- result.join
76
+ escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
61
77
  end
62
78
 
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
79
+ def build_data(escape_attrs, quote, *hashes)
80
+ build_data_attribute(:data, escape_attrs, quote, *hashes)
76
81
  end
77
82
 
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
83
+ def build_aria(escape_attrs, quote, *hashes)
84
+ build_data_attribute(:aria, escape_attrs, quote, *hashes)
96
85
  end
97
86
 
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
87
+ private
108
88
 
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")
89
+ def build_data_attribute(key, escape_attrs, quote, *hashes)
90
+ attrs = []
91
+ if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) }
92
+ data_value = merge_all_attrs(hashes)
93
+ else
94
+ data_value = hashes.last
95
+ end
96
+ hash = flatten_attributes(key => data_value)
97
+
98
+ hash.sort_by(&:first).each do |key, value|
99
+ case value
100
+ when true
101
+ attrs << " #{key}"
102
+ when nil, false
103
+ # noop
104
+ else
105
+ attrs << " #{key}=#{quote}#{escape_html(escape_attrs, value.to_s)}#{quote}"
113
106
  end
114
107
  end
108
+ attrs.join
115
109
  end
116
110
 
117
- private
111
+ def flatten_attributes(attributes)
112
+ flattened = {}
118
113
 
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
114
+ attributes.each do |key, value|
115
+ case value
116
+ when attributes
117
+ when Hash
118
+ flatten_attributes(value).each do |k, v|
119
+ if k.nil?
120
+ flattened[key] = v
121
+ else
122
+ flattened["#{key}-#{k.to_s.gsub(/_/, '-')}"] = v
123
+ end
124
+ end
125
+ else
126
+ flattened[key] = value if value
143
127
  end
144
- merged_class
145
- else
146
- from
147
128
  end
129
+ flattened
148
130
  end
149
131
 
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]
132
+ def merge_all_attrs(hashes)
133
+ merged = {}
134
+ hashes.each do |hash|
135
+ hash.each do |key, value|
136
+ key = key.to_s
137
+ case key
138
+ when 'id'.freeze, 'class'.freeze, 'data'.freeze
139
+ merged[key] ||= []
140
+ merged[key] << value
141
+ else
142
+ merged[key] = value
143
+ end
158
144
  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
145
  end
146
+ merged
172
147
  end
173
148
 
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?
149
+ def build_boolean!(escape_attrs, quote, format, buf, key, value)
150
+ case value
151
+ when true
152
+ case format
153
+ when :xhtml
154
+ buf << " #{key}=#{quote}#{key}#{quote}"
193
155
  else
194
- ref.id
156
+ buf << " #{key}"
195
157
  end
196
- id = "#{class_name}_#{ref_id || 'new'}"
197
- if prefix
198
- class_name = "#{ prefix }_#{ class_name}"
199
- id = "#{ prefix }_#{ id }"
158
+ when false, nil
159
+ # omitted
160
+ else
161
+ buf << " #{key}=#{quote}#{escape_html(escape_attrs, value)}#{quote}"
200
162
  end
201
-
202
- { 'id'.freeze => id, 'class'.freeze => class_name }
203
163
  end
204
164
 
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
165
+ def escape_html(escape_attrs, str)
166
+ if escape_attrs
167
+ Haml::Util.escape_html(str)
168
+ else
169
+ str
170
+ end
216
171
  end
217
172
  end
218
173
  end