slimi 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05cfa3a429ac2da0e1241e65e69a40f9171079cddb9451a6d7186726a5cb498c
4
- data.tar.gz: 68826c3fe78dc87bfd704cc38a9222da1adeed36a8c0d0e903bb4f3077ed698d
3
+ metadata.gz: 36247783488eef0fc3711f969b37bc5b0cbc06b40086e7c524dfa4f46bba2823
4
+ data.tar.gz: 63e12ac1f2d178840b4d956b3c62195ce3601a3c212b1c48e5cc65ce7ca61a24
5
5
  SHA512:
6
- metadata.gz: 9c729933bc060a21df2e55db525d28a8e519526995ec5b7fb3defd17ecb11c71fc7e6ec664c162396f6a8be9175549dd842c7c5f9be76c68afadadc9e3e442be
7
- data.tar.gz: 60b3b1ad54a01bcbc2154e83b11efe3ac1b3fb5723773f04aabff9d1957dec60cb8afd431630b78dc8e13fa86d109d86a78c66005bafb10c23e0965368d9bd80
6
+ metadata.gz: 549fc053c09c2283042f7bad2200d79d07e970d97c3aadacbd38e5fbbff54534bb0918b145580c23f1d4ffb2cba760167f09c6bc2b41de71c0b457c76c981f18
7
+ data.tar.gz: af6cc99ec67062a742f8bff4c76ac6da875a97a7afb5c6d193cd6c50b23e9e7e4ddd62b1ac690d3b94f6094ed5da1986cd2705b58a6147941788b13100ee77d2
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
+ require:
2
+ - rubocop-rspec
3
+
1
4
  AllCops:
2
5
  NewCops: enable
3
6
  SuggestExtensions: false
@@ -12,5 +15,17 @@ Lint/InterpolationCheck:
12
15
  Metrics:
13
16
  Enabled: false
14
17
 
18
+ RSpec/AnyInstance:
19
+ Enabled: false
20
+
21
+ RSpec/ImplicitSubject:
22
+ Enabled: false
23
+
24
+ RSpec/MultipleExpectations:
25
+ Enabled: false
26
+
27
+ RSpec/NamedSubject:
28
+ Enabled: false
29
+
15
30
  Style/Documentation:
16
31
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.1 - 2022-01-02
6
+
7
+ ### Changed
8
+
9
+ - Wrap slim attrvalue by slimi position expression.
10
+
11
+ ## 0.5.0 - 2022-01-02
12
+
13
+ ### Added
14
+
15
+ - Add Slimi::Engine.
16
+
17
+ ## 0.4.2 - 2021-12-27
18
+
19
+ ### Fixed
20
+
21
+ - Fix bug on quoted attribute parser.
22
+ - Remove trailing line-ending from source line of syntax error message.
23
+ - Support multi-line attributes.
24
+
25
+ ## 0.4.1 - 2021-12-26
26
+
27
+ ### Fixed
28
+
29
+ - Fix bug on parsing Ruby attribute value.
30
+ - Fix bug on empty line in text block.
31
+
5
32
  ## 0.4.0 - 2021-12-25
6
33
 
7
34
  ### Added
@@ -17,7 +44,7 @@
17
44
 
18
45
  ### Added
19
46
 
20
- - Support unquoted attributes.
47
+ - Support Ruby attributes.
21
48
 
22
49
  ### Fixed
23
50
 
data/Gemfile CHANGED
@@ -6,6 +6,6 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  gem 'rake', '~> 13.0'
9
- gem 'rspec', '~> 3.0'
10
- gem 'rubocop', '~> 1.21'
11
- gem 'slim'
9
+ gem 'rspec'
10
+ gem 'rubocop'
11
+ gem 'rubocop-rspec'
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- slimi (0.4.0)
4
+ slimi (0.5.1)
5
5
  temple
6
+ tilt
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
@@ -40,10 +41,9 @@ GEM
40
41
  unicode-display_width (>= 1.4.0, < 3.0)
41
42
  rubocop-ast (1.15.0)
42
43
  parser (>= 3.0.1.1)
44
+ rubocop-rspec (2.6.0)
45
+ rubocop (~> 1.19)
43
46
  ruby-progressbar (1.11.0)
