slimi 0.4.2 → 0.7.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: a503c590465e63f43b6d9ef8047700ff415bfea923df65be323f2d37f5d94b71
4
- data.tar.gz: 4ad486738d38c81b670cb693afa31c9c7f8acb92ab05a844d47cf3df1d0219c8
3
+ metadata.gz: '09a450cbc4d4a536b55bee031d9b76345aee45e82d112563d83cdadc1476a82c'
4
+ data.tar.gz: 781931ba445275b0bc24157df457dde36e49f74b7c48afedcbfa8cb279c6a401
5
5
  SHA512:
6
- metadata.gz: '08dc1c84baa6a338564757e6c28143a77f1ecc03eb4ba7f72bf2af82ce673eb2c573debbbbf5eaf16673b53e07a49dd3493888875e6b9e5016557ab7f8373368'
7
- data.tar.gz: dd3bb74308363284ff648f80d2dcc37fe9b7faa75df2b31210ca4567fa088debca11b5831bb8ec745800bd15e9886e5ff5bf6074bcb6aa673ca826421e10bc31
6
+ metadata.gz: 3810040838f6d5eaf636326eaa4021edd62a810d508b41efe7f27a29702c444a783cd2f0bfbd9dedc535d52738bc2446c55df08e6a6e0acee3cb3743ae7798f8
7
+ data.tar.gz: 625e3d9bd3be61d376364388f6ef86f555fe9065a73d802e907fcb1a6238d4707d42a5d4597acc05794d6e46d4623ebb01007836b2b8bb5ae2884158e3d16ad1
data/.rubocop.yml CHANGED
@@ -24,8 +24,14 @@ RSpec/ImplicitSubject:
24
24
  RSpec/MultipleExpectations:
25
25
  Enabled: false
26
26
 
27
+ RSpec/MultipleMemoizedHelpers:
28
+ Enabled: false
29
+
27
30
  RSpec/NamedSubject:
28
31
  Enabled: false
29
32
 
33
+ Security/Eval:
34
+ Enabled: false
35
+
30
36
  Style/Documentation:
31
37
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.7.0 - 2022-01-04
6
+
7
+ ### Added
8
+
9
+ - Add `slimi` executable.
10
+
11
+ ### Fixed
12
+
13
+ - Remove preceding white spaces from HTML comment.
14
+ - Fix bug that :generator option was not working.
15
+ - Fix bug that Ruby code in nested line can be problem on compilation.
16
+
17
+ ## 0.6.0 - 2022-01-03
18
+
19
+ ### Added
20
+
21
+ - Support annotate_rendered_view_with_filenames.
22
+
23
+ ### Changed
24
+
25
+ - Rename expression name from slim to slimi.
26
+
27
+ ### Fixed
28
+
29
+ - Fix bug at registering handler to ActionView.
30
+ - Fix Engine options at RailsTemplateHandler.
31
+ - Define missing :generator option at Engine.
32
+
33
+ ## 0.5.1 - 2022-01-02
34
+
35
+ ### Changed
36
+
37
+ - Wrap slim attrvalue by slimi position expression.
38
+
39
+ ## 0.5.0 - 2022-01-02
40
+
41
+ ### Added
42
+
43
+ - Add Slimi::Engine.
44
+
5
45
  ## 0.4.2 - 2021-12-27
6
46
 
7
47
  ### Fixed
data/Gemfile CHANGED
@@ -5,8 +5,8 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in slimi.gemspec
6
6
  gemspec
7
7
 
8
+ gem 'actionview'
8
9
  gem 'rake', '~> 13.0'
9
10
  gem 'rspec'
10
11
  gem 'rubocop'
11
12
  gem 'rubocop-rspec'
12
- gem 'slim'
data/Gemfile.lock CHANGED
@@ -1,17 +1,48 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- slimi (0.4.2)
4
+ slimi (0.7.0)
5
5
  temple
6
+ thor
7
+ tilt
6
8
 
7
9
  GEM
8
10
  remote: https://rubygems.org/
9
11
  specs:
12
+ actionview (7.0.0)
13
+ activesupport (= 7.0.0)
14
+ builder (~> 3.1)
15
+ erubi (~> 1.4)
16
+ rails-dom-testing (~> 2.0)
17
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
18
+ activesupport (7.0.0)
19
+ concurrent-ruby (~> 1.0, >= 1.0.2)
20
+ i18n (>= 1.6, < 2)
21
+ minitest (>= 5.1)
22
+ tzinfo (~> 2.0)
10
23
  ast (2.4.2)
