haml 5.1.2 → 6.3.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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/workflows/test.yml +36 -0
  4. data/.gitignore +16 -15
  5. data/.yardopts +0 -3
  6. data/CHANGELOG.md +189 -1
  7. data/FAQ.md +1 -1
  8. data/Gemfile +20 -12
  9. data/MIT-LICENSE +1 -1
  10. data/README.md +10 -17
  11. data/REFERENCE.md +129 -164
  12. data/Rakefile +15 -89
  13. data/bin/bench +66 -0
  14. data/bin/console +11 -0
  15. data/bin/ruby +3 -0
  16. data/bin/setup +7 -0
  17. data/bin/stackprof +27 -0
  18. data/bin/test +24 -0
  19. data/exe/haml +6 -0
  20. data/haml.gemspec +34 -35
  21. data/lib/haml/ambles.rb +20 -0
  22. data/lib/haml/attribute_builder.rb +131 -133
  23. data/lib/haml/attribute_compiler.rb +91 -182
  24. data/lib/haml/attribute_parser.rb +92 -126
  25. data/lib/haml/cli.rb +154 -0
  26. data/lib/haml/compiler/children_compiler.rb +155 -0
  27. data/lib/haml/compiler/comment_compiler.rb +51 -0
  28. data/lib/haml/compiler/doctype_compiler.rb +52 -0
  29. data/lib/haml/compiler/script_compiler.rb +114 -0
  30. data/lib/haml/compiler/silent_script_compiler.rb +24 -0
  31. data/lib/haml/compiler/tag_compiler.rb +76 -0
  32. data/lib/haml/compiler.rb +63 -296
  33. data/lib/haml/dynamic_merger.rb +67 -0
  34. data/lib/haml/engine.rb +48 -227
  35. data/lib/haml/error.rb +5 -4
  36. data/lib/haml/escape.rb +13 -0
  37. data/lib/haml/escape_any.rb +21 -0
  38. data/lib/haml/filters/base.rb +12 -0
  39. data/lib/haml/filters/cdata.rb +20 -0
  40. data/lib/haml/filters/coffee.rb +17 -0
  41. data/lib/haml/filters/css.rb +33 -0
  42. data/lib/haml/filters/erb.rb +10 -0
  43. data/lib/haml/filters/escaped.rb +22 -0
  44. data/lib/haml/filters/javascript.rb +33 -0
  45. data/lib/haml/filters/less.rb +20 -0
  46. data/lib/haml/filters/markdown.rb +11 -0
  47. data/lib/haml/filters/plain.rb +29 -0
  48. data/lib/haml/filters/preserve.rb +22 -0
  49. data/lib/haml/filters/ruby.rb +10 -0
  50. data/lib/haml/filters/sass.rb +15 -0
  51. data/lib/haml/filters/scss.rb +15 -0
  52. data/lib/haml/filters/text_base.rb +25 -0
  53. data/lib/haml/filters/tilt_base.rb +59 -0
  54. data/lib/haml/filters.rb +54 -378
  55. data/lib/haml/force_escape.rb +29 -0
  56. data/lib/haml/helpers.rb +3 -691
  57. data/lib/haml/html.rb +22 -0
  58. data/lib/haml/identity.rb +13 -0
  59. data/lib/haml/object_ref.rb +35 -0
  60. data/lib/haml/parser.rb +190 -27
  61. data/lib/haml/rails_helpers.rb +53 -0
  62. data/lib/haml/rails_template.rb +62 -0
  63. data/lib/haml/railtie.rb +3 -41
  64. data/lib/haml/ruby_expression.rb +32 -0
  65. data/lib/haml/string_splitter.rb +140 -0
  66. data/lib/haml/template.rb +15 -34
  67. data/lib/haml/temple_line_counter.rb +2 -1
  68. data/lib/haml/util.rb +20 -16
  69. data/lib/haml/version.rb +1 -2
  70. data/lib/haml/whitespace.rb +8 -0
  71. data/lib/haml.rb +8 -20
  72. metadata +205 -53
  73. data/.gitmodules +0 -3
  74. data/.travis.yml +0 -97
  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 -238
  80. data/lib/haml/escapable.rb +0 -50
  81. data/lib/haml/exec.rb +0 -347
  82. data/lib/haml/generator.rb +0 -42
  83. data/lib/haml/helpers/action_view_extensions.rb +0 -60
  84. data/lib/haml/helpers/action_view_mods.rb +0 -132
  85. data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
  86. data/lib/haml/helpers/safe_erubi_template.rb +0 -20
  87. data/lib/haml/helpers/safe_erubis_template.rb +0 -33
  88. data/lib/haml/helpers/xss_mods.rb +0 -111
  89. data/lib/haml/options.rb +0 -273
  90. data/lib/haml/plugin.rb +0 -37
  91. data/lib/haml/sass_rails_filter.rb +0 -47
  92. data/lib/haml/template/options.rb +0 -27
  93. data/lib/haml/temple_engine.rb +0 -123
  94. data/yard/default/.gitignore +0 -1
  95. data/yard/default/fulldoc/html/css/common.sass +0 -15
  96. data/yard/default/layout/html/footer.erb +0 -12
