slimi 0.7.8 → 0.9.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: 29937913d2bf6ac3b92dcb1e94964655b79ee48d8e891890ca53618afb3f63db
4
- data.tar.gz: b15a369b677fe16bb18b837cd523dd01f317a05b0135b3f1c690bbc8f2d389fc
3
+ metadata.gz: df7564efc95dcfc968f262e77fff5be0daa2c779c43f8da3ee858a9508264685
4
+ data.tar.gz: 414c5620fb99766908c117ef3ae49d14df04206d6121714fb167ec4a4773678b
5
5
  SHA512:
6
- metadata.gz: 45fe23fedad4989d7b0241a28ef46de56dca448f9e67fa68cfb47058dfc73055a891c84775e53e0ca89f6dd8e6efe5ce381ad3e5df31d8d0e04300a25ca89e5c
7
- data.tar.gz: 859ee454f3ec3712540981e1ea315b9049aeda13473d42b0f5a0edeba8f186a44ad266925441f354e9c78e2414d6cbaf3cd2e9614f17b2c4c56876998bf60e32
6
+ metadata.gz: 12999b7675a1e9aa480cc32c8380a4a09f906376ce34e545db48f481991e1edb6d10bd3b8d6c0f1e09596d357b084b2f92d9958bee66149c27eacb50d0c76991
7
+ data.tar.gz: 037a95dd6470e185b63c6c145706cf27c9271aa2d1017c8ab5806dd87f96a8a05d0f779d5dcb279c7bd242043f80d9af43e2da2515997f0fcbb4b33aa303defd
data/.rubocop.yml CHANGED
@@ -4,7 +4,6 @@ require:
4
4
  AllCops:
5
5
  NewCops: enable
6
6
  SuggestExtensions: false
7
- TargetRubyVersion: 2.6
8
7
 
9
8
  Layout/LineLength:
10
9
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Change required ruby version from 2.6+ to 3.3+.
6
+
7
+ ## 0.8.0 - 2026-06-13
8
+
9
+ - Support splat attributes.
10
+
5
11
  ## 0.7.5 - 2024-07-25
6
12
 
7
13
  ### Fixed
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- slimi (0.7.8)
4
+ slimi (0.9.0)
5
5
  temple
6
6
  thor
7
7
  tilt
@@ -9,22 +9,25 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- actionview (7.1.3.4)
13
- activesupport (= 7.1.3.4)
12
+ actionview (8.1.3)
13
+ activesupport (= 8.1.3)
14
14
  builder (~> 3.1)
15
15
  erubi (~> 1.11)
16
16
  rails-dom-testing (~> 2.2)
17
17
  rails-html-sanitizer (~> 1.6)
18
- activesupport (7.1.3.4)
18
+ activesupport (8.1.3)
19
19
  base64
20
20
  bigdecimal
21
- concurrent-ruby (~> 1.0, >= 1.0.2)
21
+ concurrent-ruby (~> 1.0, >= 1.3.1)
22
22
  connection_pool (>= 2.2.5)
23
23
  drb
24
24
  i18n (>= 1.6, < 2)
25
+ json
26
+ logger (>= 1.4.2)
25
27
  minitest (>= 5.1)
26
- mutex_m
27
- tzinfo (~> 2.0)
28
+ securerandom (>= 0.3)
29
+ tzinfo (~> 2.0, >= 2.0.5)
30
+ uri (>= 0.13.1)
28
31
  ast (2.4.2)
29
32
  base64 (0.2.0)
30
33
  bigdecimal (3.1.8)
@@ -39,17 +42,20 @@ GEM
39
42
  concurrent-ruby (~> 1.0)
40
43
  json (2.7.2)
41
44
  language_server-protocol (3.17.0.3)
45
+ logger (1.7.0)
42
46
  loofah (2.22.0)
43
47
  crass (~> 1.0.2)
44
48
  nokogiri (>= 1.12.0)