24
+ builder (3.2.4)
25
+ concurrent-ruby (1.1.9)
26
+ crass (1.0.6)
11
27
  diff-lcs (1.4.4)
28
+ erubi (1.10.0)
29
+ i18n (1.8.11)
30
+ concurrent-ruby (~> 1.0)
31
+ loofah (2.13.0)
32
+ crass (~> 1.0.2)
33
+ nokogiri (>= 1.5.9)
34
+ minitest (5.15.0)
35
+ nokogiri (1.12.5-x86_64-linux)
36
+ racc (~> 1.4)
12
37
  parallel (1.21.0)
13
38
  parser (3.0.3.2)
14
39
  ast (~> 2.4.1)
40
+ racc (1.6.0)
41
+ rails-dom-testing (2.0.3)
42
+ activesupport (>= 4.2.0)
43
+ nokogiri (>= 1.6)
44
+ rails-html-sanitizer (1.4.2)
45
+ loofah (~> 2.3)
15
46
  rainbow (3.0.0)
16
47
  rake (13.0.6)
17
48
  regexp_parser (2.2.0)
@@ -43,22 +74,22 @@ GEM
43
74
  rubocop-rspec (2.6.0)
44
75
  rubocop (~> 1.19)
45
76
  ruby-progressbar (1.11.0)
46
- slim (4.1.0)
47
- temple (>= 0.7.6, < 0.9)
48
- tilt (>= 2.0.6, < 2.1)
49
77
  temple (0.8.2)
78
+ thor (1.1.0)
50
79
  tilt (2.0.10)
80
+ tzinfo (2.0.4)
81
+ concurrent-ruby (~> 1.0)
51
82
  unicode-display_width (2.1.0)
52
83
 
53
84
  PLATFORMS
54
85
  x86_64-linux
55
86
 
56
87
  DEPENDENCIES
88
+ actionview
57
89
  rake (~> 13.0)
58
90
  rspec
59
91
  rubocop
60
92
  rubocop-rspec
61
- slim
62
93
  slimi!
63
94
 
64
95
  BUNDLED WITH
data/README.md CHANGED
@@ -5,28 +5,63 @@
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:
16
+ ## Usage
19
17
 
20
- ```
21
- bundle install
22
- ```
18
+ ### Rails
23
19
 
24
- Or install it yourself as:
20
+ Add this line to your application's Gemfile.
25
21
 