data/bin/stackprof ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'hamlit'
5
+ require 'stackprof'
6
+
7
+ def open_flamegraph(report)
8
+ temp = `mktemp /tmp/stackflame-XXXXXXXX`.strip
9
+ data_path = "#{temp}.js"
10
+ system("mv #{temp} #{data_path}")
11
+
12
+ File.open(data_path, 'w') do |f|
13
+ report.print_flamegraph(f)
14
+ end
15
+
16
+ viewer_path = File.join(`bundle show stackprof`.strip, 'lib/stackprof/flamegraph/viewer.html')
17
+ url = "file://#{viewer_path}?data=#{data_path}"
18
+ system(%Q[osascript -e 'open location "#{url}"'])
19
+ end
20
+
21
+ haml = File.read(ARGV.first)
22
+ StackProf.start(mode: :wall, interval: 1, raw: false)
23
+ Hamlit::Engine.new.call(haml)
24
+ StackProf.stop
25
+
26
+ report = StackProf::Report.new(StackProf.results)
27
+ report.print_text(false)
data/bin/test ADDED
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+
3
+ VERSIONS=(
4
+ 2.1.10
5
+ 2.2.5
6
+ 2.3.1
7
+ )
8
+
9
+ set -e
10
+ trap 'echo "${VERSIONS[2]}" > .ruby-version' 0
11
+
12
+ function test_with() {
13
+ version=$1
14
+ rbenv local $version
15
+ if ! bundle check > /dev/null; then
16
+ bundle install
17
+ fi
18
+ ruby -v
19
+ bundle exec rake test
20
+ }
21
+
22
+ for version in ${VERSIONS[@]}; do
23
+ test_with $version
24
+ done
data/exe/haml ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path('../lib', __dir__)
4
+ require 'haml/cli'
5
+
6
+ Haml::CLI.start(ARGV)
data/haml.gemspec CHANGED
@@ -1,44 +1,43 @@
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/master/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
- }
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'
25
16
 
26
- spec.required_ruby_version = '>= 2.0.0'
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']
27
21
 
28
- spec.add_dependency 'temple', '>= 0.8.0'
29
- spec.add_dependency 'tilt'
22
+ spec.metadata = { 'rubygems_mfa_required' => 'true' }
30
23
 
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'
24
+ spec.required_ruby_version = '>= 2.1.0'
35
25
 
36
- spec.description = <<-END
37
- Haml (HTML Abstraction Markup Language) is a layer on top of HTML or XML that's
38
- designed to express the structure of documents in a non-repetitive, elegant, and
39
- easy way by using indentation rather than closing tags and allowing Ruby to be
40
- embedded with ease. It was originally envisioned as a plugin for Ruby on Rails,
41
- but it can function as a stand-alone templating engine.
42
- END
26
+ spec.add_dependency 'temple', '>= 0.8.2'
27
+ spec.add_dependency 'thor'
28
+ spec.add_dependency 'tilt'
43
29
 
30
+ spec.add_development_dependency 'benchmark_driver'
31
+ spec.add_development_dependency 'bundler'
32
+ spec.add_development_dependency 'coffee-script'
33
+ spec.add_development_dependency 'erubi'
34
+ spec.add_development_dependency 'haml', '>= 5'
35
+ spec.add_development_dependency 'less'
36
+ spec.add_development_dependency 'minitest-reporters', '~> 1.1'
37
+ spec.add_development_dependency 'rails', '>= 4.0'
38
+ spec.add_development_dependency 'rake'
39
+ spec.add_development_dependency 'sass'
40
+ spec.add_development_dependency 'slim'
41
+ spec.add_development_dependency 'string_template'
42
+ spec.add_development_dependency 'unindent'
44
43
  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,163 +1,161 @@
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
-
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
- Haml::Helpers.escape_once(value.to_s)
40
- elsif escape_attrs
41
- Haml::Helpers.html_escape(value.to_s)
42
- else
43
- value.to_s
44
- end
45
- " #{attr}=#{attr_wrapper}#{value}#{attr_wrapper}"
2
+ require 'haml/object_ref'
3
+
4
+ module Haml::AttributeBuilder
5
+ class << self
6
+ def build(escape_attrs, quote, format, object_ref, *hashes)
7
+ hashes << Haml::ObjectRef.parse(object_ref) if object_ref
8
+ buf = []
9
+ hash = merge_all_attrs(hashes)
10
+
11
+ keys = hash.keys.sort!
12
+ keys.each do |key|
13
+ case key
14
+ when 'id'
15
+ buf << " id=#{quote}#{build_id(escape_attrs, *hash[key])}#{quote}"
16
+ when 'class'
17
+ buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}"
18
+ when 'data'
19
+ buf << build_data(escape_attrs, quote, *hash[key])
20
+ when 'aria'
21
+ buf << build_aria(escape_attrs, quote, *hash[key])
22
+ when *Haml::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
23
+ build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
24
+ else
25
+ buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
46
26
  end