45
- minitest (5.24.1)
46
- mutex_m (0.2.0)
47
- nokogiri (1.18.10-x86_64-linux-gnu)
49
+ minitest (6.0.6)
50
+ drb (~> 2.0)
51
+ prism (~> 1.5)
52
+ nokogiri (1.19.3-x86_64-linux-gnu)
48
53
  racc (~> 1.4)
49
54
  parallel (1.25.1)
50
55
  parser (3.3.4.0)
51
56
  ast (~> 2.4.1)
52
57
  racc
58
+ prism (1.9.0)
53
59
  racc (1.8.0)
54
60
  rails-dom-testing (2.2.0)
55
61
  activesupport (>= 5.0.0)
@@ -92,6 +98,7 @@ GEM
92
98
  rubocop-rspec (3.0.3)
93
99
  rubocop (~> 1.61)
94
100
  ruby-progressbar (1.13.0)
101
+ securerandom (0.4.1)
95
102
  strscan (3.1.0)
96
103
  temple (0.10.3)
97
104
  thor (1.3.2)
@@ -99,6 +106,7 @@ GEM
99
106
  tzinfo (2.0.6)
100
107
  concurrent-ruby (~> 1.0)
101
108
  unicode-display_width (2.5.0)
109
+ uri (1.1.1)
102
110
 
103
111
  PLATFORMS
104
112
  x86_64-linux
data/README.md CHANGED
@@ -62,7 +62,7 @@ Commands:
62
62
  - [x] Boolean attributes
63
63
  - [x] Attribute merging
64
64
  - [x] Attribute shortcuts
65
- - [ ] Splat attributes
65
+ - [x] Splat attributes
66
66
  - Plugins
67
67
  - [ ] Include partials
68
68
  - [ ] Translator/I18n
data/lib/slimi/engine.rb CHANGED
@@ -24,6 +24,7 @@ module Slimi
24
24
  use Filters::Control
25
25
  use Filters::Output
26
26
  use Filters::Text
27
+ use Filters::Splat
27
28
  html :AttributeSorter
28
29
  html :AttributeMerger
29
30
  use Filters::Attribute
data/lib/slimi/errors.rb CHANGED
@@ -62,5 +62,14 @@ module Slimi
62
62
 
63
63
  class UnknownLineIndicatorError < SlimSyntaxError
64
64
  end
65
+
66
+ class RenderError < BaseError
67
+ end
68
+
69
+ class InvalidAttributeNameError < RenderError
70
+ end
71
+
72
+ class MultipleAttributesError < RenderError
73
+ end
65
74
  end
66
75
  end
@@ -4,7 +4,7 @@ module Slimi
4
4
  module Filters
5
5
  # Append missing `do` to embedded Ruby code.
6
6
  class DoInserter < Base
7
- VALID_RUBY_LINE_REGEXP = /(\A(if|unless|else|elsif|when|begin|rescue|ensure|case)\b)|\bdo\s*(\|[^|]*\|\s*)?\Z/.freeze
7
+ VALID_RUBY_LINE_REGEXP = /(\A(if|unless|else|elsif|when|begin|rescue|ensure|case)\b)|\bdo\s*(\|[^|]*\|\s*)?\Z/
8
8
 
9
9
  # @param [String] code
10
10
  # @param [Array] expressio
@@ -36,11 +36,11 @@ module Slimi
36
36
  end
37
37
 
38
38
  class Expression
39
- IF_REGEXP = /\A(if|begin|unless|else|elsif|when|rescue|ensure)\b|\bdo\s*(\|[^|]*\|)?\s*$/.freeze
39
+ IF_REGEXP = /\A(if|begin|unless|else|elsif|when|rescue|ensure)\b|\bdo\s*(\|[^|]*\|)?\s*$/
40
40
 
41
- ELSE_REGEXP = /\A(else|elsif|when|rescue|ensure)\b/.freeze
41
+ ELSE_REGEXP = /\A(else|elsif|when|rescue|ensure)\b/
42
42
 
43
- END_REGEXP = /\Aend\b/.freeze
43
+ END_REGEXP = /\Aend\b/
44
44
 
45
45
  # @param [Array] expression
46
46
  def initialize(expression)
@@ -6,7 +6,7 @@ module Slimi
6
6
  class Output < Base
7
7
  define_options :disable_capture
8
8
 
9
- IF_REGEXP = /\A(if|unless)\b|\bdo\s*(\|[^|]*\|)?\s*$/.freeze
9
+ IF_REGEXP = /\A(if|unless)\b|\bdo\s*(\|[^|]*\|)?\s*$/
10
10
 
11
11
  # @param [Boolean] escape
12
12
  # @param [String] code
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ class Splat < Base
6
+ define_options :attr_quote, :format, :merge_attrs, :sort_attrs, use_html_safe: ''.respond_to?(:html_safe?)
7
+
8
+ # @param [Array] exp
9
+ # @return [Array]
10
+ def call(exp)
11
+ @splat_options = nil
12
+ exp = compile(exp)
13
+ if @splat_options
14
+ [:multi, [:code, "#{@splat_options} = #{splat_builder_options.inspect}"], exp]
15
+ else
16
+ exp
17
+ end
18
+ end
19
+
20
+ # @param [Array<Array>] attrs
21
+ # @return [Array]
22
+ def on_html_attrs(*attrs)
23
+ return super unless attrs.any? { |attr| splat?(attr) }
24
+
25
+ @splat_options ||= unique_name
26
+ builder = unique_name
27
+ result = [:multi]
28
+ result << [:code, "#{builder} = ::#{Builder.name}.new(#{@splat_options})"]
29
+ attrs.each do |attr|
30
+ result << compile_attribute(builder, attr)
31
+ end
32
+ result << [:dynamic, "#{builder}.to_s"]
33
+ end
34
+
35
+ private
36
+
37
+ # @param [String] builder
38
+ # @param [Array] attr
39
+ # @return [Array]
40
+ def compile_attribute(builder, attr)
41
+ return [:code, "#{builder}.splat((#{attr[2]}))"] if splat?(attr)
42
+
43
+ _, _, name, value = attr
44
+ if value[0] == :slimi && value[1] == :attrvalue
45
+ # Pass the raw expression and escape flag through, so that the Builder can
46
+ # apply the same merge / boolean / nil / escape rules as Filters::Attribute.
47
+ [:code, "#{builder}.code_attribute(#{name.to_s.inspect}, #{value[2]}, (#{value[3]}))"]
48
+ elsif value == [:multi]
49
+ # Boolean attribute, e.g. `div(*foo disabled)`.
50
+ [:code, "#{builder}.code_attribute(#{name.to_s.inspect}, false, true)"]
51
+ else
52
+ # Static or interpolated value whose escaping is compiled into the capture,
53
+ # so the Builder must not escape it again.
54
+ tmp = unique_name
55
+ [:multi,
56
+ [:capture, tmp, compile(value)],
57
+ [:code, "#{builder}.attribute(#{name.to_s.inspect}, #{tmp})"]]
58
+ end
59
+ end
60
+
61
+ # @param [Array] attr
62
+ # @return [Boolean]
63
+ def splat?(attr)
64
+ attr[0] == :slimi && attr[1] == :splat
65
+ end
66
+
67
+ # @return [Hash]
68
+ def splat_builder_options
69
+ {
70
+ attr_quote: options[:attr_quote],
71
+ format: options[:format],
72
+ merge_attrs: options[:merge_attrs],
73
+ sort_attrs: options[:sort_attrs],
74
+ use_html_safe: options[:use_html_safe]
75
+ }
76
+ end
77
+
78
+ class Builder
79
+ # https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
80
+ INVALID_ATTRIBUTE_NAME_REGEXP = %r{[ \0"'>/=]}
81
+
82
+ # @param [Hash] options
83
+ def initialize(options)
84
+ @attr_quote = options[:attr_quote]
85
+ @format = options[:format]
86
+ @merge_attrs = options[:merge_attrs] || {}
87
+ @sort_attrs = options.fetch(:sort_attrs, true)
88
+ @use_html_safe = options[:use_html_safe]
89
+ @attributes = {}
90
+ end
91
+
92
+ # Add an attribute value already rendered and escaped by the compiled template.
93
+ # @param [String] name
94
+ # @param [String] value
95
+ def attribute(name, value)
96
+ store(name, value)
97
+ end
98
+
99
+ # Add an attribute value, applying merge / boolean / nil / escape rules.
100
+ # @param [String] name
101
+ # @param [Boolean] escape
102
+ # @param [Object] value
103
+ def code_attribute(name, escape, value)
104
+ if (delimiter = @merge_attrs[name])
105
+ value = value.is_a?(::Array) ? value.flatten.map(&:to_s).reject(&:empty?).join(delimiter) : value.to_s
106
+ store(name, escape_html(escape, value)) unless value.empty?
107
+ elsif value.is_a?(::Hash)
108
+ value.each do |key, nested_value|
109
+ code_attribute("#{name}-#{key}", escape, nested_value)
110
+ end
111
+ elsif value != false && !value.nil?
112
+ store(name, value == true ? true : escape_html(escape, value.to_s))
113
+ end
114
+ end
115
+
116
+ # @param [Hash] hash
117
+ def splat(hash)
118
+ hash.each do |name, value|
119
+ code_attribute(name.to_s, true, value)
120
+ end
121
+ end
122
+
123
+ # @return [String]
124
+ def to_s
125
+ attributes = @sort_attrs ? @attributes.sort_by(&:first) : @attributes
126
+ attributes.map { |name, value| render(name, value) }.join
127
+ end
128
+
129
+ private
130
+
131
+ # @param [String] name
132
+ # @param [String, true] value
133
+ def store(name, value)
134
+ raise Errors::InvalidAttributeNameError, "Invalid attribute name '#{name}' was rendered" if INVALID_ATTRIBUTE_NAME_REGEXP.match?(name)
135
+
136
+ if @attributes.key?(name)
137
+ delimiter = @merge_attrs[name]
138
+ raise Errors::MultipleAttributesError, "Multiple #{name} attributes specified" unless delimiter
139
+
140
+ @attributes[name] = "#{@attributes[name]}#{delimiter}#{value}"
141
+ else
142
+ @attributes[name] = value
143
+ end
144
+ end
145
+
146
+ # @param [String] name
147
+ # @param [String, true] value
148
+ # @return [String]
149
+ def render(name, value)
150
+ if value == true
151
+ if @format == :xhtml
152
+ " #{name}=#{@attr_quote}#{@attr_quote}"
153
+ else
154
+ " #{name}"
155
+ end
156
+ else
157
+ " #{name}=#{@attr_quote}#{value}#{@attr_quote}"
158
+ end
159
+ end
160
+
161
+ # @param [Boolean] escape
162
+ # @param [String] string
163
+ # @return [String]
164
+ def escape_html(escape, string)
165
+ return string unless escape
166
+
167
+ if @use_html_safe
168
+ ::Temple::Utils.escape_html_safe(string)
169
+ else
170
+ ::Temple::Utils.escape_html(string)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
data/lib/slimi/filters.rb CHANGED
@@ -11,6 +11,7 @@ module Slimi
11
11
  autoload :EndInserter, 'slimi/filters/end_inserter'
12
12
  autoload :Interpolation, 'slimi/filters/interpolation'
13
13
  autoload :Output, 'slimi/filters/output'
14
+ autoload :Splat, 'slimi/filters/splat'
14
15
  autoload :Text, 'slimi/filters/text'
15
16
  autoload :Unposition, 'slimi/filters/unposition'
16
17
  end
data/lib/slimi/parser.rb CHANGED
@@ -38,6 +38,7 @@ module Slimi
38
38
  @attribute_shortcut_regexp = factory.attribute_shortcut_regexp
39
39
  @attribute_delimiter_regexp = factory.attribute_delimiter_regexp
40
40
  @quoted_attribute_regexp = factory.quoted_attribute_regexp
41
+ @splat_attribute_regexp = factory.splat_attribute_regexp
41
42
  @tag_name_regexp = factory.tag_name_regexp
42
43
  @attribute_name_regexp = factory.attribute_name_regexp
43
44
  @ruby_attribute_regexp = factory.ruby_attribute_regexp
@@ -259,9 +260,13 @@ module Slimi
259
260
  attribute_delimiter_closing_part_regexp = /[ \t]*#{attribute_delimiter_closing_regexp}/
260
261
  end
261
262
 
262
- # TODO: Support splat attributes.
263
263
  loop do
264
- if @scanner.skip(@quoted_attribute_regexp)
264
+ if @scanner.skip(@splat_attribute_regexp)
265
+ charpos = @scanner.charpos
266
+ code = parse_ruby_attribute_value(attribute_delimiter_closing)
267
+ syntax_error!(Errors::InvalidEmptyAttributeError) if code.empty?
268
+ attributes << [:slimi, :position, charpos, charpos + code.length, [:slimi, :splat, code]]
269
+ elsif @scanner.skip(@quoted_attribute_regexp)
265
270
  attribute_name = @scanner[1]
266
271
  escape = @scanner[2].empty?
267
272
  quote = @scanner[3]
@@ -676,6 +681,11 @@ module Slimi
676
681
  /#{attribute_name_regexp}[ \t]*=(=?)[ \t]*("|')/
677
682
  end
678
683
 
684
+ # @return [Regexp]
685
+ def splat_attribute_regexp
686
+ /[ \t]*\*(?=[^\s])/
687
+ end
688
+
679
689
  # @return [Regexp]
680
690
  def ruby_attribute_delimiter_regexp
681
691
  ::Regexp.union(@ruby_attribute_delimiters.keys)
@@ -8,8 +8,8 @@ module Slimi
8
8
  # @return [String]
9
9
  def call(template, source = nil)
10
10
  Renderer.new(
11
- source: source,
12
- template: template
11
+ source:,
12
+ template:
13
13
  ).call
14
14
  end
15
15
 
data/lib/slimi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Slimi
4
- VERSION = '0.7.8'
4
+ VERSION = '0.9.0'
5
5
  end
data/slimi.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = 'Yet another implementation for Slim template language.'
12
12
  spec.homepage = 'https://github.com/r7kamura/slimi'
13
13
  spec.license = 'MIT'
14
- spec.required_ruby_version = '>= 2.6.0'
14
+ spec.required_ruby_version = '>= 3.3'
15
15
 
16
16
  spec.metadata['homepage_uri'] = spec.homepage
17
17
  spec.metadata['source_code_uri'] = spec.homepage
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slimi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.8
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
@@ -85,6 +85,7 @@ files:
85
85
  - lib/slimi/filters/end_inserter.rb
86
86
  - lib/slimi/filters/interpolation.rb
87
87
  - lib/slimi/filters/output.rb
88
+ - lib/slimi/filters/splat.rb
88
89
  - lib/slimi/filters/text.rb
89
90
  - lib/slimi/filters/unposition.rb
90
91
  - lib/slimi/parser.rb
@@ -108,14 +109,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
109
  requirements:
109
110
  - - ">="
110
111
  - !ruby/object:Gem::Version
111
- version: 2.6.0
112
+ version: '3.3'
112
113
  required_rubygems_version: !ruby/object:Gem::Requirement
113
114
  requirements:
114
115
  - - ">="
115
116
  - !ruby/object:Gem::Version
116
117
  version: '0'
117
118
  requirements: []
118
- rubygems_version: 3.6.9
119
+ rubygems_version: 3.6.7
119
120
  specification_version: 4
120
121
  summary: Yet another implementation for Slim template language.
121
122
  test_files: []