temple 0.6.7 → 0.10.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 +5 -5
- data/.github/workflows/test.yml +34 -0
- data/.gitignore +1 -0
- data/CHANGES +106 -1
- data/EXPRESSIONS.md +3 -2
- data/Gemfile +0 -1
- data/README.md +14 -10
- data/Rakefile +4 -11
- data/lib/temple/engine.rb +7 -3
- data/lib/temple/erb/engine.rb +5 -3
- data/lib/temple/erb/parser.rb +1 -1
- data/lib/temple/erb/trimming.rb +11 -26
- data/lib/temple/filters/ambles.rb +21 -0
- data/lib/temple/filters/encoding.rb +1 -1
- data/lib/temple/filters/eraser.rb +1 -1
- data/lib/temple/filters/escapable.rb +2 -2
- data/lib/temple/filters/remove_bom.rb +2 -9
- data/lib/temple/filters/static_analyzer.rb +30 -0
- data/lib/temple/filters/string_splitter.rb +141 -0
- data/lib/temple/filters/validator.rb +1 -1
- data/lib/temple/generator.rb +32 -6
- data/lib/temple/generators/array.rb +2 -2
- data/lib/temple/generators/array_buffer.rb +6 -5
- data/lib/temple/generators/erb.rb +1 -5
- data/lib/temple/generators/rails_output_buffer.rb +7 -8
- data/lib/temple/generators/string_buffer.rb +2 -2
- data/lib/temple/html/attribute_merger.rb +6 -11
- data/lib/temple/html/attribute_remover.rb +1 -1
- data/lib/temple/html/attribute_sorter.rb +1 -1
- data/lib/temple/html/fast.rb +49 -44
- data/lib/temple/html/pretty.rb +34 -43
- data/lib/temple/html/safe.rb +23 -0
- data/lib/temple/map.rb +105 -0
- data/lib/temple/mixins/dispatcher.rb +10 -7
- data/lib/temple/mixins/engine_dsl.rb +42 -67
- data/lib/temple/mixins/grammar_dsl.rb +10 -8
- data/lib/temple/mixins/options.rb +26 -24
- data/lib/temple/mixins/template.rb +3 -3
- data/lib/temple/static_analyzer.rb +77 -0
- data/lib/temple/templates/rails.rb +17 -36
- data/lib/temple/templates/tilt.rb +7 -13
- data/lib/temple/utils.rb +27 -29
- data/lib/temple/version.rb +1 -1
- data/lib/temple.rb +8 -4
- data/spec/engine_spec.rb +189 -0
- data/{test/test_erb.rb → spec/erb_spec.rb} +12 -13
- data/spec/filter_spec.rb +29 -0
- data/{test/filters/test_code_merger.rb → spec/filters/code_merger_spec.rb} +7 -7
- data/{test/filters/test_control_flow.rb → spec/filters/control_flow_spec.rb} +13 -13
- data/{test/filters/test_dynamic_inliner.rb → spec/filters/dynamic_inliner_spec.rb} +18 -18
- data/{test/filters/test_eraser.rb → spec/filters/eraser_spec.rb} +13 -13
- data/{test/filters/test_escapable.rb → spec/filters/escapable_spec.rb} +15 -13
- data/{test/filters/test_multi_flattener.rb → spec/filters/multi_flattener_spec.rb} +4 -4
- data/spec/filters/static_analyzer_spec.rb +35 -0
- data/{test/filters/test_static_merger.rb → spec/filters/static_merger_spec.rb} +7 -7
- data/spec/filters/string_splitter_spec.rb +50 -0
- data/spec/generator_spec.rb +158 -0
- data/spec/grammar_spec.rb +47 -0
- data/{test/html/test_attribute_merger.rb → spec/html/attribute_merger_spec.rb} +11 -11
- data/{test/html/test_attribute_remover.rb → spec/html/attribute_remover_spec.rb} +7 -7
- data/{test/html/test_attribute_sorter.rb → spec/html/attribute_sorter_spec.rb} +8 -8
- data/{test/html/test_fast.rb → spec/html/fast_spec.rb} +23 -23
- data/{test/html/test_pretty.rb → spec/html/pretty_spec.rb} +9 -15
- data/spec/map_spec.rb +39 -0
- data/{test/mixins/test_dispatcher.rb → spec/mixins/dispatcher_spec.rb} +12 -12
- data/{test/mixins/test_grammar_dsl.rb → spec/mixins/grammar_dsl_spec.rb} +19 -19
- data/{test/helper.rb → spec/spec_helper.rb} +9 -15
- data/spec/static_analyzer_spec.rb +39 -0
- data/spec/utils_spec.rb +39 -0
- data/temple.gemspec +4 -2
- metadata +62 -63
- data/.travis.yml +0 -13
- data/lib/temple/hash.rb +0 -104
- data/test/test_engine.rb +0 -170
- data/test/test_filter.rb +0 -29
- data/test/test_generator.rb +0 -136
- data/test/test_grammar.rb +0 -47
- data/test/test_hash.rb +0 -39
- data/test/test_utils.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c7475d628203a95d9ec067ac1388f8428cb213f4534a2fd2ed1b9cf0be7d3293
|
4
|
+
data.tar.gz: d828006ab486aee65e70e8e321022d28191deba994d43c1141dccad2815f41f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d74cc956889989a4d65f6727d7f521cbcbf3d9c2de1a6abda92b4957a7e3992de2b7de6c7634b4dfafe9d4ce1a505f22b8f5af1ef9a217876d6d67f276e04557
|
7
|
+
data.tar.gz: 1958e441fbab605e06f560db0f4c26e5fecf7c673a12a5aad843a3bd5bb4c0ce77e3c2ea13f46b22d962770898587b7f76bd0ad2b11377898e61924e7bc20c87
|
@@ -0,0 +1,34 @@
|
|
1
|
+
name: test
|
2
|
+
on:
|
3
|
+
push:
|
4
|
+
branches:
|
5
|
+
- master
|
6
|
+
pull_request:
|
7
|
+
types:
|
8
|
+
- opened
|
9
|
+
- synchronize
|
10
|
+
- reopened
|
11
|
+
schedule:
|
12
|
+
- cron: "00 15 * * *"
|
13
|
+
jobs:
|
14
|
+
test:
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
strategy:
|
17
|
+
fail-fast: false
|
18
|
+
matrix:
|
19
|
+
ruby:
|
20
|
+
- '2.5'
|
21
|
+
- '2.6'
|
22
|
+
- '2.7'
|
23
|
+
- '3.0'
|
24
|
+
- '3.1'
|
25
|
+
- jruby
|
26
|
+
- truffleruby-head
|
27
|
+
steps:
|
28
|
+
- uses: actions/checkout@v2
|
29
|
+
- name: Set up Ruby
|
30
|
+
uses: ruby/setup-ruby@v1
|
31
|
+
with:
|
32
|
+
ruby-version: ${{ matrix.ruby }}
|
33
|
+
bundler-cache: true
|
34
|
+
- run: bundle exec rake spec
|
data/.gitignore
CHANGED
data/CHANGES
CHANGED
@@ -1,3 +1,108 @@
|
|
1
|
+
0.10.0
|
2
|
+
|
3
|
+
* Regression: Revert changes to :capture_generator since 0.8.2 (#112, #113, #137)
|
4
|
+
* Regression: Ensure that output buffer is not reused for capturing in Rails (#135)
|
5
|
+
* Drop support for Rails 4.x
|
6
|
+
|
7
|
+
0.9.1
|
8
|
+
|
9
|
+
* Fix Slim's error in AttributeMerger due to 0.9.0's :capture_generator (#137)
|
10
|
+
* Use specified :capture_generator for nested captures (#112)
|
11
|
+
* Fix Temple::ERB::Engine's <%= to not escape and <%== to escape expressions
|
12
|
+
|
13
|
+
0.9.0
|
14
|
+
|
15
|
+
* Require Ruby 2.5+ (#131)
|
16
|
+
* Change default :capture_generator to self (#113)
|
17
|
+
* Improve compatibility with Rails 7.1 (#135)
|
18
|
+
* Support Rails 6.1's annotate_rendered_view_with_filenames
|
19
|
+
with Temple::Filters::Ambles (#134)
|
20
|
+
* Fix a crash in StringSplitter filter (#138)
|
21
|
+
* Fix a warning by Object#=~ since Ruby 2.6 (#129)
|
22
|
+
* Fix deprecated Tilt template mime type (#108)
|
23
|
+
* Stop using deprecated EscapeUtils from Temple::Utils (#136)
|
24
|
+
|
25
|
+
0.8.2
|
26
|
+
|
27
|
+
* Support TruffleRuby in Temple::Filters::StaticAnalyzer (#127)
|
28
|
+
* Support TruffleRuby in Temple::Filters::StringSplitter (#127)
|
29
|
+
|
30
|
+
0.8.1
|
31
|
+
|
32
|
+
* Stop relying on deprecated method in Rails (#121)
|
33
|
+
* Fix issue with --enable-frozen-string-literal
|
34
|
+
* Escape html in markdown
|
35
|
+
|
36
|
+
0.8.0
|
37
|
+
|
38
|
+
* Add Temple::StaticAnalyzer to analyze Ruby expressions
|
39
|
+
* Support newlines in Temple::Filters::StaticAnalyzer
|
40
|
+
|
41
|
+
0.7.8
|
42
|
+
|
43
|
+
* Fix a warning in StaticAnalyzer
|
44
|
+
|
45
|
+
0.7.7
|
46
|
+
|
47
|
+
* Add Temple::Filters::StaticAnalyzer, Temple::Filters::StringSplitter
|
48
|
+
* Freeze string literals
|
49
|
+
|
50
|
+
0.7.6
|
51
|
+
|
52
|
+
* EngineDSL - add support for use(:Filter) { FilterClassName }
|
53
|
+
|
54
|
+
0.7.5
|
55
|
+
|
56
|
+
* HTML::Pretty Fix indentation issue (https://github.com/slim-template/slim-rails/issues/78)
|
57
|
+
|
58
|
+
0.7.4
|
59
|
+
|
60
|
+
* EngineDSL: allow to replace/remove with regexp
|
61
|
+
* Fix deprecation warning (#83)
|
62
|
+
|
63
|
+
0.7.3
|
64
|
+
|
65
|
+
* Temple::ERB::Trimming - replace option trim_mode with trim and switch to erubis-like trimming
|
66
|
+
|
67
|
+
0.7.2
|
68
|
+
|
69
|
+
* Remove Filters::StaticFreezer, the generator does the freezing
|
70
|
+
|
71
|
+
0.7.1
|
72
|
+
|
73
|
+
* Rename *Hash to *Map
|
74
|
+
* Add Filters::StaticFreezer
|
75
|
+
|
76
|
+
0.7.0
|
77
|
+
|
78
|
+
* Drop Ruby 1.8.7 support
|
79
|
+
* EngineDSL: Remove option filter
|
80
|
+
* HTML: Deprecate :html4, :html5 formats
|
81
|
+
* HTML: Add format :xml
|
82
|
+
* Rename DefaultOptions to ClassOptions
|
83
|
+
* Deprecate default_options in favor of options
|
84
|
+
* Add Utils.indent_dynamic
|
85
|
+
|
86
|
+
0.6.10
|
87
|
+
|
88
|
+
* Tilt template: Support :outvar and save/restore buffer to make the behaviour compatible with ERB
|
89
|
+
|
90
|
+
0.6.9
|
91
|
+
|
92
|
+
* HTML::Pretty: Fix wrong line numbers
|
93
|
+
* Tilt template: Don't overwrite buffer always
|
94
|
+
* Generator: add preamble and postamble which do nothing
|
95
|
+
* Tilt template: don't overwrite streaming option
|
96
|
+
* OptionHash: inherit valid keys
|
97
|
+
* temple/html/safe: add poor man's html_safe? implementation (not required automatically)
|
98
|
+
* Temple::Mixins::GrammarDSL - Add some missing match? methods
|
99
|
+
* Temple::Utils.escape_html_safe - Add parameter safe
|
100
|
+
|
101
|
+
0.6.8
|
102
|
+
|
103
|
+
* HTML::Fast add svg doctype
|
104
|
+
* Render standalone html 5 attributes
|
105
|
+
|
1
106
|
0.6.7
|
2
107
|
|
3
108
|
* HTML::Pretty - change some block level tags
|
@@ -172,7 +277,7 @@
|
|
172
277
|
|
173
278
|
0.1.1
|
174
279
|
|
175
|
-
* Test added
|
280
|
+
* Test added
|
176
281
|
|
177
282
|
0.1.0
|
178
283
|
|
data/EXPRESSIONS.md
CHANGED
@@ -188,7 +188,7 @@ The HTML abstraction is processed by the html filters (Temple::HTML::Fast and Te
|
|
188
188
|
Example:
|
189
189
|
[:html, :doctype, '5']
|
190
190
|
generates
|
191
|
-
<!DOCTYPE
|
191
|
+
<!DOCTYPE html>
|
192
192
|
|
193
193
|
Supported doctypes:
|
194
194
|
|
@@ -220,13 +220,14 @@ generates:
|
|
220
220
|
### [:html, :tag, identifier, attributes, optional-sexp]
|
221
221
|
|
222
222
|
HTML tag abstraction. Identifier can be a String or a Symbol. If the optional content Sexp is omitted
|
223
|
-
the tag is closed (e.g.
|
223
|
+
the tag is closed (e.g. `<br/>` `<img/>`). The tag is also closed if the content Sexp is empty
|
224
224
|
(consists only of :multi and :newline expressions) and the tag is registered as auto-closing.
|
225
225
|
|
226
226
|
Example:
|
227
227
|
[:html, :tag, 'img', [:html, :attrs, [:html, :attr, 'src', 'image.png']]]
|
228
228
|
[:html, :tag, 'p', [:multi], [:static, 'Content']]
|
229
229
|
generates:
|
230
|
+
|
230
231
|
<img src="image.png"/>
|
231
232
|
<p>Content</p>
|
232
233
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Temple
|
2
2
|
======
|
3
3
|
|
4
|
-
[](https://github.com/judofyr/temple/actions/workflows/test.yml) [](https://codeclimate.com/github/judofyr/temple) [](https://rubygems.org/gems/temple) [](http://rubydoc.info/gems/temple/frames)
|
5
5
|
|
6
6
|
Temple is an abstraction and a framework for compiling templates to pure Ruby.
|
7
7
|
It's all about making it easier to experiment, implement and optimize template
|
@@ -23,6 +23,7 @@ Links
|
|
23
23
|
* API documentation:
|
24
24
|
* Latest Gem: <http://rubydoc.info/gems/temple/frames>
|
25
25
|
* GitHub master: <http://rubydoc.info/github/judofyr/temple/master/frames>
|
26
|
+
* Abstractions: <http://github.com/judofyr/temple/blob/master/EXPRESSIONS.md>
|
26
27
|
|
27
28
|
Overview
|
28
29
|
--------
|
@@ -123,7 +124,7 @@ continue to use the HTML abstraction. Maybe you even want to write your
|
|
123
124
|
compiler in another language? Sexps are easily serialized and if you don't
|
124
125
|
mind working across processes, it's not a problem at all.
|
125
126
|
|
126
|
-
All abstractions used by Temple are documented in
|
127
|
+
All abstractions used by Temple are documented in [EXPRESSIONS.md](EXPRESSIONS.md).
|
127
128
|
|
128
129
|
Compilers
|
129
130
|
---------
|
@@ -195,8 +196,8 @@ When you have a chain of a parsers, some filters and a generator you can finally
|
|
195
196
|
|
196
197
|
```ruby
|
197
198
|
class MyEngine < Temple::Engine
|
198
|
-
# First run MyParser
|
199
|
-
use MyParser
|
199
|
+
# First run MyParser
|
200
|
+
use MyParser
|
200
201
|
|
201
202
|
# Then a custom filter
|
202
203
|
use MyFilter
|
@@ -207,10 +208,10 @@ When you have a chain of a parsers, some filters and a generator you can finally
|
|
207
208
|
filter :DynamicInliner
|
208
209
|
|
209
210
|
# Finally the generator
|
210
|
-
generator :ArrayBuffer
|
211
|
+
generator :ArrayBuffer
|
211
212
|
end
|
212
213
|
|
213
|
-
engine = MyEngine.new(:
|
214
|
+
engine = MyEngine.new(strict: "For MyParser")
|
214
215
|
engine.call(something)
|
215
216
|
```
|
216
217
|
|
@@ -227,7 +228,7 @@ Rails.
|
|
227
228
|
require 'tilt'
|
228
229
|
|
229
230
|
# Create template class MyTemplate and register your file extension
|
230
|
-
MyTemplate = Temple::Templates::Tilt(MyEngine, :
|
231
|
+
MyTemplate = Temple::Templates::Tilt(MyEngine, register_as: 'ext')
|
231
232
|
|
232
233
|
Tilt.new('example.ext').render # => Render a file
|
233
234
|
MyTemplate.new { "String" }.render # => Render a string
|
@@ -236,7 +237,7 @@ Rails.
|
|
236
237
|
Installation
|
237
238
|
------------
|
238
239
|
|
239
|
-
You need
|
240
|
+
You need at least Ruby 1.9.3 to work with Temple. Temple is published as a Ruby Gem which can be installed
|
240
241
|
as following:
|
241
242
|
|
242
243
|
```bash
|
@@ -246,10 +247,13 @@ as following:
|
|
246
247
|
Engines using Temple
|
247
248
|
--------------------
|
248
249
|
|
249
|
-
* [Slim](
|
250
|
-
* [
|
250
|
+
* [Slim](https://github.com/slim-template/slim)
|
251
|
+
* [Hamlit](https://github.com/k0kubun/hamlit)
|
252
|
+
* [Faml](https://github.com/eagletmt/faml)
|
253
|
+
* [Sal](https://github.com/stonean/sal.rb)
|
251
254
|
* [Temple-Mustache (Example implementation)](https://github.com/minad/temple-mustache)
|
252
255
|
* Temple ERB example implementation (Temple::ERB::Template)
|
256
|
+
* [WLang](https://github.com/blambeau/wlang)
|
253
257
|
|
254
258
|
Acknowledgements
|
255
259
|
----------------
|
data/Rakefile
CHANGED
@@ -1,15 +1,8 @@
|
|
1
|
-
require '
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
2
3
|
|
3
|
-
|
4
|
-
task :
|
5
|
-
sh "bacon -Ilib -Itest --automatic --quiet"
|
6
|
-
end
|
7
|
-
|
8
|
-
#Rake::TestTask.new(:test) do |t|
|
9
|
-
# t.libs << 'lib' << 'test'
|
10
|
-
# t.pattern = 'test/**/test_*.rb'
|
11
|
-
# t.verbose = false
|
12
|
-
#end
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
task default: :spec
|
13
6
|
|
14
7
|
begin
|
15
8
|
require 'rcov/rcovtask'
|
data/lib/temple/engine.rb
CHANGED
@@ -24,7 +24,7 @@ module Temple
|
|
24
24
|
# replace :ArrayBuffer, Temple::Generators::RailsOutputBuffer
|
25
25
|
# end
|
26
26
|
#
|
27
|
-
# engine = MyEngine.new(:
|
27
|
+
# engine = MyEngine.new(strict: "For MyParser")
|
28
28
|
# engine.call(something)
|
29
29
|
#
|
30
30
|
# @api public
|
@@ -33,7 +33,7 @@ module Temple
|
|
33
33
|
include Mixins::EngineDSL
|
34
34
|
extend Mixins::EngineDSL
|
35
35
|
|
36
|
-
define_options :file, :streaming, :buffer
|
36
|
+
define_options :file, :streaming, :buffer, :save_buffer
|
37
37
|
|
38
38
|
attr_reader :chain
|
39
39
|
|
@@ -57,7 +57,11 @@ module Temple
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def call_chain
|
60
|
-
@call_chain ||= @chain.map
|
60
|
+
@call_chain ||= @chain.map do |name, constructor|
|
61
|
+
f = constructor.call(self)
|
62
|
+
raise "Constructor #{name} must return callable object" if f && !f.respond_to?(:call)
|
63
|
+
f
|
64
|
+
end.compact
|
61
65
|
end
|
62
66
|
end
|
63
67
|
end
|
data/lib/temple/erb/engine.rb
CHANGED
@@ -5,10 +5,12 @@ module Temple
|
|
5
5
|
# @api public
|
6
6
|
class Engine < Temple::Engine
|
7
7
|
use Temple::ERB::Parser
|
8
|
-
use Temple::ERB::Trimming
|
9
|
-
filter :Escapable
|
8
|
+
use Temple::ERB::Trimming
|
9
|
+
filter :Escapable
|
10
|
+
filter :StringSplitter
|
11
|
+
filter :StaticAnalyzer
|
10
12
|
filter :MultiFlattener
|
11
|
-
filter :
|
13
|
+
filter :StaticMerger
|
12
14
|
generator :ArrayBuffer
|
13
15
|
end
|
14
16
|
end
|
data/lib/temple/erb/parser.rb
CHANGED
data/lib/temple/erb/trimming.rb
CHANGED
@@ -1,38 +1,23 @@
|
|
1
1
|
module Temple
|
2
2
|
module ERB
|
3
|
-
# ERB trimming
|
4
|
-
#
|
5
|
-
# <> - omit newline for lines starting with <% and ending in %>
|
6
|
-
# > - omit newline for lines ending in %>
|
7
|
-
#
|
3
|
+
# ERB trimming like in erubis
|
4
|
+
# Deletes spaces around '<% %>' and leave spaces around '<%= %>'.
|
8
5
|
# @api public
|
9
6
|
class Trimming < Filter
|
10
|
-
define_options :
|
7
|
+
define_options trim: true
|
11
8
|
|
12
9
|
def on_multi(*exps)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
exps = exps.each_with_index.map do |e,i|
|
11
|
+
if e.first == :static && i > 0 && exps[i-1].first == :code
|
12
|
+
[:static, e.last.lstrip]
|
13
|
+
elsif e.first == :static && i < exps.size-1 && exps[i+1].first == :code
|
14
|
+
[:static, e.last.rstrip]
|
15
|
+
else
|
16
|
+
e
|
19
17
|
end
|
20
|
-
|
21
|
-
i = 0
|
22
|
-
while i < exps.size
|
23
|
-
exps.delete_at(i + 1) if code?(exps[i]) && exps[i+1] == [:static, "\n"] &&
|
24
|
-
(!exps[i-1] || (exps[i-1] == [:newline]))
|
25
|
-
i += 1
|
26
|
-
end
|
27
|
-
end
|
18
|
+
end if options[:trim]
|
28
19
|
[:multi, *exps]
|
29
20
|
end
|
30
|
-
|
31
|
-
protected
|
32
|
-
|
33
|
-
def code?(exp)
|
34
|
-
exp[0] == :escape || exp[0] == :code
|
35
|
-
end
|
36
21
|
end
|
37
22
|
end
|
38
23
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Temple
|
2
|
+
module Filters
|
3
|
+
class Ambles < Filter
|
4
|
+
define_options :preamble, :postamble
|
5
|
+
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
@preamble = options[:preamble]
|
9
|
+
@postamble = options[:postamble]
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(ast)
|
13
|
+
ret = [:multi]
|
14
|
+
ret << [:static, @preamble] if @preamble
|
15
|
+
ret << ast
|
16
|
+
ret << [:static, @postamble] if @postamble
|
17
|
+
ret
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -9,8 +9,8 @@ module Temple
|
|
9
9
|
class Escapable < Filter
|
10
10
|
# Activate the usage of html_safe? if it is available (for Rails 3 for example)
|
11
11
|
define_options :escape_code,
|
12
|
-
:
|
13
|
-
:
|
12
|
+
:disable_escape,
|
13
|
+
use_html_safe: ''.respond_to?(:html_safe?)
|
14
14
|
|
15
15
|
def initialize(opts = {})
|
16
16
|
super
|
@@ -5,15 +5,8 @@ module Temple
|
|
5
5
|
# @api public
|
6
6
|
class RemoveBOM < Parser
|
7
7
|
def call(s)
|
8
|
-
if s.
|
9
|
-
|
10
|
-
s.gsub(Regexp.new("\\A\uFEFF".encode(s.encoding.name)), '')
|
11
|
-
else
|
12
|
-
s
|
13
|
-
end
|
14
|
-
else
|
15
|
-
s.gsub(/\A\xEF\xBB\xBF/, '')
|
16
|
-
end
|
8
|
+
return s if s.encoding.name !~ /^UTF-(8|16|32)(BE|LE)?/
|
9
|
+
s.gsub(Regexp.new("\\A\uFEFF".encode(s.encoding.name)), ''.freeze)
|
17
10
|
end
|
18
11
|
end
|
19
12
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Temple
|
2
|
+
module Filters
|
3
|
+
# Convert [:dynamic, code] to [:static, text] if code is static Ruby expression.
|
4
|
+
class StaticAnalyzer < Filter
|
5
|
+
def call(exp)
|
6
|
+
# Optimize only when Ripper is available.
|
7
|
+
if ::Temple::StaticAnalyzer.available?
|
8
|
+
super
|
9
|
+
else
|
10
|
+
exp
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_dynamic(code)
|
15
|
+
if ::Temple::StaticAnalyzer.static?(code)
|
16
|
+
exp = [:static, eval(code).to_s]
|
17
|
+
|
18
|
+
newlines = code.count("\n")
|
19
|
+
if newlines == 0
|
20
|
+
exp
|
21
|
+
else
|
22
|
+
[:multi, exp, *newlines.times.map { [:newline] }]
|
23
|
+
end
|
24
|
+
else
|
25
|
+
[:dynamic, code]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
begin
|
2
|
+
require 'ripper'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
6
|
+
module Temple
|
7
|
+
module Filters
|
8
|
+
# Compile [:dynamic, "foo#{bar}"] to [:multi, [:static, 'foo'], [:dynamic, 'bar']]
|
9
|
+
class StringSplitter < Filter
|
10
|
+
if defined?(Ripper) && Ripper.respond_to?(:lex)
|
11
|
+
class << self
|
12
|
+
# `code` param must be valid string literal
|
13
|
+
def compile(code)
|
14
|
+
[].tap do |exps|
|
15
|
+
tokens = Ripper.lex(code.strip)
|
16
|
+
tokens.pop while tokens.last && [:on_comment, :on_sp].include?(tokens.last[1])
|
17
|
+
|
18
|
+
if tokens.size < 2
|
19
|
+
raise(FilterError, "Expected token size >= 2 but got: #{tokens.size}")
|
20
|
+
end
|
21
|
+
compile_tokens!(exps, tokens)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def strip_quotes!(tokens)
|
28
|
+
_, type, beg_str = tokens.shift
|
29
|
+
if type != :on_tstring_beg
|
30
|
+
raise(FilterError, "Expected :on_tstring_beg but got: #{type}")
|
31
|
+
end
|
32
|
+
|
33
|
+
_, type, end_str = tokens.pop
|
34
|
+
if type != :on_tstring_end
|
35
|
+
raise(FilterError, "Expected :on_tstring_end but got: #{type}")
|
36
|
+
end
|
37
|
+
|
38
|
+
[beg_str, end_str]
|
39
|
+
end
|
40
|
+
|
41
|
+
def compile_tokens!(exps, tokens)
|
42
|
+
beg_str, end_str = strip_quotes!(tokens)
|
43
|
+
|
44
|
+
until tokens.empty?
|
45
|
+
_, type, str = tokens.shift
|
46
|
+
|
47
|
+
case type
|
48
|
+
when :on_tstring_content
|
49
|
+
beg_str, end_str = escape_quotes(beg_str, end_str)
|
50
|
+
exps << [:static, eval("#{beg_str}#{str}#{end_str}").to_s]
|
51
|
+
when :on_embexpr_beg
|
52
|
+
embedded = shift_balanced_embexpr(tokens)
|
53
|
+
exps << [:dynamic, embedded] unless embedded.empty?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Some quotes are split-unsafe. Replace such quotes with null characters.
|
59
|
+
def escape_quotes(beg_str, end_str)
|
60
|
+
case [beg_str[-1], end_str]
|
61
|
+
when ['(', ')'], ['[', ']'], ['{', '}']
|
62
|
+
[beg_str.sub(/.\z/) { "\0" }, "\0"]
|
63
|
+
else
|
64
|
+
[beg_str, end_str]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def shift_balanced_embexpr(tokens)
|
69
|
+
String.new.tap do |embedded|
|
70
|
+
embexpr_open = 1
|
71
|
+
|
72
|
+
until tokens.empty?
|
73
|
+
_, type, str = tokens.shift
|
74
|
+
case type
|
75
|
+
when :on_embexpr_beg
|
76
|
+
embexpr_open += 1
|
77
|
+
when :on_embexpr_end
|
78
|
+
embexpr_open -= 1
|
79
|
+
break if embexpr_open == 0
|
80
|
+
end
|
81
|
+
|
82
|
+
embedded << str
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def on_dynamic(code)
|
89
|
+
return [:dynamic, code] unless string_literal?(code)
|
90
|
+
return [:dynamic, code] if code.include?("\n")
|
91
|
+
|
92
|
+
temple = [:multi]
|
93
|
+
StringSplitter.compile(code).each do |type, content|
|
94
|
+
case type
|
95
|
+
when :static
|
96
|
+
temple << [:static, content]
|
97
|
+
when :dynamic
|
98
|
+
temple << on_dynamic(content)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
temple
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def string_literal?(code)
|
107
|
+
return false if SyntaxChecker.syntax_error?(code)
|
108
|
+
|
109
|
+
type, instructions = Ripper.sexp(code)
|
110
|
+
return false if type != :program
|
111
|
+
return false if instructions.size > 1
|
112
|
+
|
113
|
+
type, _ = instructions.first
|
114
|
+
type == :string_literal
|
115
|
+
end
|
116
|
+
|
117
|
+
class SyntaxChecker < Ripper
|
118
|
+
class ParseError < StandardError; end
|
119
|
+
|
120
|
+
def self.syntax_error?(code)
|
121
|
+
self.new(code).parse
|
122
|
+
false
|
123
|
+
rescue ParseError
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def on_parse_error(*)
|
130
|
+
raise ParseError
|
131
|
+
end
|
132
|
+
end
|
133
|
+
else
|
134
|
+
# Do nothing if ripper is unavailable
|
135
|
+
def call(ast)
|
136
|
+
ast
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|