47
- result.compact!
48
- result.sort!
49
- result.join
50
27
  end
28
+ buf.join
29
+ end
51
30
 
52
- # @return [String, nil]
53
- def filter_and_join(value, separator)
54
- return '' if (value.respond_to?(:empty?) && value.empty?)
31
+ def build_id(escape_attrs, *values)
32
+ escape_html(escape_attrs, values.flatten.select { |v| v }.join('_'))
33
+ end
55
34
 
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)
35
+ def build_class(escape_attrs, *values)
36
+ if values.size == 1
37
+ value = values.first
38
+ case
39
+ when value.is_a?(String)
40
+ # noop
41
+ when value.is_a?(Array)
42
+ value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ')
43
+ when value
44
+ value = value.to_s
61
45
  else
62
- value = value ? value.to_s : nil
46
+ return ''
63
47
  end
64
- !value.nil? && !value.empty? && value
48
+ return escape_html(escape_attrs, value)
65
49
  end
66
50
 
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])
51
+ classes = []
52
+ values.each do |value|
53
+ case
54
+ when value.is_a?(String)
55
+ classes += value.split(' ')
56
+ when value.is_a?(Array)
57
+ classes += value.select { |v| v }
58
+ when value
59
+ classes << value.to_s
83
60
  end
84
- to
85
61
  end
62
+ escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
63
+ end
86
64
 
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
65
+ def build_data(escape_attrs, quote, *hashes)
66
+ build_data_attribute(:data, escape_attrs, quote, *hashes)
67
+ end
97
68
 
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
69
+ def build_aria(escape_attrs, quote, *hashes)
70
+ build_data_attribute(:aria, escape_attrs, quote, *hashes)
71
+ end
72
+
73
+ private
74
+
75
+ def build_data_attribute(key, escape_attrs, quote, *hashes)
76
+ attrs = []
77
+ if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) }
78
+ data_value = merge_all_attrs(hashes)
79
+ else
80
+ data_value = hashes.last
81
+ end
82
+ hash = flatten_attributes(key => data_value)
83
+
84
+ hash.sort_by(&:first).each do |key, value|
85
+ case value
86
+ when true
87
+ attrs << " #{key}"
88
+ when nil, false
89
+ # noop
90
+ else
91
+ attrs << " #{key}=#{quote}#{escape_html(escape_attrs, value.to_s)}#{quote}"
103
92
  end
104
93
  end
94
+ attrs.join
95
+ end
105
96
 
106
- private
97
+ def flatten_attributes(attributes)
98
+ flattened = {}
107
99
 
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 = (merged_class.split(' ') | to.split(' ')).sort.join(' ')
130
- elsif to || merged_class
131
- merged_class ||= to
100
+ attributes.each do |key, value|
101
+ case value
102
+ when attributes
103
+ when Hash
104
+ flatten_attributes(value).each do |k, v|
105
+ if k.nil?
106
+ flattened[key] = v
107
+ else
108
+ flattened["#{key}-#{k.to_s.gsub(/_/, '-')}"] = v
109
+ end
132
110
  end
133
- merged_class
134
111
  else
135
- from
112
+ flattened[key] = value if value
136
113
  end
137
114
  end
115
+ flattened
116
+ end
138
117
 
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]
118
+ def merge_all_attrs(hashes)
119
+ merged = {}
120
+ hashes.each do |hash|
121
+ unless hash.is_a?(Hash)
122
+ raise ArgumentError, "Non-hash object is given to attributes!"
123
+ end
124
+ hash.each do |key, value|
125
+ key = key.to_s
126
+ case key
127
+ when 'id', 'class', 'data', 'aria'
128
+ merged[key] ||= []
129
+ merged[key] << value
145
130
  else
146
- ["#{attr_name}-#{name}", value]
131
+ merged[key] = value
147
132
  end
148
- end]
133
+ end
149
134
  end
135
+ merged
136
+ end
150
137
 
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)
138
+ def build_boolean!(escape_attrs, quote, format, buf, key, value)
139
+ case value
140
+ when true
141
+ case format
142
+ when :xhtml
143
+ buf << " #{key}=#{quote}#{key}#{quote}"
144
+ else
145
+ buf << " #{key}"
160
146
  end
147
+ when false, nil
148
+ # omitted
149
+ else
150
+ buf << " #{key}=#{quote}#{escape_html(escape_attrs, value)}#{quote}"
151
+ end
152
+ end
153
+
154
+ def escape_html(escape_attrs, str)
155
+ if escape_attrs
156
+ Haml::Util.escape_html(str)
157
+ else
158
+ str
161
159
  end
162
160
  end
163
161
  end