44
- slim (4.1.0)
45
- temple (>= 0.7.6, < 0.9)
46
- tilt (>= 2.0.6, < 2.1)
47
47
  temple (0.8.2)
48
48
  tilt (2.0.10)
49
49
  unicode-display_width (2.1.0)
@@ -53,9 +53,9 @@ PLATFORMS
53
53
 
54
54
  DEPENDENCIES
55
55
  rake (~> 13.0)
56
- rspec (~> 3.0)
57
- rubocop (~> 1.21)
58
- slim
56
+ rspec
57
+ rubocop
58
+ rubocop-rspec
59
59
  slimi!
60
60
 
61
61
  BUNDLED WITH
data/README.md CHANGED
@@ -5,28 +5,55 @@
5
5
 
6
6
  Yet another implementation for [Slim](https://github.com/slim-template/slim) template language.
7
7
 
8
- Slimi is used by [Slimcop](https://github.com/r7kamura/slimcop).
8
+ ## Introduction
9
9
 
10
- ## Installation
10
+ Slimi provides almost the same functionality as Slim, with a few additional useful features,
11
+ such as generating AST with detailed location information about embedded Ruby codes.
11
12
 
12
- Add this line to your application's Gemfile:
13
+ Originally, Slimi was developed for [Slimcop](https://github.com/r7kamura/slimcop), a RuboCop runner for Slim template.
14
+ It uses Slimi to apply `rubocop --auto-correct` to embedded Ruby codes in Slim template.
13
15
 
14
- ```ruby
15
- gem 'slimi'
16
- ```
17
-
18
- And then execute:
19
-
20
- ```
21
- bundle install
22
- ```
16
+ ## Usage
23
17
 
24
- Or install it yourself as:
18
+ Just replace `gem 'slim'` with `gem 'slimi'` in your application's Gemfile.
25
19
 
20
+ ```ruby
21
+ gem 'slimi'
26
22
  ```
27
- gem install slimi
28
- ```
29
-
30
- ## Usage
31
23
 
32
- TBD.
24
+ ## Compatibility
25
+
26
+ - Line indicators
27
+ - [x] Vebatim text
28
+ - [x] Inline HTML
29
+ - [x] Control
30
+ - [x] Output
31
+ - [x] HTML comment
32
+ - [x] Code comment
33
+ - [x] IE conditional comment
34
+ - Tags
35
+ - [x] Doctype declaration
36
+ - [x] Closed tags
37
+ - [x] Trailing and leading white space
38
+ - [x] Inline tags
39
+ - [x] Text content
40
+ - [x] Dynamic content
41
+ - [x] Tag shortcuts
42
+ - [ ] Dynamic tags
43
+ - Attributes
44
+ - [x] Attributes wrapper
45
+ - [x] Quoted attributes
46
+ - [x] Ruby attributes
47
+ - [x] Boolean attributes
48
+ - [x] Attribute merging
49
+ - [x] Attribute shortcuts
50
+ - [ ] Splat attributes
51
+ - Plugins
52
+ - [ ] Include partials
53
+ - [ ] Translator/I18n
54
+ - [ ] Logic-less mode
55
+ - [ ] Smart text mode
56
+ - Etc.
57
+ - [ ] CLI tools
58
+ - Slimi-only features
59
+ - [x] Embedded Ruby code location
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temple'
4
+
5
+ module Slimi
6
+ # Convert Slim code into Ruby code.
7
+ class Engine < ::Temple::Engine
8
+ define_options(
9
+ attr_quote: '"',
10
+ default_tag: 'div',
11
+ format: :xhtml,
12
+ merge_attrs: { 'class' => ' ' },
13
+ pretty: false,
14
+ sort_attrs: true
15
+ )
16
+
17
+ use Parser
18
+ use Filters::Unposition
19
+ use Filters::Embedded
20
+ use Filters::Interpolation
21
+ use Filters::DoInserter
22
+ use Filters::EndInserter
23
+ use Filters::Control
24
+ use Filters::Output
25
+ use Filters::Text
26
+ html :AttributeSorter
27
+ html :AttributeMerger
28
+ use Filters::Attribute
29
+ use(:AttributeRemover) { ::Temple::HTML::AttributeRemover.new(remove_empty_attrs: options[:merge_attrs].keys) }
30
+ html :Pretty
31
+ filter :Escapable
32
+ filter :ControlFlow
33
+ filter :MultiFlattener
34
+ filter :StaticMerger
35
+ generator :StringBuffer
36
+ end
37
+ end
data/lib/slimi/errors.rb CHANGED
@@ -23,7 +23,7 @@ module Slimi
23
23
  def to_s
24
24
  <<~TEXT
25
25
  #{error_type} at #{@file_path}:#{@line_number}:#{@column}
26
- #{@line}
26
+ #{@line.rstrip}
27
27
  #{' ' * (@column - 1)}^
28
28
  TEXT
29
29
  end
@@ -36,6 +36,9 @@ module Slimi
36
36
  end
37
37
  end
38
38
 
39
+ class AttributeClosingDelimiterNotFoundError < SlimSyntaxError
40
+ end
41
+
39
42
  class InvalidEmptyAttributeError < SlimSyntaxError
40
43
  end
41
44
 
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Handle `[:slimi, :attributes, ...]`.
6
+ class Attribute < ::Temple::HTML::Filter
7
+ define_options :merge_attrs
8
+
9
+ # @param [Array<Array>] expressions
10
+ # @return [Array]
11
+ def on_html_attrs(*expressions)
12
+ [:multi, *expressions.map { |expression| compile(expression) }]
13
+ end
14
+
15
+ # @param [String] name
16
+ # @param [Array] value
17
+ # @return [Array]
18
+ def on_html_attr(name, value)
19
+ if value[0] == :slim && value[1] == :attrvalue && !options[:merge_attrs][name]
20
+ escape = value[2]
21
+ code = value[3]
22
+ case code
23
+ when 'true'
24
+ [:html, :attr, name, [:multi]]
25
+ when 'false', 'nil'
26
+ [:multi]
27
+ else
28
+ tmp = unique_name
29
+ [:multi,
30
+ [:code, "#{tmp} = #{code}"],
31
+ [:if, tmp,
32
+ [:if, "#{tmp} == true",
33
+ [:html, :attr, name, [:multi]],
34
+ [:html, :attr, name, [:escape, escape, [:dynamic, tmp]]]]]]
35
+ end
36
+ else
37
+ @attr = name
38
+ super
39
+ end
40
+ end
41
+
42
+ # @param [Boolean] escape
43
+ # @param [String] code\
44
+ # @return [Array]\
45
+ def on_slim_attrvalue(escape, code)
46
+ if (delimiter = options[:merge_attrs][@attr])
47
+ tmp = unique_name
48
+ [:multi,
49
+ [:code, "#{tmp} = #{code}"],
50
+ [:if, "Array === #{tmp}",
51
+ [:multi,
52
+ [:code, "#{tmp} = #{tmp}.flatten"],
53
+ [:code, "#{tmp}.map!(&:to_s)"],
54
+ [:code, "#{tmp}.reject!(&:empty?)"],
55
+ [:escape, escape, [:dynamic, "#{tmp}.join(#{delimiter.inspect})"]]],
56
+ [:escape, escape, [:dynamic, tmp]]]]
57
+ else
58
+ [:escape, escape, [:dynamic, code]]
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Handle `[:slim, :control, code, multi]`.
6
+ class Control < ::Temple::HTML::Filter
7
+ # @param [String] code
8
+ # @param [Array] multi
9
+ # @return [Array]
10
+ def on_slim_control(code, multi)
11
+ [
12
+ :multi,
13
+ [:code, code],
14
+ compile(multi)
15
+ ]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Append missing `do` to embedded Ruby code.
6
+ class DoInserter < ::Temple::HTML::Filter
7
+ VALID_RUBY_LINE_REGEXP = /(\A(if|unless|else|elsif|when|begin|rescue|ensure|case)\b)|\bdo\s*(\|[^|]*\|\s*)?\Z/.freeze
8
+
9
+ # @param [String] code
10
+ # @param [Array] expressio
11
+ # @return [Array]
12
+ def on_slim_control(code, expression)
13
+ code += ' do' unless code.match?(VALID_RUBY_LINE_REGEXP) || empty_exp?(expression)
14
+ [:slim, :control, code, compile(expression)]
15
+ end
16
+
17
+ # @param [Boolean] escape
18
+ # @param [String] code
19
+ # @param [Array] expression
20
+ # @return [Array]
21
+ def on_slim_output(escape, code, expression)
22
+ code += ' do' unless code.match?(VALID_RUBY_LINE_REGEXP) || empty_exp?(expression)
23
+ [:slim, :output, escape, code, compile(expression)]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # @api private
6
+ class TextCollector < ::Temple::HTML::Filter
7
+ def call(exp)
8
+ @collected = ''
9
+ super(exp)
10
+ @collected
11
+ end
12
+
13
+ def on_slim_interpolate(text)
14
+ @collected << text
15
+ nil
16
+ end
17
+ end
18
+
19
+ # @api private
20
+ class NewlineCollector < ::Temple::HTML::Filter
21
+ def call(exp)
22
+ @collected = [:multi]
23
+ super(exp)
24
+ @collected
25
+ end
26
+
27
+ def on_newline
28
+ @collected << [:newline]
29
+ nil
30
+ end
31
+ end
32
+
33
+ # @api private
34
+ class OutputProtector < ::Temple::HTML::Filter
35
+ def call(exp)
36
+ @protect = []
37
+ @collected = ''
38
+ @tag = "%#{object_id.abs.to_s(36)}%"
39
+ super(exp)
40
+ @collected
41
+ end
42
+
43
+ def on_static(text)
44
+ @collected << text
45
+ nil
46
+ end
47
+
48
+ def on_slim_output(escape, text, content)
49
+ @collected << @tag
50
+ @protect << [:slim, :output, escape, text, content]
51
+ nil
52
+ end
53
+
54
+ def unprotect(text)
55
+ block = [:multi]
56
+ while text =~ /#{@tag}/
57
+ block << [:static, Regexp.last_match.pre_match]
58
+ block << @protect.shift
59
+ text = Regexp.last_match.post_match
60
+ end
61
+ block << [:static, text]
62
+ end
63
+ end
64
+
65
+ # Temple filter which processes embedded engines
66
+ # @api private
67
+ class Embedded < ::Temple::HTML::Filter
68
+ @engines = {}
69
+
70
+ class << self
71
+ attr_reader :engines
72
+
73
+ # Register embedded engine
74
+ #
75
+ # @param [String] name Name of the engine
76
+ # @param [Class] klass Engine class
77
+ # @param option_filter List of options to pass to engine.
78
+ # Last argument can be default option hash.
79
+ def register(name, klass, *option_filter)
80
+ name = name.to_sym
81
+ local_options = option_filter.last.respond_to?(:to_hash) ? option_filter.pop.to_hash : {}
82
+ define_options(name, *option_filter)
83
+ klass.define_options(name)
84
+ engines[name.to_sym] = proc do |options|
85
+ klass.new({}.update(options).delete_if { |k, _v| !option_filter.include?(k) && k != name }.update(local_options))
86
+ end
87
+ end
88
+
89
+ def create(name, options)
90
+ constructor = engines[name] || raise(Temple::FilterError, "Embedded engine #{name} not found")
91
+ constructor.call(options)
92
+ end
93
+ end
94
+
95
+ define_options :enable_engines, :disable_engines
96
+
97
+ def initialize(opts = {})
98
+ super
99
+ @engines = {}
100
+ @enabled = normalize_engine_list(options[:enable_engines])
101
+ @disabled = normalize_engine_list(options[:disable_engines])
102
+ end
103
+
104
+ def on_slim_embedded(name, body, attrs)
105
+ name = name.to_sym
106
+ raise(Temple::FilterError, "Embedded engine #{name} is disabled") unless enabled?(name)
107
+
108
+ @engines[name] ||= self.class.create(name, options)
109
+ @engines[name].on_slim_embedded(name, body, attrs)
110
+ end
111
+
112
+ def enabled?(name)
113
+ (!@enabled || @enabled.include?(name)) &&
114
+ (!@disabled || !@disabled.include?(name))
115
+ end
116
+
117
+ protected
118
+
119
+ def normalize_engine_list(list)
120
+ raise(ArgumentError, 'Option :enable_engines/:disable_engines must be String or Symbol list') unless !list || list.is_a?(Array)
121
+
122
+ list&.map(&:to_sym)
123
+ end
124
+
125
+ class Engine < ::Temple::HTML::Filter
126
+ protected
127
+
128
+ def collect_text(body)
129
+ @text_collector ||= TextCollector.new
130
+ @text_collector.call(body)
131
+ end
132
+
133
+ def collect_newlines(body)
134
+ @newline_collector ||= NewlineCollector.new
135
+ @newline_collector.call(body)
136
+ end
137
+ end
138
+
139
+ # Basic tilt engine
140
+ class TiltEngine < Engine
141
+ def on_slim_embedded(engine, body, _attrs)
142
+ tilt_engine = Tilt[engine] || raise(Temple::FilterError, "Tilt engine #{engine} is not available.")
143
+ tilt_options = options[engine.to_sym] || {}
144
+ tilt_options[:default_encoding] ||= 'utf-8'
145
+ [:multi, tilt_render(tilt_engine, tilt_options, collect_text(body)), collect_newlines(body)]
146
+ end
147
+
148
+ protected
149
+
150
+ def tilt_render(tilt_engine, tilt_options, text)
151
+ [:static, tilt_engine.new(tilt_options) { text }.render]
152
+ end
153
+ end
154
+
155
+ # Sass engine which supports :pretty option
156
+ class SassEngine < TiltEngine
157
+ define_options :pretty
158
+
159
+ protected
160
+
161
+ def tilt_render(tilt_engine, tilt_options, text)
162
+ text = tilt_engine.new(tilt_options.merge(
163
+ style: options[:pretty] ? :expanded : :compressed,
164
+ cache: false
165
+ )) { text }.render
166
+ text = text.chomp
167
+ [:static, text]
168
+ end
169
+ end
170
+
171
+ # Static template with interpolated ruby code
172
+ class InterpolateTiltEngine < TiltEngine
173
+ def collect_text(body)
174
+ output_protector.call(interpolation.call(body))
175
+ end
176
+
177
+ def tilt_render(tilt_engine, tilt_options, text)
178
+ output_protector.unprotect(tilt_engine.new(tilt_options) { text }.render)
179
+ end
180
+
181
+ private
182
+
183
+ def interpolation
184
+ @interpolation ||= Interpolation.new
185
+ end
186
+
187
+ def output_protector
188
+ @output_protector ||= OutputProtector.new
189
+ end
190
+ end
191
+
192
+ # Tag wrapper engine
193
+ # Generates a html tag and wraps another engine (specified via :engine option)
194
+ class TagEngine < Engine
195
+ disable_option_validator!
196
+
197
+ def on_slim_embedded(engine, body, attrs)
198
+ unless options[:attributes].empty?
199
+ options[:attributes].map do |k, v|
200
+ attrs << [:html, :attr, k, [:static, v]]
201
+ end
202
+ end
203
+
204
+ if options[:engine]
205
+ opts = {}.update(options)
206
+ opts.delete(:engine)
207
+ opts.delete(:tag)
208
+ opts.delete(:attributes)
209
+ @engine ||= options[:engine].new(opts)
210
+ body = @engine.on_slim_embedded(engine, body, attrs)
211
+ end
212
+
213
+ [:html, :tag, options[:tag], attrs, body]
214
+ end
215
+ end
216
+
217
+ # Javascript wrapper engine.
218
+ # Like TagEngine, but can wrap content in html comment or cdata.
219
+ class JavaScriptEngine < TagEngine
220
+ disable_option_validator!
221
+
222
+ set_options tag: :script, attributes: {}
223
+
224
+ def on_slim_embedded(engine, body, attrs)
225
+ super(engine, [:html, :js, body], attrs)
226
+ end
227
+ end
228
+
229
+ # Embeds ruby code
230
+ class RubyEngine < Engine
231
+ def on_slim_embedded(_engine, body, _attrs)
232
+ [:multi, [:newline], [:code, "#{collect_text(body)}\n"]]
233
+ end
234
+ end
235
+
236
+ # These engines are executed at compile time, embedded ruby is interpolated
237
+ register :markdown, InterpolateTiltEngine
238
+ register :textile, InterpolateTiltEngine
239
+ register :rdoc, InterpolateTiltEngine
240
+
241
+ # These engines are executed at compile time
242
+ register :coffee, JavaScriptEngine, engine: TiltEngine
243
+ register :less, TagEngine, tag: :style, attributes: { type: 'text/css' }, engine: TiltEngine
244
+ register :sass, TagEngine, :pretty, tag: :style, attributes: { type: 'text/css' }, engine: SassEngine
245
+ register :scss, TagEngine, :pretty, tag: :style, attributes: { type: 'text/css' }, engine: SassEngine
246
+
247
+ # Embedded javascript/css
248
+ register :javascript, JavaScriptEngine
249
+ register :css, TagEngine, tag: :style, attributes: { type: 'text/css' }
250
+
251
+ # Embedded ruby code
252
+ register :ruby, RubyEngine
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Append missing `end` line to embedded Ruby code in control block.
6
+ class EndInserter < ::Temple::HTML::Filter
7
+ # @param [Array<Array>] expressions
8
+ def on_multi(*expressions)
9
+ result = [:multi]
10
+ prev_indent = false
11
+ expressions.each do |source|
12
+ expression = Expression.new(source)
13
+ if expression.control?
14
+ raise ::Temple::FilterError, 'Explicit end statements are forbidden.' if expression.end?
15
+
16
+ result << code_end if prev_indent && !expression.else?
17
+
18
+ prev_indent = expression.if?
19
+ elsif !expression.newline? && prev_indent
20
+ result << code_end
21
+ prev_indent = false
22
+ end
23
+
24
+ result << compile(source)
25
+ end
26
+
27
+ result << code_end if prev_indent
28
+ result
29
+ end
30
+
31
+ private
32
+
33
+ # @return [Array]
34
+ def code_end
35
+ [:code, 'end']
36
+ end
37
+
38
+ class Expression
39
+ IF_REGEXP = /\A(if|begin|unless|else|elsif|when|rescue|ensure)\b|\bdo\s*(\|[^|]*\|)?\s*$/.freeze
40
+
41
+ ELSE_REGEXP = /\A(else|elsif|when|rescue|ensure)\b/.freeze
42
+
43
+ END_REGEXP = /\Aend\b/.freeze
44
+
45
+ # @param [Array] expression
46
+ def initialize(expression)
47
+ @expression = expression
48
+ end
49
+
50
+ # @return [Boolean]
51
+ def control?
52
+ @expression[0] == :slim && @expression[1] == :control
53
+ end
54
+
55
+ # @return [Boolean]
56
+ def if?
57
+ @expression[2].match?(IF_REGEXP)
58
+ end
59
+
60
+ # @return [Boolean]
61
+ def else?
62
+ @expression[2].match?(ELSE_REGEXP)
63
+ end
64
+
65
+ # @return [Boolean]
66
+ def end?
67
+ @expression[2].match?(END_REGEXP)
68
+ end
69
+
70
+ # @return [Boolean]
71
+ def newline?
72
+ @expression[0] == :newline
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Handle `[:slim, :output, escape, code, multi]`.
6
+ class Output < ::Temple::HTML::Filter
7
+ define_options :disable_capture
8
+
9
+ IF_REGEXP = /\A(if|unless)\b|\bdo\s*(\|[^|]*\|)?\s*$/.freeze
10
+
11
+ # @param [Boolean] escape
12
+ # @param [String] code
13
+ # @param [Array] multi
14
+ # @return [Array]
15
+ def on_slim_output(escape, code, multi)
16
+ if code.match?(IF_REGEXP)
17
+ tmp = unique_name
18
+ [
19
+ :multi,
20
+ [:block, "#{tmp} = #{code}", options[:disable_capture] ? compile(multi) : [:capture, unique_name, compile(multi)]],
21
+ [:escape, escape, [:dynamic, tmp]]
22
+ ]
23
+ else
24
+ [
25
+ :multi,
26
+ [:escape, escape, [:dynamic, code]],
27
+ multi
28
+ ]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Handle `[:slim, :text, multi]`.
6
+ class Text < ::Temple::HTML::Filter
7
+ # @param [Symbol] _type
8
+ # @param [Array] multi
9
+ # @return [Array]
10
+ def on_slim_text(_type, multi)
11
+ compile(multi)
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/slimi/filters.rb CHANGED
@@ -2,7 +2,14 @@
2
2
 
3
3
  module Slimi
4
4
  module Filters
5
+ autoload :Attribute, 'slimi/filters/attribute'
6
+ autoload :Control, 'slimi/filters/control'
7
+ autoload :DoInserter, 'slimi/filters/do_inserter'
8
+ autoload :Embedded, 'slimi/filters/embedded'
9
+ autoload :EndInserter, 'slimi/filters/end_inserter'
5
10
  autoload :Interpolation, 'slimi/filters/interpolation'
11
+ autoload :Output, 'slimi/filters/output'
12
+ autoload :Text, 'slimi/filters/text'
6
13
  autoload :Unposition, 'slimi/filters/unposition'
7
14
  end
8
15
  end
data/lib/slimi/parser.rb CHANGED
@@ -220,18 +220,24 @@ module Slimi
220
220
  value = +''
221
221
  count = 0
222
222
  loop do
223
- if @scanner.match?(/#{quote}/) && count.zero?
224
- end_ = @scanner.charpos
225
- @scanner.pos += @scanner.matched_size
226
- break
227
- end
228
-
229
- if @scanner.skip(/\{/)
223
+ if @scanner.match?(/#{quote}/)
224
+ if count.zero?
225
+ end_ = @scanner.charpos
226
+ @scanner.pos += @scanner.matched_size
227
+ break
228
+ else
229
+ @scanner.pos += @scanner.matched_size
230
+ value << @scanner.matched
231
+ end
232
+ elsif @scanner.skip(/\{/)
230
233
  count += 1
234
+ value << @scanner.matched
231
235
  elsif @scanner.skip(/\}/)
232
236
  count -= 1
237
+ value << @scanner.matched
238
+ else
239
+ value << @scanner.scan(/[^{}#{quote}]*/)
233
240
  end
234
- value << @scanner.scan(/[^{}#{quote}]*/)
235
241
  end
236
242
  [:slimi, :interpolate, begin_, end_, value]
237
243
  end
@@ -263,9 +269,10 @@ module Slimi
263
269
  elsif @scanner.skip(@ruby_attribute_regexp)
264
270
  attribute_name = @scanner[1]
265
271
  escape = @scanner[2].empty?
272
+ charpos = @scanner.charpos
266
273
  attribute_value = parse_ruby_attribute_value(attribute_delimiter_closing)
267
274
  syntax_error!(Errors::InvalidEmptyAttributeError) if attribute_value.empty?
268
- attributes << [:html, :attr, attribute_name, [:slim, :attrvalue, escape, attribute_value]]
275
+ attributes << [:html, :attr, attribute_name, [:slimi, :position, charpos, charpos + attribute_value.length, [:slim, :attrvalue, escape, attribute_value]]]
269
276
  elsif !attribute_delimiter_closing_part_regexp
270
277
  break
271
278
  elsif @scanner.skip(boolean_attribute_regexp)
@@ -273,7 +280,10 @@ module Slimi
273
280
  elsif @scanner.skip(attribute_delimiter_closing_part_regexp) # rubocop:disable Lint/DuplicateBranch
274
281
  break
275
282
  else
276
- raise ::NotImplementedError
283
+ @scanner.skip(/[ \t]+/)
284
+ expect_line_ending
285
+
286
+ syntax_error!(Errors::AttributeClosingDelimiterNotFoundError) if @scanner.eos?
277
287
  end
278
288
  end
279
289
 
@@ -300,12 +310,12 @@ module Slimi
300
310
  attribute_value << @scanner[1] << "\n"
301
311
  else
302
312
  if count.positive?
303
- if opening_delimiter && @scanner.skip(::Regexp.escape(opening_delimiter))
313
+ if opening_delimiter && @scanner.match?(/#{::Regexp.escape(opening_delimiter)}/)
304
314
  count += 1
305
- elsif closing_delimiter && @scanner.skip(::Regexp.escape(closing_delimiter))
315
+ elsif closing_delimiter && @scanner.match?(/#{::Regexp.escape(closing_delimiter)}/)
306
316
  count -= 1
307
317
  end
308
- elsif @scanner.skip(@ruby_attribute_delimiter_regexp)
318
+ elsif @scanner.match?(@ruby_attribute_delimiter_regexp)
309
319
  count = 1
310
320
  opening_delimiter = @scanner.matched
311
321
  closing_delimiter = @ruby_attribute_delimiters[opening_delimiter]
@@ -503,9 +513,13 @@ module Slimi
503
513
  interpolate = parse_interpolate_line
504
514
  result << interpolate if interpolate
505
515
 
506
- loop do
507
- break unless @scanner.match?(/\r?\n[ \t]*/)
516
+ until @scanner.eos?
517
+ if @scanner.skip(/\r?\n[ \t]*(?=\r?\n)/)
518
+ result << [:newline]
519
+ next
520
+ end
508
521
 
522
+ @scanner.match?(/\r?\n[ \t]*/)
509
523
  indent = indent_from_last_match
510
524
  break if indent <= @indents.last
511
525
 
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ class RailsTemplateHandler
5
+ def initialize
6
+ @engine = Engine.new
7
+ end
8
+
9
+ def call(template, source = nil)
10
+ source ||= template.source
11
+ @engine.call(source)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ class Railtie < ::Rails::Railtie
5
+ initializer 'Register Slimi template handler' do
6
+ ::ActiveSupport.on_load(:action_view) do
7
+ ::ActionView::Template.register_template_handler(
8
+ RailsTemplateHandler.new
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
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.4.0'
4
+ VERSION = '0.5.1'
5
5
  end
data/lib/slimi.rb CHANGED
@@ -3,8 +3,12 @@
3
3
  require_relative 'slimi/version'
4
4
 
5
5
  module Slimi
6
+ autoload :Engine, 'slimi/engine'
6
7
  autoload :Errors, 'slimi/errors'
7
8
  autoload :Filters, 'slimi/filters'
8
9
  autoload :Parser, 'slimi/parser'
10
+ autoload :RailsTemplateHandler, 'slimi/rails_template_handler'
9
11
  autoload :Range, 'slimi/range'
10
12
  end
13
+
14
+ require_relative 'slimi/railtie' if defined?(Rails)
data/slimi.gemspec CHANGED
@@ -32,4 +32,5 @@ Gem::Specification.new do |spec|
32
32
  }
33
33
 
34
34
  spec.add_dependency 'temple'
35
+ spec.add_dependency 'tilt'
35
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slimi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-24 00:00:00.000000000 Z
11
+ date: 2022-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: temple
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tilt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description:
28
42
  email:
29
43
  - r7kamura@gmail.com
@@ -43,11 +57,21 @@ files:
43
57
  - bin/console
44
58
  - bin/setup
45
59
  - lib/slimi.rb
60
+ - lib/slimi/engine.rb
46
61
  - lib/slimi/errors.rb
47
62
  - lib/slimi/filters.rb
63
+ - lib/slimi/filters/attribute.rb
64
+ - lib/slimi/filters/control.rb
65
+ - lib/slimi/filters/do_inserter.rb
66
+ - lib/slimi/filters/embedded.rb
67
+ - lib/slimi/filters/end_inserter.rb
48
68
  - lib/slimi/filters/interpolation.rb
69
+ - lib/slimi/filters/output.rb
70
+ - lib/slimi/filters/text.rb
49
71
  - lib/slimi/filters/unposition.rb
50
72
  - lib/slimi/parser.rb
73
+ - lib/slimi/rails_template_handler.rb
74
+ - lib/slimi/railtie.rb
51
75
  - lib/slimi/range.rb
52
76
  - lib/slimi/version.rb
53
77
  - slimi.gemspec