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 +4 -4
- data/.rubocop.yml +15 -0
- data/CHANGELOG.md +33 -1
- data/Gemfile +3 -3
- data/Gemfile.lock +7 -7
- data/README.md +9 -19
- data/lib/slimi/engine.rb +37 -0
- data/lib/slimi/errors.rb +5 -2
- data/lib/slimi/filters/attribute.rb +63 -0
- data/lib/slimi/filters/control.rb +19 -0
- data/lib/slimi/filters/do_inserter.rb +27 -0
- data/lib/slimi/filters/embedded.rb +255 -0
- data/lib/slimi/filters/end_inserter.rb +77 -0
- data/lib/slimi/filters/output.rb +33 -0
- data/lib/slimi/filters/text.rb +15 -0
- data/lib/slimi/filters.rb +7 -0
- data/lib/slimi/parser.rb +32 -19
- data/lib/slimi/rails_template_handler.rb +14 -0
- data/lib/slimi/railtie.rb +13 -0
- data/lib/slimi/version.rb +1 -1
- data/lib/slimi.rb +4 -0
- data/slimi.gemspec +1 -0
- metadata +26 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94ce8aacd3d29798f0831cedcd033b1706b2ccebe85264cff5321d8671d7fda3
|
4
|
+
data.tar.gz: 5cbe5fe9791eafac5951f02853bb9600efb726f10d4a990d96b8ef5bfb2c4c38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
41
|
+
- Support Ruby attributes.
|
10
42
|
|
11
43
|
### Fixed
|
12
44
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
slimi (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
|
57
|
-
rubocop
|
58
|
-
|
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
|
-
|
8
|
+
## Introduction
|
9
9
|
|
10
|
-
|
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
|
-
|
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
|
-
|
15
|
-
gem 'slimi'
|
16
|
-
```
|
17
|
-
|
18
|
-
And then execute:
|
19
|
-
|
20
|
-
```
|
21
|
-
bundle install
|
22
|
-
```
|
16
|
+
## Usage
|
23
17
|
|
24
|
-
|
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.
|
data/lib/slimi/engine.rb
ADDED
@@ -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
|
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(
|
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
|
-
|
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}/)
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
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.
|
312
|
+
if opening_delimiter && @scanner.match?(/#{::Regexp.escape(opening_delimiter)}/)
|
304
313
|
count += 1
|
305
|
-
elsif closing_delimiter && @scanner.
|
314
|
+
elsif closing_delimiter && @scanner.match?(/#{::Regexp.escape(closing_delimiter)}/)
|
306
315
|
count -= 1
|
307
316
|
end
|
308
|
-
elsif @scanner.
|
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
|
-
|
507
|
-
|
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:
|
572
|
+
file_path: @file_path,
|
560
573
|
line: range.line,
|
561
574
|
line_number: range.line_number
|
562
575
|
)
|
@@ -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
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
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
|
+
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:
|
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
|