slimi 0.3.0 → 0.5.0

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: 69032d72b1695bfe2afc7a11dbb965e4abf252a7c42caab252e972bcb45c13f8
4
- data.tar.gz: af3822a5d2375300d4b07f0114c30c933eb215c88bfcee1bd6cc4938d769d76e
3
+ metadata.gz: 94ce8aacd3d29798f0831cedcd033b1706b2ccebe85264cff5321d8671d7fda3
4
+ data.tar.gz: 5cbe5fe9791eafac5951f02853bb9600efb726f10d4a990d96b8ef5bfb2c4c38
5
5
  SHA512:
6
- metadata.gz: b59318e78f3c7c83e67334326992babdc60d3d299ed8973778f7c3c463b1eaaaacbefafe20ee8f7072eff13db72c3a88c5a0cae7f4ecd18629bc9e8a35645599
7
- data.tar.gz: 173fdc474e985a89ead0c473d25bcb2bb4deb586679d17bee18d5384b29e21900e79e03567ed302bce9ac37b81dc4ad29ed3b837eeeac5027d7db57ed3879c4b
6
+ metadata.gz: ba6180bd2e6e890dd2149e19390a4cb030c85ddbaac97f89d259fc9cd476673ea62cc788f84654561642ec6e86055ac5c0ae0cd8d3f4eb86a7225ca200b82d30
7
+ data.tar.gz: 537418a830f3b7d791e98ac107eea63732ede37d531c1d879eb9ded7588ab2a4897569fb05a552e73f543d6a4ce159fe9a66c0f0c2d6cd870e9976d58b6a46a2
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,11 +2,43 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.0 - 2022-01-02
6
+
7
+ ### Added
8
+
9
+ - Add Slimi::Engine.
10
+
11
+ ## 0.4.2 - 2021-12-27
12
+
13
+ ### Fixed
14
+
15
+ - Fix bug on quoted attribute parser.
16
+ - Remove trailing line-ending from source line of syntax error message.
17
+ - Support multi-line attributes.
18
+
19
+ ## 0.4.1 - 2021-12-26
20
+
21
+ ### Fixed
22
+
23
+ - Fix bug on parsing Ruby attribute value.
24
+ - Fix bug on empty line in text block.
25
+
26
+ ## 0.4.0 - 2021-12-25
27
+
28
+ ### Added
29
+
30
+ - Support :file option on parser for showing correct file path on syntax error.
31
+
32
+ ### Fixed
33
+
34
+ - Fix NameError on unknown line indicator.
35
+ - Fix bug that default parser options are not used.
36
+
5
37
  ## 0.3.0 - 2021-12-24
6
38
 
7
39
  ### Added
8
40
 
9
- - Support unquoted attributes.
41
+ - Support Ruby attributes.
10
42
 
11
43
  ### Fixed
12
44
 
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.3.0)
4
+ slimi (0.5.0)
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,18 @@
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
-
32
- TBD.
@@ -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
 
@@ -57,7 +60,7 @@ module Slimi
57
60
  class UnexpectedTextAfterClosedTagError < SlimSyntaxError
58
61
  end
59
62
 
60
- class UnknownLineIndicator < SlimSyntaxError
63
+ class UnknownLineIndicatorError < SlimSyntaxError
61
64
  end
62
65
  end
63
66
  end
@@ -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
@@ -6,6 +6,7 @@ require 'temple'
6
6
  module Slimi
7
7
  class Parser < ::Temple::Parser
8
8
  define_options(
9
+ :file,
9
10
  attr_list_delims: {
10
11
  '(' => ')',
11
12
  '[' => ']',
@@ -22,8 +23,9 @@ module Slimi
22
23
  }
23
24
  )
24
25
 
25
- def initialize(options = {})
26
+ def initialize(_options = {})
26
27
  super
28
+ @file_path = options[:file] || '(__TEMPLATE__)'
27
29
  factory = Factory.new(
28
30
  attribute_delimiters: options[:attr_list_delims] || {},
29
31
  default_tag: options[:default_tag] || 'div',
@@ -69,7 +71,7 @@ module Slimi
69
71
  parse_embedded_template ||
70
72
  parse_doctype ||
71
73
  parse_tag ||
72
- raise(Errors::UnknownLineIndicatorError)
74
+ syntax_error!(Errors::UnknownLineIndicatorError)
73
75
  end
74
76
 
75
77
  # Parse blank line.
@@ -198,8 +200,6 @@ module Slimi
198
200
  marker = @scanner[1]
199
201
  attribute_value = @scanner[2]
200
202
  attribute_names = @attribute_shortcuts[marker]
201
- raise 'Illegal shortcut' unless attribute_names
202
-
203
203
  attribute_names.map do |attribute_name|
204
204
  result << [:html, :attr, attribute_name.to_s, [:static, attribute_value]]
205
205
  end
@@ -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
@@ -273,7 +279,10 @@ module Slimi
273
279
  elsif @scanner.skip(attribute_delimiter_closing_part_regexp) # rubocop:disable Lint/DuplicateBranch
274
280
  break
275
281
  else
276
- raise ::NotImplementedError
282
+ @scanner.skip(/[ \t]+/)
283
+ expect_line_ending
284
+
285
+ syntax_error!(Errors::AttributeClosingDelimiterNotFoundError) if @scanner.eos?
277
286
  end
278
287
  end
279
288
 
@@ -300,12 +309,12 @@ module Slimi
300
309
  attribute_value << @scanner[1] << "\n"
301
310
  else
302
311
  if count.positive?
303
- if opening_delimiter && @scanner.skip(::Regexp.escape(opening_delimiter))
312
+ if opening_delimiter && @scanner.match?(/#{::Regexp.escape(opening_delimiter)}/)
304
313
  count += 1
305
- elsif closing_delimiter && @scanner.skip(::Regexp.escape(closing_delimiter))
314
+ elsif closing_delimiter && @scanner.match?(/#{::Regexp.escape(closing_delimiter)}/)
306
315
  count -= 1
307
316
  end
308
- elsif @scanner.skip(@ruby_attribute_delimiter_regexp)
317
+ elsif @scanner.match?(@ruby_attribute_delimiter_regexp)
309
318
  count = 1
310
319
  opening_delimiter = @scanner.matched
311
320
  closing_delimiter = @ruby_attribute_delimiters[opening_delimiter]
@@ -503,9 +512,13 @@ module Slimi
503
512
  interpolate = parse_interpolate_line
504
513
  result << interpolate if interpolate
505
514
 
506
- loop do
507
- break unless @scanner.match?(/\r?\n[ \t]*/)
515
+ until @scanner.eos?
516
+ if @scanner.skip(/\r?\n[ \t]*(?=\r?\n)/)
517
+ result << [:newline]
518
+ next
519
+ end
508
520
 
521
+ @scanner.match?(/\r?\n[ \t]*/)
509
522
  indent = indent_from_last_match
510
523
  break if indent <= @indents.last
511
524
 
@@ -556,7 +569,7 @@ module Slimi
556
569
  range = Range.new(index: @scanner.charpos, source: @scanner.string)
557
570
  raise syntax_error_class.new(
558
571
  column: range.column,
559
- file_path: '(__TEMPLATE__)',
572
+ file_path: @file_path,
560
573
  line: range.line,
561
574
  line_number: range.line_number
562
575
  )
@@ -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.3.0'
4
+ VERSION = '0.5.0'
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.3.0
4
+ version: 0.5.0
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-23 00:00:00.000000000 Z
11
+ date: 2022-01-01 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