temple 0.6.7 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![
|
4
|
+
[![test](https://github.com/judofyr/temple/actions/workflows/test.yml/badge.svg)](https://github.com/judofyr/temple/actions/workflows/test.yml) [![Code Climate](https://codeclimate.com/github/judofyr/temple.svg)](https://codeclimate.com/github/judofyr/temple) [![Gem Version](https://badge.fury.io/rb/temple.svg)](https://rubygems.org/gems/temple) [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](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
|