slimi 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|