haml 7.2.2 → 7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: def36fc38e0b0116356b085a4120ea0564c5624c766e03f705ec91d3657e2e8c
4
- data.tar.gz: 937230d9bbf1ad0cbad38457d985bd964a5edd49137f31398fafad0aa474a8bf
3
+ metadata.gz: 381430279e5ca21c3d03f899c686f972609e54633c8e530609511e852597b7ef
4
+ data.tar.gz: a108268dccda6dc0e3ca014e484a7bcc0f50c973771a0057a086d75de2847495
5
5
  SHA512:
6
- metadata.gz: f57d6abe6fdac34effcac8e52c889142828bf405f915239526fe6540ccb2fe138e79f972536e79a1c5dc7ab4de1871030900d0b2a0118325049d316bd5e534f7
7
- data.tar.gz: b9135ee6741e0105df6e5f4a92c5c3ea660e10be525aca983d2b0d7f509f80d61216fcc32d340f678c3e1ca7ab91da70d4a436a6c45ce6b956d48cf8105097c8
6
+ metadata.gz: b5f8d7bcc23e52be3fd4ed443ec3fdf171773f9c9aaacd1705d92ec09a3ef372adf13a8715ca4b4d039aa2a07fc2fdbb7e59eef30a22f55be2823bb0c7e23f0d
7
+ data.tar.gz: 954f3aa91fa3f49499d05dd1503056dd65a439519c5b2bcc300a6cf3dfbacf686717c8aa1e10ddb1ca7fa779504d92561a33e8a7d729b8013967cde102636fca
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Haml Changelog
2
2
 
3
+ ## 7.3.0
4
+
5
+ * Replace Ripper with Prism https://github.com/haml/haml/pull/1214
6
+ * Interpolate `#@ivar`, `#$gvar` and `#@@cvar` in string literals instead of dropping them
7
+ * Keep an escaped delimiter of a percent literal, so `= %q{a\}b}` renders `a}b`
8
+ * Deprecate `Haml::AttributeParser.available?`, which is now always true and will be removed in the future
9
+
3
10
  ## 7.2.2
4
11
 
5
12
  * Remove obsolete TruffleRuby compatibility skips https://github.com/haml/haml/pull/1210
data/haml.gemspec CHANGED
@@ -21,11 +21,14 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.metadata = { 'rubygems_mfa_required' => 'true' }
23
23
 
24
- spec.metadata["changelog_uri"] = "https://github.com/haml/haml/releases"
24
+ spec.metadata["bug_tracker_uri"] = "https://github.com/haml/haml/issues"
25
+ spec.metadata["changelog_uri"] = "https://github.com/haml/haml/blob/main/CHANGELOG.md"
26
+ spec.metadata["homepage_uri"] = "https://haml.info/"
25
27
  spec.metadata["source_code_uri"] = "https://github.com/haml/haml"
26
28
 
27
29
  spec.required_ruby_version = '>= 3.2.0'
28
30
 
31
+ spec.add_dependency 'prism', '>= 1.1.0'
29
32
  spec.add_dependency 'temple', '>= 0.8.2'
30
33
  spec.add_dependency 'thor'
31
34
  spec.add_dependency 'tilt'
@@ -22,9 +22,7 @@ module Haml
22
22
 
23
23
  def compile(node)
24
24
  hashes = []