26
- ```
27
- gem install slimi
22
+ ```ruby
23
+ gem 'slimi'
28
24
  ```
29
25
 
30
- ## Usage
26
+ This will cause `app/views/**/*.slim` files to be rendered by Slimi.
27
+
28
+ ## Compatibility
31
29
 
32
- TBD.
30
+ - Line indicators
31
+ - [x] Vebatim text
32
+ - [x] Inline HTML
33
+ - [x] Control
34
+ - [x] Output
35
+ - [x] HTML comment
36
+ - [x] Code comment
37
+ - [x] IE conditional comment
38
+ - Tags
39
+ - [x] Doctype declaration
40
+ - [x] Closed tags
41
+ - [x] Trailing and leading white space
42
+ - [x] Inline tags
43
+ - [x] Text content
44
+ - [x] Dynamic content
45
+ - [x] Tag shortcuts
46
+ - [ ] Dynamic tags
47
+ - Attributes
48
+ - [x] Attributes wrapper
49
+ - [x] Quoted attributes
50
+ - [x] Ruby attributes
51
+ - [x] Boolean attributes
52
+ - [x] Attribute merging
53
+ - [x] Attribute shortcuts
54
+ - [ ] Splat attributes
55
+ - Plugins
56
+ - [ ] Include partials
57
+ - [ ] Translator/I18n
58
+ - [ ] Logic-less mode
59
+ - [ ] Smart text mode
60
+ - CLI
61
+ - [x] Convert Slim to Ruby
62
+ - [x] Convert Slim to HTML
63
+ - [x] Convert Slim to ERB
64
+ - Slimi-only features
65
+ - [x] Embedded Ruby code location
66
+ - [x] Support for annotate_rendered_view_with_filenames
67
+ - [x] Convert Slim to Temple expression by CLI
data/exe/slimi ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+ require 'slimi'
6
+
7
+ Slimi::Cli.start
data/lib/slimi/cli.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Slimi
6
+ # Provide CLI features.
7
+ class Cli < ::Thor
8
+ desc 'compile', 'Convert Slim into Ruby'
9
+ def compile
10
+ slim = $stdin.read
11
+ ruby = Engine.new.call(slim)
12
+ puts ruby
13
+ end
14
+
15
+ desc 'erb', 'Convert Slim into ERB'
16
+ def erb
17
+ slim = $stdin.read
18
+ expression = ErbConverter.new.call(slim)
19
+ puts expression
20
+ end
21
+
22
+ desc 'parse', 'Convert Slim into Temple expression'
23
+ def parse
24
+ slim = $stdin.read
25
+ expression = Parser.new.call(slim)
26
+ pp expression
27
+ end
28
+
29
+ desc 'render', 'Convert Slim into HTML'
30
+ def render
31
+ slim = $stdin.read
32
+ ruby = Engine.new.call(slim)
33
+ result = eval(ruby)
34
+ puts result
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temple'
4
+
5
+ module Slimi
6
+ # Convert Slim into Ruby.
7
+ class Engine < ::Temple::Engine
8
+ define_options(
9
+ attr_quote: '"',
10
+ default_tag: 'div',
11
+ format: :xhtml,
12
+ generator: ::Temple::Generators::StringBuffer,
13
+ merge_attrs: { 'class' => ' ' },
14
+ pretty: false,
15
+ sort_attrs: true
16
+ )
17
+
18
+ use Parser
19
+ use Filters::Embedded
20
+ use Filters::Interpolation
21
+ use Filters::Unposition
22
+ use Filters::DoInserter
23
+ use Filters::EndInserter
24
+ use Filters::Control
25
+ use Filters::Output
26
+ use Filters::Text
27
+ html :AttributeSorter
28
+ html :AttributeMerger
29
+ use Filters::Attribute
30
+ use(:AttributeRemover) { ::Temple::HTML::AttributeRemover.new(remove_empty_attrs: options[:merge_attrs].keys) }
31
+ html :Pretty
32
+ use Filters::Amble
33
+ filter :Escapable
34
+ filter :ControlFlow
35
+ filter :MultiFlattener
36
+ filter :StaticMerger
37
+ use(:Generator) { options[:generator] }
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ # Convert Slim into ERB.
5
+ class ErbConverter < Engine
6
+ replace :StaticMerger, ::Temple::Filters::CodeMerger
7
+ replace :Generator, Temple::Generators::ERB
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Support Rails annotate_rendered_view_with_filenames feature.
6
+ class Amble < Base
7
+ define_options(
8
+ :postamble,
9
+ :preamble
10
+ )
11
+
12
+ # @param [Array] expression
13
+ # @return [Array]
14
+ def call(expression)
15
+ result = %i[multi]
16
+ result << [:static, options[:preamble]] if options[:preamble]
17
+ result << expression
18
+ result << [:static, options[:postamble]] if options[:postamble]
19
+ result
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Handle `[:slimi, :attributes, ...]`.
6
+ class Attribute < Base
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] == :slimi && 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_slimi_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,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temple'
4
+
5
+ module Slimi
6
+ module Filters
7
+ # Pass-through some expressions which are unknown for Temple.
8
+ class Base < ::Temple::HTML::Filter
9
+ # @param [String] code
10
+ # @param [Array] expression
11
+ # @return [Array]
12
+ def on_slimi_control(code, expression)
13
+ [:slimi, :control, code, compile(expression)]
14
+ end
15
+
16
+ # @param [String] type
17
+ # @param [String] code
18
+ # @param [Array] expression
19
+ # @param [Array] attributes
20
+ # @return [Array]
21
+ def on_slimi_embedded(type, expression, attributes)
22
+ [:slimi, :embedded, type, compile(expression), attributes]
23
+ end
24
+
25
+ # @param [Boolean] escape
26
+ # @param [String] code
27
+ # @param [Array] expression
28
+ # @return [Array]
29
+ def on_slimi_output(escape, code, expression)
30
+ [:slimi, :output, escape, code, compile(expression)]
31
+ end
32
+
33
+ # @param [Integer] begin_
34
+ # @param [Integer] end_
35
+ # @param [Array] expression
36
+ # @return [Array]
37
+ def on_slimi_position(begin_, end_, expression)
38
+ [:slimi, :position, begin_, end_, compile(expression)]
39
+ end
40
+
41
+ # @param [String] type
42
+ # @param [Array] expression
43
+ # @return [Array]
44
+ def on_slimi_text(type, expression)
45
+ [:slimi, :text, type, compile(expression)]
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Handle `[:slimi, :control, code, multi]`.
6
+ class Control < Base
7
+ # @param [String] code
8
+ # @param [Array] multi
9
+ # @return [Array]
10
+ def on_slimi_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 < Base
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_slimi_control(code, expression)
13
+ code += ' do' unless code.match?(VALID_RUBY_LINE_REGEXP) || empty_exp?(expression)
14
+ [:slimi, :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_slimi_output(escape, code, expression)
22
+ code += ' do' unless code.match?(VALID_RUBY_LINE_REGEXP) || empty_exp?(expression)
23
+ [:slimi, :output, escape, code, compile(expression)]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tilt'
4
+
5
+ module Slimi
6
+ module Filters
7
+ # @api private
8
+ class TextCollector < Base
9
+ def call(exp)
10
+ @collected = ''
11
+ super(exp)
12
+ @collected
13
+ end
14
+
15
+ def on_slimi_interpolate(text)
16
+ @collected << text
17
+ nil
18
+ end
19
+ end
20
+
21
+ # @api private
22
+ class NewlineCollector < Base
23
+ def call(exp)
24
+ @collected = [:multi]
25
+ super(exp)
26
+ @collected
27
+ end
28
+
29
+ def on_newline
30
+ @collected << [:newline]
31
+ nil
32
+ end
33
+ end
34
+
35
+ # @api private
36
+ class OutputProtector < Base
37
+ def call(exp)
38
+ @protect = []
39
+ @collected = ''
40
+ @tag = "%#{object_id.abs.to_s(36)}%"
41
+ super(exp)
42
+ @collected
43
+ end
44
+
45
+ def on_static(text)
46
+ @collected << text
47
+ nil
48
+ end
49
+
50
+ def on_slimi_output(escape, text, content)
51
+ @collected << @tag
52
+ @protect << [:slimi, :output, escape, text, content]
53
+ nil
54
+ end
55
+
56
+ def unprotect(text)
57
+ block = [:multi]
58
+ while text =~ /#{@tag}/
59
+ block << [:static, Regexp.last_match.pre_match]
60
+ block << @protect.shift
61
+ text = Regexp.last_match.post_match
62
+ end
63
+ block << [:static, text]
64
+ end
65
+ end
66
+
67
+ # Temple filter which processes embedded engines
68
+ # @api private
69
+ class Embedded < Base
70
+ @engines = {}
71
+
72
+ class << self
73
+ attr_reader :engines
74
+
75
+ # Register embedded engine
76
+ #
77
+ # @param [String] name Name of the engine
78
+ # @param [Class] klass Engine class
79
+ # @param option_filter List of options to pass to engine.
80
+ # Last argument can be default option hash.
81
+ def register(name, klass, *option_filter)
82
+ name = name.to_sym
83
+ local_options = option_filter.last.respond_to?(:to_hash) ? option_filter.pop.to_hash : {}
84
+ define_options(name, *option_filter)
85
+ klass.define_options(name)
86
+ engines[name.to_sym] = proc do |options|
87
+ klass.new({}.update(options).delete_if { |k, _v| !option_filter.include?(k) && k != name }.update(local_options))
88
+ end
89
+ end
90
+
91
+ def create(name, options)
92
+ constructor = engines[name] || raise(Temple::FilterError, "Embedded engine #{name} not found")
93
+ constructor.call(options)
94
+ end
95
+ end
96
+
97
+ define_options :enable_engines, :disable_engines
98
+
99
+ def initialize(opts = {})
100
+ super
101
+ @engines = {}
102
+ @enabled = normalize_engine_list(options[:enable_engines])
103
+ @disabled = normalize_engine_list(options[:disable_engines])
104
+ end
105
+
106
+ def on_slimi_embedded(name, body, attrs)
107
+ name = name.to_sym
108
+ raise(Temple::FilterError, "Embedded engine #{name} is disabled") unless enabled?(name)
109
+
110
+ @engines[name] ||= self.class.create(name, options)
111
+ @engines[name].on_slimi_embedded(name, body, attrs)
112
+ end
113
+
114
+ def enabled?(name)
115
+ (!@enabled || @enabled.include?(name)) &&
116
+ (!@disabled || !@disabled.include?(name))
117
+ end
118
+
119
+ protected
120
+
121
+ def normalize_engine_list(list)
122
+ raise(ArgumentError, 'Option :enable_engines/:disable_engines must be String or Symbol list') unless !list || list.is_a?(Array)
123
+
124
+ list&.map(&:to_sym)
125
+ end
126
+
127
+ class Engine < Base
128
+ protected
129
+
130
+ def collect_text(body)
131
+ @text_collector ||= TextCollector.new
132
+ @text_collector.call(body)
133
+ end
134
+
135
+ def collect_newlines(body)
136
+ @newline_collector ||= NewlineCollector.new
137
+ @newline_collector.call(body)
138
+ end
139
+ end
140
+
141
+ # Basic tilt engine
142
+ class TiltEngine < Engine
143
+ def on_slimi_embedded(engine, body, _attrs)
144
+ tilt_engine = Tilt[engine] || raise(Temple::FilterError, "Tilt engine #{engine} is not available.")
145
+ tilt_options = options[engine.to_sym] || {}
146
+ tilt_options[:default_encoding] ||= 'utf-8'
147
+ [:multi, tilt_render(tilt_engine, tilt_options, collect_text(body)), collect_newlines(body)]
148
+ end
149
+
150
+ protected
151
+
152
+ def tilt_render(tilt_engine, tilt_options, text)
153
+ [:static, tilt_engine.new(tilt_options) { text }.render]
154
+ end
155
+ end
156
+
157
+ # Sass engine which supports :pretty option
158
+ class SassEngine < TiltEngine
159
+ define_options :pretty
160
+
161
+ protected
162
+
163
+ def tilt_render(tilt_engine, tilt_options, text)
164
+ text = tilt_engine.new(tilt_options.merge(
165
+ style: options[:pretty] ? :expanded : :compressed,
166
+ cache: false
167
+ )) { text }.render
168
+ text = text.chomp
169
+ [:static, text]
170
+ end
171
+ end
172
+
173
+ # Static template with interpolated ruby code
174
+ class InterpolateTiltEngine < TiltEngine
175
+ def collect_text(body)
176
+ output_protector.call(interpolation.call(body))
177
+ end
178
+
179
+ def tilt_render(tilt_engine, tilt_options, text)
180
+ output_protector.unprotect(tilt_engine.new(tilt_options) { text }.render)
181
+ end
182
+
183
+ private
184
+
185
+ def interpolation
186
+ @interpolation ||= Interpolation.new
187
+ end
188
+
189
+ def output_protector
190
+ @output_protector ||= OutputProtector.new
191
+ end
192
+ end
193
+
194
+ # Tag wrapper engine
195
+ # Generates a html tag and wraps another engine (specified via :engine option)
196
+ class TagEngine < Engine
197
+ disable_option_validator!
198
+
199
+ def on_slimi_embedded(engine, body, attrs)
200
+ unless options[:attributes].empty?
201
+ options[:attributes].map do |k, v|
202
+ attrs << [:html, :attr, k, [:static, v]]
203
+ end
204
+ end
205
+
206
+ if options[:engine]
207
+ opts = {}.update(options)
208
+ opts.delete(:engine)
209
+ opts.delete(:tag)
210
+ opts.delete(:attributes)
211
+ @engine ||= options[:engine].new(opts)
212
+ body = @engine.on_slimi_embedded(engine, body, attrs)
213
+ end
214
+
215
+ [:html, :tag, options[:tag], attrs, body]
216
+ end
217
+ end
218
+
219
+ # Javascript wrapper engine.
220
+ # Like TagEngine, but can wrap content in html comment or cdata.
221
+ class JavaScriptEngine < TagEngine
222
+ disable_option_validator!
223
+
224
+ set_options tag: :script, attributes: {}
225
+
226
+ def on_slimi_embedded(engine, body, attrs)
227
+ super(engine, [:html, :js, body], attrs)
228
+ end
229
+ end
230
+
231
+ # Embeds ruby code
232
+ class RubyEngine < Engine
233
+ def on_slimi_embedded(_engine, body, _attrs)
234
+ [:multi, [:newline], [:code, "#{collect_text(body)}\n"]]
235
+ end
236
+ end
237
+
238
+ # These engines are executed at compile time, embedded ruby is interpolated
239
+ register :markdown, InterpolateTiltEngine
240
+ register :textile, InterpolateTiltEngine
241
+ register :rdoc, InterpolateTiltEngine
242
+
243
+ # These engines are executed at compile time
244
+ register :coffee, JavaScriptEngine, engine: TiltEngine
245
+ register :less, TagEngine, tag: :style, attributes: { type: 'text/css' }, engine: TiltEngine
246
+ register :sass, TagEngine, :pretty, tag: :style, attributes: { type: 'text/css' }, engine: SassEngine
247
+ register :scss, TagEngine, :pretty, tag: :style, attributes: { type: 'text/css' }, engine: SassEngine
248
+
249
+ # Embedded javascript/css
250
+ register :javascript, JavaScriptEngine
251
+ register :css, TagEngine, tag: :style, attributes: { type: 'text/css' }
252
+
253
+ # Embedded ruby code
254
+ register :ruby, RubyEngine
255
+ end
256
+ end
257
+ 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 < Base
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] == :slimi && @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
@@ -4,29 +4,7 @@ require 'strscan'
4
4
 
5
5
  module Slimi
6
6
  module Filters
7
- class Interpolation
8
- def initialize(*); end
9
-
10
- def call(node)
11
- convert(node)
12
- end
13
-
14
- private
15
-
16
- def convert(value)
17
- if value.instance_of?(::Array)
18
- if value[0] == :slimi && value[1] == :interpolate
19
- on_slimi_interpolate(value[2], value[3], value[4])
20
- else
21
- value.map do |element|
22
- call(element)
23
- end
24
- end
25
- else
26
- value
27
- end
28
- end
29
-
7
+ class Interpolation < Base
30
8
  # @param [Integer] begin_
31
9
  # @param [Integer] end_
32
10
  # @return [Array] S-expression.
@@ -47,7 +25,7 @@ module Slimi
47
25
  else
48
26
  escape = false
49
27
  end
50
- block << [:slimi, :position, begin2, begin2 + code.length, [:slim, :output, escape, code, [:multi]]]
28
+ block << [:slimi, :position, begin2, begin2 + code.length, [:slimi, :output, escape, code, [:multi]]]
51
29
  elsif (value = scanner.scan(/([#\\]?[^#\\]*([#\\][^\\\#{][^#\\]*)*)/)) # rubocop:disable Lint/DuplicateBranch
52
30
  block << [:static, value]
53
31
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ module Filters
5
+ # Handle `[:slimi, :output, escape, code, multi]`.
6
+ class Output < Base
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_slimi_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 `[:slimi, :text, multi]`.
6
+ class Text < Base
7
+ # @param [Symbol] _type
8
+ # @param [Array] multi
9
+ # @return [Array]
10
+ def on_slimi_text(_type, multi)
11
+ compile(multi)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,29 +2,13 @@
2
2
 
3
3
  module Slimi
4
4
  module Filters
5
- class Unposition
6
- def initialize(*); end
7
-
8
- # @param [Array] node S-expression.
9
- # @return [Array] S-expression.
10
- def call(node)
11
- convert(node)
12
- end
13
-
14
- private
15
-
16
- def convert(value)
17
- if value.instance_of?(::Array)
18
- if value[0] == :slimi && value[1] == :position
19
- call(value[4])
20
- else
21
- value.map do |element|
22
- call(element)
23
- end
24
- end
25
- else
26
- value
27
- end
5
+ class Unposition < Base
6
+ # @param [Integer] _begin
7
+ # @param [Integer] _end
8
+ # @param [Array] expression
9
+ # @return [Array]
10
+ def on_slimi_position(_begin, _end, expression)
11
+ compile(expression)
28
12
  end
29
13
  end
30
14
  end
data/lib/slimi/filters.rb CHANGED
@@ -2,7 +2,16 @@
2
2
 
3
3
  module Slimi
4
4
  module Filters
5
+ autoload :Amble, 'slimi/filters/amble'
6
+ autoload :Attribute, 'slimi/filters/attribute'
7
+ autoload :Base, 'slimi/filters/base'
8
+ autoload :Control, 'slimi/filters/control'
9
+ autoload :DoInserter, 'slimi/filters/do_inserter'
10
+ autoload :Embedded, 'slimi/filters/embedded'
11
+ autoload :EndInserter, 'slimi/filters/end_inserter'
5
12
  autoload :Interpolation, 'slimi/filters/interpolation'
13
+ autoload :Output, 'slimi/filters/output'
14
+ autoload :Text, 'slimi/filters/text'
6
15
  autoload :Unposition, 'slimi/filters/unposition'
7
16
  end
8
17
  end
data/lib/slimi/parser.rb CHANGED
@@ -115,7 +115,7 @@ module Slimi
115
115
 
116
116
  embedded_template_engine_name = @scanner[1]
117
117
  attributes = parse_attributes
118
- @stacks.last << [:slim, :embedded, embedded_template_engine_name, parse_text_block, attributes]
118
+ @stacks.last << [:slimi, :embedded, embedded_template_engine_name, parse_text_block, attributes]
119
119
  end
120
120
 
121
121
  # @return [Boolean]
@@ -150,14 +150,14 @@ module Slimi
150
150
  block = [:multi]
151
151
  @stacks.last.insert(-2, [:static, ' ']) if with_leading_white_space2
152
152
  @scanner.skip(/[ \t]+/)
153
- tag << with_position { [:slim, :output, escape, parse_broken_lines, block] }
153
+ tag << with_position { [:slimi, :output, escape, parse_broken_lines, block] }
154
154
  @stacks.last << [:static, ' '] if with_trailing_white_space2
155
155
  @stacks << block
156
156
  elsif @scanner.skip(%r{[ \t]*/[ \t]*})
157
157
  syntax_error!(Errors::UnexpectedTextAfterClosedTagError) unless @scanner.match?(/\r?\n/)
158
158
  else
159
159
  @scanner.skip(/[ \t]+/)
160
- tag << [:slim, :text, :inline, parse_text_block]
160
+ tag << [:slimi, :text, :inline, parse_text_block]
161
161
  end
162
162
  true
163
163
  else
@@ -269,9 +269,10 @@ module Slimi
269
269
  elsif @scanner.skip(@ruby_attribute_regexp)
270
270
  attribute_name = @scanner[1]
271
271
  escape = @scanner[2].empty?
272
+ charpos = @scanner.charpos
272
273
  attribute_value = parse_ruby_attribute_value(attribute_delimiter_closing)
273
274
  syntax_error!(Errors::InvalidEmptyAttributeError) if attribute_value.empty?
274
- attributes << [:html, :attr, attribute_name, [:slim, :attrvalue, escape, attribute_value]]
275
+ attributes << [:html, :attr, attribute_name, [:slimi, :position, charpos, charpos + attribute_value.length, [:slimi, :attrvalue, escape, attribute_value]]]
275
276
  elsif !attribute_delimiter_closing_part_regexp
276
277
  break
277
278
  elsif @scanner.skip(boolean_attribute_regexp)
@@ -331,9 +332,9 @@ module Slimi
331
332
 
332
333
  # @return [Boolean]
333
334
  def parse_html_comment
334
- if @scanner.skip(%r{/!})
335
+ if @scanner.skip(%r{/![ \t]*})
335
336
  text_block = parse_text_block
336
- text = [:slim, :text, :verbatim, text_block]
337
+ text = [:slimi, :text, :verbatim, text_block]
337
338
  @stacks.last << [:html, :comment, text]
338
339
  true
339
340
  else
@@ -380,7 +381,7 @@ module Slimi
380
381
  def parse_verbatim_text_block_inner
381
382
  if @scanner.skip(/([|']) ?/)
382
383
  with_trailing_white_space = @scanner[1] == "'"
383
- @stacks.last << [:slim, :text, :verbatim, parse_text_block]
384
+ @stacks.last << [:slimi, :text, :verbatim, parse_text_block]
384
385
  @stacks.last << [:static, ' '] if with_trailing_white_space
385
386
  true
386
387
  else
@@ -419,7 +420,7 @@ module Slimi
419
420
  if @scanner.skip(/-/)
420
421
  block = [:multi]
421
422
  @scanner.skip(/[ \t]+/)
422
- @stacks.last << with_position { [:slim, :control, parse_broken_lines, block] }
423
+ @stacks.last << with_position { [:slimi, :control, parse_broken_lines, block] }
423
424
  @stacks << block
424
425
  true
425
426
  else
@@ -442,7 +443,7 @@ module Slimi
442
443
  block = [:multi]
443
444
  @stacks.last << [:static, ' '] if with_trailing_white_space
444
445
  @scanner.skip(/[ \t]+/)
445
- @stacks.last << with_position { [:slim, :output, escape, parse_broken_lines, block] }
446
+ @stacks.last << with_position { [:slimi, :output, escape, parse_broken_lines, block] }
446
447
  @stacks.last << [:static, ' '] if with_leading_white_space
447
448
  @stacks << block
448
449
  else
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slimi
4
+ # Render Slim template in response to requests from Rails.
5
+ class RailsTemplateHandler
6
+ # @param [ActionView::Template] template
7
+ # @param [String, nil] source
8
+ # @return [String]
9
+ def call(template, source = nil)
10
+ Renderer.new(
11
+ source: source,
12
+ template: template
13
+ ).call
14
+ end
15
+
16
+ # Render HTML from given source and options.
17
+ class Renderer
18
+ # @param [String] source
19
+ # @param [ActionView::Template] template
20
+ def initialize(
21
+ source:,
22
+ template:
23
+ )
24
+ @source = source
25
+ @template = template
26
+ end
27
+
28
+ # @return [String]
29
+ def call
30
+ engine.call(source)
31
+ end
32
+
33
+ private
34
+
35
+ # @return [Slimi::Engine]
36
+ def engine
37
+ Engine.new(engine_options)
38
+ end
39
+
40
+ # @return [Hash{Symbol => Object}]
41
+ def engine_options
42
+ engine_default_options.merge(engine_amble_options)
43
+ end
44
+
45
+ # @return [Hash{Symbol => Object}]
46
+ def engine_default_options
47
+ {
48
+ generator: ::Temple::Generators::RailsOutputBuffer,
49
+ streaming: true,
50
+ use_html_safe: true
51
+ }
52
+ end
53
+
54
+ # @return [Hash{Symbol => Object}]
55
+ def engine_amble_options
56
+ if with_annotate_rendered_view_with_filenames?
57
+ {
58
+ postamble: "<!-- END #{@template.short_identifier} -->\n",
59
+ preamble: "<!-- BEGIN #{@template.short_identifier} -->\n"
60
+ }
61
+ else
62
+ {}
63
+ end
64
+ end
65
+
66
+ # @return [String]
67
+ def source
68
+ @source || @template.source
69
+ end
70
+
71
+ # @return [Boolean]
72
+ def with_annotate_rendered_view_with_filenames?
73
+ ::ActionView::Base.try(:annotate_rendered_view_with_filenames) && @template.format == :html
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,14 @@
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
+ :slim,
9
+ RailsTemplateHandler.new
10
+ )
11
+ end
12
+ end
13
+ end
14
+ 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.2'
4
+ VERSION = '0.7.0'
5
5
  end
data/lib/slimi.rb CHANGED
@@ -3,8 +3,14 @@
3
3
  require_relative 'slimi/version'
4
4
 
5
5
  module Slimi
6
+ autoload :Cli, 'slimi/cli'
7
+ autoload :Engine, 'slimi/engine'
8
+ autoload :ErbConverter, 'slimi/erb_converter'
6
9
  autoload :Errors, 'slimi/errors'
7
10
  autoload :Filters, 'slimi/filters'
8
11
  autoload :Parser, 'slimi/parser'
12
+ autoload :RailsTemplateHandler, 'slimi/rails_template_handler'
9
13
  autoload :Range, 'slimi/range'
10
14
  end
15
+
16
+ require_relative 'slimi/railtie' if defined?(Rails)
data/slimi.gemspec CHANGED
@@ -32,4 +32,6 @@ Gem::Specification.new do |spec|
32
32
  }
33
33
 
34
34
  spec.add_dependency 'temple'
35
+ spec.add_dependency 'thor'
36
+ spec.add_dependency 'tilt'
35
37
  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.2
4
+ version: 0.7.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-26 00:00:00.000000000 Z
11
+ date: 2022-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: temple
@@ -24,10 +24,39 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
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'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tilt
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  description:
28
56
  email:
29
57
  - r7kamura@gmail.com
30
- executables: []
58
+ executables:
59
+ - slimi
31
60
  extensions: []
32
61
  extra_rdoc_files: []
33
62
  files:
@@ -42,12 +71,27 @@ files:
42
71
  - Rakefile
43
72
  - bin/console
44
73
  - bin/setup
74
+ - exe/slimi
45
75
  - lib/slimi.rb
76
+ - lib/slimi/cli.rb
77
+ - lib/slimi/engine.rb
78
+ - lib/slimi/erb_converter.rb
46
79
  - lib/slimi/errors.rb
47
80
  - lib/slimi/filters.rb
81
+ - lib/slimi/filters/amble.rb
82
+ - lib/slimi/filters/attribute.rb
83
+ - lib/slimi/filters/base.rb
84
+ - lib/slimi/filters/control.rb
85
+ - lib/slimi/filters/do_inserter.rb
86
+ - lib/slimi/filters/embedded.rb
87
+ - lib/slimi/filters/end_inserter.rb
48
88
  - lib/slimi/filters/interpolation.rb
89
+ - lib/slimi/filters/output.rb
90
+ - lib/slimi/filters/text.rb
49
91
  - lib/slimi/filters/unposition.rb
50
92
  - lib/slimi/parser.rb
93
+ - lib/slimi/rails_template_handler.rb
94
+ - lib/slimi/railtie.rb
51
95
  - lib/slimi/range.rb
52
96
  - lib/slimi/version.rb
53
97
  - slimi.gemspec