temple 0.6.7 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +34 -0
  3. data/.gitignore +1 -0
  4. data/CHANGES +106 -1
  5. data/EXPRESSIONS.md +3 -2
  6. data/Gemfile +0 -1
  7. data/README.md +14 -10
  8. data/Rakefile +4 -11
  9. data/lib/temple/engine.rb +7 -3
  10. data/lib/temple/erb/engine.rb +5 -3
  11. data/lib/temple/erb/parser.rb +1 -1
  12. data/lib/temple/erb/trimming.rb +11 -26
  13. data/lib/temple/filters/ambles.rb +21 -0
  14. data/lib/temple/filters/encoding.rb +1 -1
  15. data/lib/temple/filters/eraser.rb +1 -1
  16. data/lib/temple/filters/escapable.rb +2 -2
  17. data/lib/temple/filters/remove_bom.rb +2 -9
  18. data/lib/temple/filters/static_analyzer.rb +30 -0
  19. data/lib/temple/filters/string_splitter.rb +141 -0
  20. data/lib/temple/filters/validator.rb +1 -1
  21. data/lib/temple/generator.rb +32 -6
  22. data/lib/temple/generators/array.rb +2 -2
  23. data/lib/temple/generators/array_buffer.rb +6 -5
  24. data/lib/temple/generators/erb.rb +1 -5
  25. data/lib/temple/generators/rails_output_buffer.rb +7 -8
  26. data/lib/temple/generators/string_buffer.rb +2 -2
  27. data/lib/temple/html/attribute_merger.rb +6 -11
  28. data/lib/temple/html/attribute_remover.rb +1 -1
  29. data/lib/temple/html/attribute_sorter.rb +1 -1
  30. data/lib/temple/html/fast.rb +49 -44
  31. data/lib/temple/html/pretty.rb +34 -43
  32. data/lib/temple/html/safe.rb +23 -0
  33. data/lib/temple/map.rb +105 -0
  34. data/lib/temple/mixins/dispatcher.rb +10 -7
  35. data/lib/temple/mixins/engine_dsl.rb +42 -67
  36. data/lib/temple/mixins/grammar_dsl.rb +10 -8
  37. data/lib/temple/mixins/options.rb +26 -24
  38. data/lib/temple/mixins/template.rb +3 -3
  39. data/lib/temple/static_analyzer.rb +77 -0
  40. data/lib/temple/templates/rails.rb +17 -36
  41. data/lib/temple/templates/tilt.rb +7 -13
  42. data/lib/temple/utils.rb +27 -29
  43. data/lib/temple/version.rb +1 -1
  44. data/lib/temple.rb +8 -4
  45. data/spec/engine_spec.rb +189 -0
  46. data/{test/test_erb.rb → spec/erb_spec.rb} +12 -13
  47. data/spec/filter_spec.rb +29 -0
  48. data/{test/filters/test_code_merger.rb → spec/filters/code_merger_spec.rb} +7 -7
  49. data/{test/filters/test_control_flow.rb → spec/filters/control_flow_spec.rb} +13 -13
  50. data/{test/filters/test_dynamic_inliner.rb → spec/filters/dynamic_inliner_spec.rb} +18 -18
  51. data/{test/filters/test_eraser.rb → spec/filters/eraser_spec.rb} +13 -13
  52. data/{test/filters/test_escapable.rb → spec/filters/escapable_spec.rb} +15 -13
  53. data/{test/filters/test_multi_flattener.rb → spec/filters/multi_flattener_spec.rb} +4 -4
  54. data/spec/filters/static_analyzer_spec.rb +35 -0
  55. data/{test/filters/test_static_merger.rb → spec/filters/static_merger_spec.rb} +7 -7
  56. data/spec/filters/string_splitter_spec.rb +50 -0
  57. data/spec/generator_spec.rb +158 -0
  58. data/spec/grammar_spec.rb +47 -0
  59. data/{test/html/test_attribute_merger.rb → spec/html/attribute_merger_spec.rb} +11 -11
  60. data/{test/html/test_attribute_remover.rb → spec/html/attribute_remover_spec.rb} +7 -7
  61. data/{test/html/test_attribute_sorter.rb → spec/html/attribute_sorter_spec.rb} +8 -8
  62. data/{test/html/test_fast.rb → spec/html/fast_spec.rb} +23 -23
  63. data/{test/html/test_pretty.rb → spec/html/pretty_spec.rb} +9 -15
  64. data/spec/map_spec.rb +39 -0
  65. data/{test/mixins/test_dispatcher.rb → spec/mixins/dispatcher_spec.rb} +12 -12
  66. data/{test/mixins/test_grammar_dsl.rb → spec/mixins/grammar_dsl_spec.rb} +19 -19
  67. data/{test/helper.rb → spec/spec_helper.rb} +9 -15
  68. data/spec/static_analyzer_spec.rb +39 -0
  69. data/spec/utils_spec.rb +39 -0
  70. data/temple.gemspec +4 -2
  71. metadata +62 -63
  72. data/.travis.yml +0 -13
  73. data/lib/temple/hash.rb +0 -104
  74. data/test/test_engine.rb +0 -170
  75. data/test/test_filter.rb +0 -29
  76. data/test/test_generator.rb +0 -136
  77. data/test/test_grammar.rb +0 -47
  78. data/test/test_hash.rb +0 -39
  79. data/test/test_utils.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 39b989dfbda6a7b4cd349b3580cb3352a1fbe16c
4
- data.tar.gz: f83ebdd80cbfe3a87cb40ee1e82b04e7985ccc95
2
+ SHA256:
3
+ metadata.gz: c7475d628203a95d9ec067ac1388f8428cb213f4534a2fd2ed1b9cf0be7d3293
4
+ data.tar.gz: d828006ab486aee65e70e8e321022d28191deba994d43c1141dccad2815f41f2
5
5
  SHA512:
6
- metadata.gz: 4cd1b41809a7d563c7c3d91c8311dde78c093ca04e279210666b4160c4b33b9efb0d21b8ca7532087536b3d4078ef912957255474189d0129a278c3d149ad42e
7
- data.tar.gz: 1daf0219baec00f3e4a768b5a4560992ceb65c2aa8a255b9cf7e266d9e1a23432ac41ffa8cc6b953950dc659d6205f312e0d033a6fcdd6c5689153b1fc84d5c1
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
@@ -2,3 +2,4 @@ coverage
2
2
  .yardoc
3
3
  doc
4
4
  Gemfile.lock
5
+ /pkg/
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 html5>
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. <br/> <img/>). The tag is also closed if the content Sexp is empty
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
@@ -1,3 +1,2 @@
1
1
  source 'https://rubygems.org/'
2
2
  gemspec
3
-
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Temple
2
2
  ======
3
3
 
4
- [![Build Status](https://secure.travis-ci.org/judofyr/temple.png?branch=master)](http://travis-ci.org/judofyr/temple) [![Dependency Status](https://gemnasium.com/judofyr/temple.png?travis)](https://gemnasium.com/judofyr/temple) [![Code Climate](https://codeclimate.com/github/judofyr/temple.png)](https://codeclimate.com/github/judofyr/temple)
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 {file:EXPRESSIONS.md EXPRESSIONS}.
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, passing the :strict option
199
- use MyParser, :strict
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, :buffer
211
+ generator :ArrayBuffer
211
212
  end
212
213
 
213
- engine = MyEngine.new(:strict => "For MyParser")
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, :register_as => 'ext')
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 Ruby 1.8.7 or Ruby 1.9.2 to work with Temple. Temple is published as a Ruby Gem which can be installed
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](http://github.com/slim-template/slim)
250
- * [Sal](http://github.com/stonean/sal.rb)
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 'rake/testtask'
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
2
3
 
3
- task :default => :test
4
- task :test do
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(:strict => "For MyParser")
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 {|name, constructor| constructor.call(self) }.compact
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
@@ -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, :trim_mode
9
- filter :Escapable, :use_html_safe, :disable_escape
8
+ use Temple::ERB::Trimming
9
+ filter :Escapable
10
+ filter :StringSplitter
11
+ filter :StaticAnalyzer
10
12
  filter :MultiFlattener
11
- filter :DynamicInliner
13
+ filter :StaticMerger
12
14
  generator :ArrayBuffer
13
15
  end
14
16
  end
@@ -27,7 +27,7 @@ module Temple
27
27
  when '#'
28
28
  result << [:code, "\n" * code.count("\n")]
29
29
  when /=/
30
- result << [:escape, indicator.size <= 1, [:dynamic, code]]
30
+ result << [:escape, indicator.size == 2, [:dynamic, code]]
31
31
  else
32
32
  result << [:code, code]
33
33
  end
@@ -1,38 +1,23 @@
1
1
  module Temple
2
2
  module ERB
3
- # ERB trimming
4
- # Set option :trim_mode to
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 :trim_mode
7
+ define_options trim: true
11
8
 
12
9
  def on_multi(*exps)
13
- case options[:trim_mode]
14
- when '>'
15
- i = 0
16
- while i < exps.size
17
- exps.delete_at(i + 1) if code?(exps[i]) && exps[i+1] == [:static, "\n"]
18
- i += 1
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
- when '<>'
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
@@ -4,7 +4,7 @@ module Temple
4
4
  #
5
5
  # @api public
6
6
  class Encoding < Parser
7
- define_options :encoding => 'utf-8'
7
+ define_options encoding: 'utf-8'
8
8
 
9
9
  def call(s)
10
10
  if options[:encoding] && s.respond_to?(:encoding)
@@ -5,7 +5,7 @@ module Temple
5
5
  # @api public
6
6
  class Eraser < Filter
7
7
  # [] is the empty type => keep all
8
- define_options :keep => [[]], :erase => nil
8
+ define_options :erase, keep: [[]]
9
9
 
10
10
  def compile(exp)
11
11
  exp.first == :multi || (do?(:keep, exp) && !do?(:erase, exp)) ?
@@ -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
- :use_html_safe => ''.respond_to?(:html_safe?),
13
- :disable_escape => false
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.respond_to?(:encoding)
9
- if s.encoding.name =~ /^UTF-(8|16|32)(BE|LE)?/
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
@@ -4,7 +4,7 @@ module Temple
4
4
  #
5
5
  # @api public
6
6
  class Validator < Filter
7
- define_options :grammar => Temple::Grammar
7
+ define_options grammar: Temple::Grammar
8
8
 
9
9
  def compile(exp)
10
10
  options[:grammar].validate!(exp)