25
- if node.value[:object_ref] != :nil || !AttributeParser.available?
26
- return runtime_compile(node)
27
- end
25
+ return runtime_compile(node) if node.value[:object_ref] != :nil
28
26
  [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
29
27
  hash = AttributeParser.parse(attribute_str)
30
28
  return runtime_compile(node) if hash.nil? || hash.any? { |_key, value| value.empty? }
@@ -1,33 +1,40 @@
1
1
  # frozen_string_literal: true
2
- require 'haml/ruby_expression'
2
+ require 'prism'
3
3
 
4
4
  module Haml
5
5
  class AttributeParser
6
- class ParseSkip < StandardError
7
- end
8
-
9
- # @return [TrueClass, FalseClass] - return true if AttributeParser.parse can be used.
6
+ # @deprecated Prism is a hard dependency, so this is always true. Haml itself no longer
7
+ # asks, and this will be removed in the future.
8
+ # @return [TrueClass] - return true if AttributeParser.parse can be used.
10
9
  def self.available?
11
- Temple::StaticAnalyzer.available?
10
+ true
12
11
  end
13
12
 
14
13
  def self.parse(text)
15
14
  self.new.parse(text)
16
15
  end
17
16
 
17
+ # @return [Hash,nil] - keys and values are the attribute source as written, or nil if
18
+ # the text is not a Hash literal whose keys are all static.
18
19
  def parse(text)
19
20
  exp = wrap_bracket(text)
20
- return if Temple::StaticAnalyzer.syntax_error?(exp)
21
+ # A multi-line hash is left to the runtime, which keeps the [:newline] bookkeeping of
22
+ # the compiled code correct. Compiling it statically is a separate optimization.
23
+ return if exp.include?("\n")
24
+
25
+ node = hash_node(exp)
26
+ return if node.nil?
21
27
 
22
28
  hash = {}
23
- tokens = Ripper.lex(exp)[1..-2] || []
24
- each_attr(tokens) do |attr_tokens|
25
- key = parse_key!(attr_tokens)
26
- hash[key] = attr_tokens.map { |t| t[2] }.join.strip
29
+ node.elements.each do |element|
30
+ return unless element.is_a?(Prism::AssocNode)
31
+
32
+ key = static_key(element.key)
33
+ return if key.nil?
34
+
35
+ hash[key] = value_source(element.value)
27
36
  end
28
37
  hash
29
- rescue ParseSkip
30
- nil
31
38
  end
32
39
 
33
40
  private
@@ -38,78 +45,34 @@ module Haml
38
45
  "{#{text}}"
39
46
  end
40
47
 
41
- def parse_key!(tokens)
42
- _, type, str = tokens.shift
43
- case type
44
- when :on_sp
45
- parse_key!(tokens)
46
- when :on_label
47
- str.tr(':', '')
48
- when :on_symbeg
49
- _, _, key = tokens.shift
50
- assert_type!(tokens.shift, :on_tstring_end) if str != ':'
51
- skip_until_hash_rocket!(tokens)
52
- key
53
- when :on_tstring_beg
54
- _, _, key = tokens.shift
55
- next_token = tokens.shift
56
- unless next_token[1] == :on_label_end
57
- assert_type!(next_token, :on_tstring_end)
58
- skip_until_hash_rocket!(tokens)
59
- end
60
- key
61
- else
62
- raise ParseSkip
63
- end
64
- end
48
+ def hash_node(exp)
49
+ # partial_script, because an attribute may legitimately call `yield` and the like:
50
+ # a template is compiled into a method body.
51
+ result = Prism.parse(exp, partial_script: true)
52
+ return if result.failure?
53
+
54
+ statements = result.value.statements.body
55
+ return if statements.size != 1
65
56
 
66
- def assert_type!(token, type)
67
- raise ParseSkip if token[1] != type
57
+ node = statements.first
58
+ node if node.is_a?(Prism::HashNode)
68
59
  end
69
60
 
70
- def skip_until_hash_rocket!(tokens)
71
- until tokens.empty?
72
- _, type, str = tokens.shift
73
- break if type == :on_op && str == '=>'
61
+ # The key as written between its delimiters, not unescaped: an escape has to reach the
62
+ # attribute name as the source spelled it, like the `\0` of `{ "a\0b" => 1 }`.
63
+ def static_key(key)
64
+ case key
65
+ when Prism::SymbolNode then key.value_loc&.slice
66
+ when Prism::StringNode then key.content_loc&.slice
74
67
  end
75
68
  end
76
69
 
77
- def each_attr(tokens)
78
- attr_tokens = []
79
- open_tokens = Hash.new { |h, k| h[k] = 0 }
70
+ def value_source(value)
71
+ # Ruby 3.1 value omission (`{ foo: }`). An empty value tells AttributeCompiler to
72
+ # fall back to the runtime, which is what resolves it.
73
+ return '' if value.is_a?(Prism::ImplicitNode)
80
74
 
81
- tokens.each do |token|
82
- _, type, _ = token
83
- case type
84
- when :on_comma
85
- if open_tokens.values.all?(&:zero?)
86
- yield(attr_tokens)
87
- attr_tokens = []
88
- next
89
- end
90
- when :on_lbracket
91
- open_tokens[:array] += 1
92
- when :on_rbracket
93
- open_tokens[:array] -= 1
94
- when :on_lbrace
95
- open_tokens[:block] += 1
96
- when :on_rbrace
97
- open_tokens[:block] -= 1
98
- when :on_lparen
99
- open_tokens[:paren] += 1
100
- when :on_rparen
101
- open_tokens[:paren] -= 1
102
- when :on_embexpr_beg
103
- open_tokens[:embexpr] += 1
104
- when :on_embexpr_end
105
- open_tokens[:embexpr] -= 1
106
- when :on_sp
107
- next if attr_tokens.empty?
108
- end
109
-
110
- attr_tokens << token
111
- end
112
- yield(attr_tokens) unless attr_tokens.empty?
75
+ value.slice
113
76
  end
114
77
  end
115
78
  end
@@ -39,8 +39,13 @@ module Haml
39
39
  # String-interpolated plain text must be compiled with this method
40
40
  # because we have to escape only interpolated values.
41
41
  def compile_interpolated_plain(node)
42
+ compiled = StringSplitter.try_compile(node.value[:text])
43
+ # Ruby that does not parse: let the generated code report it, rather than failing
44
+ # the whole compilation with an error that points at Haml instead of the template.
45
+ return delegate_optimization(node) if compiled.nil?
46
+
42
47
  temple = [:multi]
43
- StringSplitter.compile(node.value[:text]).each do |type, value|
48
+ compiled.each do |type, value|
44
49
  case type
45
50
  when :static
46
51
  temple << [:static, value]
@@ -53,8 +53,13 @@ module Haml
53
53
 
54
54
  # We should handle interpolation here to escape only interpolated values.
55
55
  def compile_interpolated_plain(node)
56
+ compiled = StringSplitter.try_compile(node.value[:value])
57
+ # Ruby that does not parse: let the generated code report it, rather than failing
58
+ # the whole compilation with an error that points at Haml instead of the template.
59
+ return delegate_optimization(node) if compiled.nil?
60
+
56
61
  temple = [:multi]
57
- StringSplitter.compile(node.value[:value]).each do |type, value|
62
+ compiled.each do |type, value|
58
63
  case type
59
64
  when :static
60
65
  temple << [:static, value]
@@ -14,7 +14,12 @@ module Haml
14
14
 
15
15
  def compile_plain(text)
16
16
  string_literal = ::Haml::Util.unescape_interpolation(text)
17
- StringSplitter.compile(string_literal).map do |temple|
17
+ compiled = StringSplitter.try_compile(string_literal)
18
+ # Ruby that does not parse: let the generated code report it against the template,
19
+ # rather than failing the whole compilation.
20
+ return [[:escape, false, [:dynamic, string_literal]]] if compiled.nil?
21
+
22
+ compiled.map do |temple|
18
23
  type, str = temple
19
24
  case type
20
25
  when :dynamic
data/lib/haml/parser.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ripper'
3
+ require 'prism'
4
4
  require 'strscan'
5
5
  require 'haml/error'
6
6
  require 'haml/util'
@@ -601,8 +601,7 @@ module Haml
601
601
  attributes
602
602
  end
603
603
 
604
- # This method doesn't use Haml::HamlAttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
605
- # Ideally this logic should be placed in Haml::HamlAttributeParser instead of here and this method should use it.
604
+ # Ideally this logic should be placed in Haml::AttributeParser instead of here and this method should use it.
606
605
  #
607
606
  # @param [String] text - Hash literal or text inside old attributes
608
607
  # @return [Hash,nil] - Return nil if text is not static Hash literal
@@ -686,17 +685,17 @@ module Haml
686
685
  # Old attributes often look like a valid Hash literal, but it sometimes allow code like
687
686
  # `{ hash, foo: bar }`, which is compiled to `_hamlout.attributes({}, nil, hash, foo: bar)`.
688
687
  #
689
- # To scan such code correctly, this scans `a( hash, foo: bar }` instead, stops when there is
690
- # 1 more :on_embexpr_end (the last '}') than :on_embexpr_beg, and resurrects '{' afterwards.
688
+ # To scan such code correctly, this scans `a( hash, foo: bar }` instead, stops on the '}'
689
+ # that closes one more brace than was opened, and resurrects '{' afterwards.
691
690
  #
692
691
  # Old attributes allow invalid Hash constructs (e.g., `{ hash, foo: bar }`).
693
692
  # Replacing the opening `{` with `a(` makes the syntax look like a method call so
694
- # Ripper can lex it reliably.
693
+ # it can be lexed reliably.
695
694
 
696
695
  attributes_hash, rest = balance_tokens(
697
696
  text.sub(?{, METHOD_CALL_PREFIX),
698
- [:on_lbrace, :on_tlambeg, :on_embexpr_beg],
699
- [:on_rbrace, :on_embexpr_end],
697
+ [:BRACE_LEFT, :LAMBDA_BEGIN, :EMBEXPR_BEGIN],
698
+ [:BRACE_RIGHT, :EMBEXPR_END],
700
699
  count: 1)
701
700
  attributes_hash = attributes_hash.sub(METHOD_CALL_PREFIX, ?{)
702
701
  rescue SyntaxError => e
@@ -854,19 +853,23 @@ module Haml
854
853
  Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
855
854
  end
856
855
 
857
- # Unlike #balance, this balances Ripper tokens to balance something like `{ a: "}" }` correctly.
856
+ # Unlike #balance, this balances lexed tokens to balance something like `{ a: "}" }` correctly.
858
857
  def balance_tokens(buf, start, finish, count: 0)
859
- text = ''.dup
860
- Ripper.lex(buf).each do |_, token, str|
861
- text << str
862
- if start.include?(token)
858
+ Prism.lex(buf).value.each do |token, _|
859
+ type = token.type
860
+ next if type == :EOF
861
+
862
+ if start.include?(type)
863
863
  count += 1
864
- elsif finish.include?(token)
864
+ elsif finish.include?(type)
865
865
  count -= 1
866
866
  end
867
867
 
868
868
  if count == 0
869
- return text, buf.sub(text, '')
869
+ # Splitting on the offset instead of the tokens seen so far: whitespace and comments
870
+ # are not tokens, so the text cannot be rebuilt by concatenating them.
871
+ offset = token.location.end_offset
872
+ return buf.byteslice(0, offset), buf.byteslice(offset..)
870
873
  end
871
874
  end
872
875
  raise SyntaxError.new(Error.message(:unbalanced_brackets))
@@ -1,32 +1,39 @@
1
1
  # frozen_string_literal: true
2
- require 'ripper'
2
+ require 'prism'
3
3
 
4
4
  module Haml
5
- class RubyExpression < Ripper
6
- class ParseError < StandardError; end
5
+ class RubyExpression
6
+ # A character literal (`?a`) is a StringNode for Prism, but it has no quotes
7
+ # to split on, so it must not be reported as a string literal.
8
+ CHAR_LITERAL_OPENING = '?'
9
+
10
+ # A template is compiled into a method body, so a jump like `yield` is valid here even
11
+ # though it would not be at the top level of a script.
12
+ PARSE_OPTIONS = { partial_script: true }.freeze
7
13
 
8
14
  def self.syntax_error?(code)
9
- self.new(code).parse
10
- false
11
- rescue ParseError
12
- true
15
+ Prism.parse_failure?(code, **PARSE_OPTIONS)
13
16
  end
14
17
 
15
18
  def self.string_literal?(code)
16
- return false if syntax_error?(code)
17
-
18
- type, instructions = Ripper.sexp(code)
19
- return false if type != :program
20
- return false if instructions.size > 1
21
-
22
- type, _ = instructions.first
23
- type == :string_literal
19
+ !string_literal_node(code).nil?
24
20
  end
25
21
 
26
- private
22
+ # @return [Prism::Node, nil] - the node of a string literal StringSplitter can split, if any.
23
+ # Its locations are byte offsets into `code` as given, so `code` must not be stripped here.
24
+ def self.string_literal_node(code)
25
+ result = Prism.parse(code, **PARSE_OPTIONS)
26
+ return if result.failure?
27
+
28
+ statements = result.value.statements.body
29
+ return if statements.size > 1
27
30
 
28
- def on_parse_error(*)
29
- raise ParseError
31
+ case (node = statements.first)
32
+ when Prism::StringNode, Prism::InterpolatedStringNode
33
+ # Adjacent concatenation (`"a" "b"`) is one node without an opening
34
+ # delimiter of its own, and each of its parts keeps its own quotes.
35
+ node if !node.opening.nil? && node.opening != CHAR_LITERAL_OPENING
36
+ end
30
37
  end
31
38
  end
32
39
  end
@@ -1,92 +1,69 @@
1
1
  # frozen_string_literal: true
2
- require 'ripper'
2
+ require 'prism'
3
+ require 'haml/ruby_expression'
3
4
 
4
5
  module Haml
5
6
  # Compile [:dynamic, "foo#{bar}"] to [:multi, [:static, 'foo'], [:dynamic, 'bar']]
6
7
  class StringSplitter < Temple::Filter
7
8
  class << self
8
- # `code` param must be valid string literal
9
- def compile(code)
10
- [].tap do |exps|
11
- tokens = Ripper.lex(code.strip)
12
- tokens.pop while tokens.last && [:on_comment, :on_sp].include?(tokens.last[1])
13
-
14
- if tokens.size < 2
15
- raise(Haml::InternalError, "Expected token size >= 2 but got: #{tokens.size}")
16
- end
17
- compile_tokens!(exps, tokens)
9
+ # `code` param must be a string literal, as RubyExpression.string_literal? defines it.
10
+ def compile(code, node: RubyExpression.string_literal_node(code))
11
+ case node
12
+ when Prism::StringNode
13
+ node.unescaped.empty? ? [] : [[:static, node.unescaped]]
14
+ when Prism::InterpolatedStringNode
15
+ compile_parts(node.parts, code)
16
+ else
17
+ raise(Haml::InternalError, "Expected a string literal but got: #{code}")
18
18
  end
19
19
  end
20
20
 
21
- private
22
-
23
- def strip_quotes!(tokens)
24
- _, type, beg_str = tokens.shift
25
- if type != :on_tstring_beg
26
- raise(Haml::InternalError, "Expected :on_tstring_beg but got: #{type}")
27
- end
28
-
29
- _, type, end_str = tokens.pop
30
- if type != :on_tstring_end
31
- raise(Haml::InternalError, "Expected :on_tstring_end but got: #{type}")
32
- end
33
-
34
- [beg_str, end_str]
21
+ # Like compile, but nil instead of raising, for callers that built `code` themselves
22
+ # and would rather emit it untouched than fail the whole compilation.
23
+ def try_compile(code)
24
+ node = RubyExpression.string_literal_node(code)
25
+ compile(code, node: node) unless node.nil?
35
26
  end
36
27
 
37
- def compile_tokens!(exps, tokens)
38
- beg_str, end_str = strip_quotes!(tokens)
39
-
40
- until tokens.empty?
41
- _, type, str = tokens.shift
28
+ private
42
29
 
43
- case type
44
- when :on_tstring_content
45
- beg_str, end_str = escape_quotes(beg_str, end_str)
46
- exps << [:static, eval("#{beg_str}#{str}#{end_str}").to_s]
47
- when :on_embexpr_beg
48
- embedded = shift_balanced_embexpr(tokens)
49
- exps << [:dynamic, embedded] unless embedded.empty?
30
+ def compile_parts(parts, code)
31
+ [].tap do |exps|
32
+ parts.each do |part|
33
+ case part
34
+ when Prism::StringNode
35
+ content = part.unescaped
36
+ exps << [:static, content] unless content.empty?
37
+ when Prism::EmbeddedStatementsNode
38
+ embedded = embedded_source(part, code)
39
+ exps << [:dynamic, embedded] unless embedded.empty?
40
+ when Prism::EmbeddedVariableNode
41
+ exps << [:dynamic, part.variable.slice]
42
+ else
43
+ # Prism allows more part types than a string literal can currently hold. Dropping
44
+ # one would silently lose content, so fail instead of rendering something wrong.
45
+ raise(Haml::InternalError, "Unexpected #{part.class} in string literal: #{code}")
46
+ end
50
47
  end
51
48
  end
52
49
  end
53
50
 
54
- # Some quotes are split-unsafe. Replace such quotes with null characters.
55
- def escape_quotes(beg_str, end_str)
56
- case [beg_str[-1], end_str]
57
- when ['(', ')'], ['[', ']'], ['{', '}']
58
- [beg_str.sub(/.\z/) { "\0" }, "\0"]
59
- else
60
- [beg_str, end_str]
61
- end
62
- end
63
-
64
- def shift_balanced_embexpr(tokens)
65
- String.new.tap do |embedded|
66
- embexpr_open = 1
67
-
68
- until tokens.empty?
69
- _, type, str = tokens.shift
70
- case type
71
- when :on_embexpr_beg
72
- embexpr_open += 1
73
- when :on_embexpr_end
74
- embexpr_open -= 1
75
- break if embexpr_open == 0
76
- end
77
-
78
- embedded << str
79
- end
80
- end
51
+ # The source between `#{` and `}`, kept verbatim. Slicing `code` rather than
52
+ # using the statements' own source preserves the whitespace around them.
53
+ def embedded_source(part, code)
54
+ from = part.opening_loc.end_offset
55
+ code.byteslice(from, part.closing_loc.start_offset - from)
81
56
  end
82
57
  end
83
58
 
84
59
  def on_dynamic(code)
85
- return [:dynamic, code] unless string_literal?(code)
86
60
  return [:dynamic, code] if code.include?("\n")
87
61
 
62
+ node = RubyExpression.string_literal_node(code)
63
+ return [:dynamic, code] if node.nil?
64
+
88
65
  temple = [:multi]
89
- StringSplitter.compile(code).each do |type, content|
66
+ StringSplitter.compile(code, node: node).each do |type, content|
90
67
  case type
91
68
  when :static
92
69
  temple << [:static, content]
@@ -96,35 +73,5 @@ module Haml
96
73
  end
97
74
  temple
98
75
  end
99
-
100
- private
101
-
102
- def string_literal?(code)
103
- return false if SyntaxChecker.syntax_error?(code)
104
-
105
- type, instructions = Ripper.sexp(code)
106
- return false if type != :program
107
- return false if instructions.size > 1
108
-
109
- type, _ = instructions.first
110
- type == :string_literal
111
- end
112
-
113
- class SyntaxChecker < Ripper
114
- class ParseError < StandardError; end
115
-
116
- def self.syntax_error?(code)
117
- self.new(code).parse
118
- false
119
- rescue ParseError
120
- true
121
- end
122
-
123
- private
124
-
125
- def on_parse_error(*)
126
- raise ParseError
127
- end
128
- end
129
76
  end
130
77
  end
data/lib/haml/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Haml
3
- VERSION = '7.2.2'
3
+ VERSION = '7.3.0'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haml
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.2.2
4
+ version: 7.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Natalie Weizenbaum
@@ -13,6 +13,20 @@ bindir: exe
13
13
  cert_chain: []
14
14
  date: 1980-01-02 00:00:00.000000000 Z
15
15
  dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: prism
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.0
16
30
  - !ruby/object:Gem::Dependency
17
31
  name: temple
18
32
  requirement: !ruby/object:Gem::Requirement
@@ -308,7 +322,9 @@ licenses:
308
322
  - MIT
309
323
  metadata:
310
324
  rubygems_mfa_required: 'true'
311
- changelog_uri: https://github.com/haml/haml/releases
325
+ bug_tracker_uri: https://github.com/haml/haml/issues
326
+ changelog_uri: https://github.com/haml/haml/blob/main/CHANGELOG.md
327
+ homepage_uri: https://haml.info/
312
328
  source_code_uri: https://github.com/haml/haml
313
329
  rdoc_options: []
314
330
  require_paths: