flavour_saver 0.3.3

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +81 -0
  6. data/Guardfile +12 -0
  7. data/LICENSE +22 -0
  8. data/README.md +339 -0
  9. data/Rakefile +2 -0
  10. data/flavour_saver.gemspec +28 -0
  11. data/lib/flavour_saver/helpers.rb +118 -0
  12. data/lib/flavour_saver/lexer.rb +127 -0
  13. data/lib/flavour_saver/nodes.rb +177 -0
  14. data/lib/flavour_saver/parser.rb +183 -0
  15. data/lib/flavour_saver/partial.rb +29 -0
  16. data/lib/flavour_saver/rails_partial.rb +10 -0
  17. data/lib/flavour_saver/runtime.rb +269 -0
  18. data/lib/flavour_saver/template.rb +19 -0
  19. data/lib/flavour_saver/version.rb +3 -0
  20. data/lib/flavour_saver.rb +78 -0
  21. data/spec/acceptance/backtrack_spec.rb +14 -0
  22. data/spec/acceptance/comment_spec.rb +12 -0
  23. data/spec/acceptance/custom_block_helper_spec.rb +35 -0
  24. data/spec/acceptance/custom_helper_spec.rb +15 -0
  25. data/spec/acceptance/ensure_no_rce_spec.rb +26 -0
  26. data/spec/acceptance/handlebars_qunit_spec.rb +911 -0
  27. data/spec/acceptance/if_else_spec.rb +17 -0
  28. data/spec/acceptance/multi_level_with_spec.rb +15 -0
  29. data/spec/acceptance/one_character_identifier_spec.rb +13 -0
  30. data/spec/acceptance/runtime_run_spec.rb +27 -0
  31. data/spec/acceptance/sections_spec.rb +25 -0
  32. data/spec/acceptance/segment_literals_spec.rb +26 -0
  33. data/spec/acceptance/simple_expression_spec.rb +13 -0
  34. data/spec/fixtures/backtrack.hbs +4 -0
  35. data/spec/fixtures/comment.hbs +1 -0
  36. data/spec/fixtures/custom_block_helper.hbs +3 -0
  37. data/spec/fixtures/custom_helper.hbs +1 -0
  38. data/spec/fixtures/if_else.hbs +5 -0
  39. data/spec/fixtures/multi_level_if.hbs +12 -0
  40. data/spec/fixtures/multi_level_with.hbs +11 -0
  41. data/spec/fixtures/one_character_identifier.hbs +1 -0
  42. data/spec/fixtures/sections.hbs +9 -0
  43. data/spec/fixtures/simple_expression.hbs +1 -0
  44. data/spec/lib/flavour_saver/lexer_spec.rb +187 -0
  45. data/spec/lib/flavour_saver/parser_spec.rb +277 -0
  46. data/spec/lib/flavour_saver/runtime_spec.rb +190 -0
  47. data/spec/lib/flavour_saver/template_spec.rb +5 -0
  48. metadata +243 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9d12597ba5058729046ad752a861932197e839cf
4
+ data.tar.gz: e1975090088071c306eff750d6f45b9155bf0cce
5
+ SHA512:
6
+ metadata.gz: d09d2f9c685d5fb79c1e86df08d01865f4b108551c67e45e754dae94483f8f739e133cf744ba0d0d9548e9365e83f579b47bad21043927b80694bba36cc6e493
7
+ data.tar.gz: 97dc6cd9a74bca558a20f0cca63ff552e7fc0a69a9a94ea38dedbc21c74e4b25c5433f288823e74db8ac69b5e36a6b9607ee9f4493347f66e909cce99eb6d37e
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-19mode
6
+ - rbx-19mode
7
+ script: 'bundle exec rspec'
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: jruby-19mode
11
+ - rvm: rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in flavour_saver.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,81 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ flavour_saver (0.3.3)
5
+ rltk (~> 2.2.0)
6
+ tilt
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (4.0.0)
12
+ i18n (~> 0.6, >= 0.6.4)
13
+ minitest (~> 4.2)
14
+ multi_json (~> 1.3)
15
+ thread_safe (~> 0.1)
16
+ tzinfo (~> 0.3.37)
17
+ atomic (1.1.14)
18
+ coderay (1.0.9)
19
+ diff-lcs (1.2.4)
20
+ ffi (1.9.0)
21
+ formatador (0.2.4)
22
+ guard (1.8.3)
23
+ formatador (>= 0.2.4)
24
+ listen (~> 1.3)
25
+ lumberjack (>= 1.0.2)
26
+ pry (>= 0.9.10)
27
+ thor (>= 0.14.6)
28
+ guard-bundler (1.0.0)
29
+ bundler (~> 1.0)
30
+ guard (~> 1.1)
31
+ guard-rspec (3.1.0)
32
+ guard (>= 1.8)
33
+ rspec (~> 2.13)
34
+ i18n (0.6.5)
35
+ listen (1.3.1)
36
+ rb-fsevent (>= 0.9.3)
37
+ rb-inotify (>= 0.9)
38
+ rb-kqueue (>= 0.2)
39
+ lumberjack (1.0.4)
40
+ method_source (0.8.2)
41
+ minitest (4.7.5)
42
+ multi_json (1.8.0)
43
+ pry (0.9.12.2)
44
+ coderay (~> 1.0.5)
45
+ method_source (~> 0.8)
46
+ slop (~> 3.4)
47
+ rake (10.1.0)
48
+ rb-fsevent (0.9.3)
49
+ rb-inotify (0.9.2)
50
+ ffi (>= 0.5.0)
51
+ rb-kqueue (0.2.0)
52
+ ffi (>= 0.5.0)
53
+ rltk (2.2.1)
54
+ ffi (>= 1.0.0)
55
+ rspec (2.14.1)
56
+ rspec-core (~> 2.14.0)
57
+ rspec-expectations (~> 2.14.0)
58
+ rspec-mocks (~> 2.14.0)
59
+ rspec-core (2.14.5)
60
+ rspec-expectations (2.14.3)
61
+ diff-lcs (>= 1.1.3, < 2.0)
62
+ rspec-mocks (2.14.3)
63
+ slop (3.4.6)
64
+ thor (0.18.1)
65
+ thread_safe (0.1.3)
66
+ atomic
67
+ tilt (1.4.1)
68
+ tzinfo (0.3.37)
69
+
70
+ PLATFORMS
71
+ ruby
72
+
73
+ DEPENDENCIES
74
+ activesupport
75
+ flavour_saver!
76
+ guard-bundler
77
+ guard-rspec
78
+ rake
79
+ rspec-core
80
+ rspec-expectations
81
+ rspec-mocks
data/Guardfile ADDED
@@ -0,0 +1,12 @@
1
+ guard 'rspec', version: 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ watch(%r{^spec/fixtures/(.+)\.(hbs|handlebars)}) { |m| "spec/acceptance/#{m[1]}_spec.rb" }
6
+ end
7
+
8
+
9
+ guard 'bundler' do
10
+ watch('Gemfile')
11
+ watch(/^.+\.gemspec/)
12
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Resistor Limited.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,339 @@
1
+ # FlavourSaver
2
+
3
+ [Handlebars.js](http://handlebarsjs.com) without the `.js`
4
+
5
+ [![Build Status](https://travis-ci.org/jamesotron/FlavourSaver.png)](https://travis-ci.org/jamesotron/FlavourSaver)
6
+ [![Dependency Status](https://gemnasium.com/jamesotron/FlavourSaver.png)](https://gemnasium.com/jamesotron/FlavourSaver)
7
+ [![Code Climate](https://codeclimate.com/github/jamesotron/FlavourSaver.png)](https://codeclimate.com/github/jamesotron/FlavourSaver)
8
+
9
+ ## WAT?
10
+
11
+ FlavourSaver is a ruby-based implementation of the [Handlebars.js](http://handlebars.js)
12
+ templating language. FlavourSaver supports Handlebars template rendering natively on
13
+ Rails and on other frameworks (such as Sinatra) via Tilt.
14
+
15
+ Please use it, break it, and send issues/PR's for improvement.
16
+
17
+ ## License
18
+
19
+ FlavourSaver is Copyright (c) 2013 Resistor Limited and licensed under the terms
20
+ of the MIT Public License (see the LICENSE file included with this distribution
21
+ for more details).
22
+
23
+ ## Installation
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ gem 'flavour_saver'
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install flavour_saver
36
+
37
+ ## Usage
38
+
39
+ FlavourSaver provides an interface to the amazing
40
+ [Tilt](https://github.com/rtomayko/tilt) templating library, meaning that it
41
+ should work with anything that has Tilt support (Sinatra, etc) and has a
42
+ native Rails template handler.
43
+
44
+ ## Status
45
+
46
+ FlavourSaver is in its infancy, your pull requests are greatly appreciated.
47
+
48
+ Currently supported:
49
+
50
+ - Full support of Mustache and Handlebars templates.
51
+ - Expressions:
52
+ - with object-paths (`{{some.method.chain}}`)
53
+ - containing object-literals (`{{object.['index'].method}}`):
54
+ Ruby's `:[](index)` method is called for literals, making FlavourSaver
55
+ compatible with `Hash` and hashlike objects.
56
+ - with list arguments (`{{method arg1 "arg2"}}`)
57
+ - with hash arguments (`{{method foo=bar bar="baz"}}`)
58
+ - with list and hash arguments (`{{method arg1 arg2 foo=bar bar="baz"}}`)
59
+ provided that the hash is the last argument.
60
+ - Comments (`{{! a comment}}`)
61
+ - Expression output is HTML escaped
62
+ - Safe expressions
63
+ - Expressions wrapped in triple-stashes are not HTML escaped (`{{{an expression}}}`)
64
+ - Block expressions
65
+ - Simple API for adding block helpers.
66
+ - Block expressions with inverse blocks
67
+ - Inverse blocks
68
+ - Partials
69
+
70
+ ## Helpers
71
+
72
+ FlavourSaver implements the following helpers by default:
73
+
74
+ ### #with
75
+
76
+ Yields its argument into the context of the block contents:
77
+
78
+ ```handlebars
79
+ {{#with person}}
80
+ {{name}}
81
+ {{/with}}
82
+ ```
83
+
84
+ ### #each
85
+
86
+ Takes a single collection argument and yields the block's contents once
87
+ for each member of the collection:
88
+
89
+ ```handlebars
90
+ {{#each people}}
91
+ {{name}}
92
+ {{/each}}
93
+ ```
94
+
95
+ ### #if
96
+
97
+ Takes a single argument and yields the contents of the block if that argument
98
+ is truthy.
99
+
100
+ ```handlebars
101
+ {{#if person}}
102
+ Hi {{person.name}}!
103
+ {{/if}}
104
+ ```
105
+
106
+ It can also handle a special case `{{else}}` expression:
107
+
108
+ ```handlebars
109
+ {{#if person}}
110
+ Hi {{person.name}}!
111
+ {{else}}
112
+ Nobody to say hi to.
113
+ {{/if}}
114
+ ```
115
+
116
+ ### #unless
117
+
118
+ Exactly the same is `#if` but backwards.
119
+
120
+ ### this
121
+
122
+ In JavaScript `this` is a native keyword, in Ruby not-so-much. FlavourSaver's `this` helper
123
+ returns `self`:
124
+
125
+ ```handlebars
126
+ {{#each names}}
127
+ {{this}}
128
+ {{/each}}
129
+ ```
130
+
131
+ ### log
132
+
133
+ Writes log output. The destination can be changed by assigning a `Logger` instance to
134
+ `FlavourSaver.logger=`. On Rails `FlavourSaver.logger` automatically points at
135
+ `Rails.logger`.
136
+
137
+ ### Adding additional helpers
138
+
139
+ Additional helpers can easy be added by calling `FS.register_helper`, eg:
140
+
141
+ ```ruby
142
+ FS.register_helper(:whom) { 'world' }
143
+ ```
144
+
145
+ Now if you were to render the following template:
146
+
147
+ ```handlebars
148
+ <h1>Hello {{whom}}!</h1>
149
+ ```
150
+
151
+ You would receive the following output:
152
+
153
+ ```html
154
+ <h1>Hello world!</h1>
155
+ ```
156
+
157
+ ### Adding block helpers
158
+
159
+ Creating a block helper works exactly like adding a regular helper, except that
160
+ the helper implementation can call `yield.contents` one or more times, with an
161
+ optional argument setting the context of the block execution:
162
+
163
+ ```ruby
164
+ FS.register_helper(:three_times) do
165
+ yield.contents
166
+ yield.contents
167
+ yield.contents
168
+ end
169
+ ```
170
+
171
+ Which when called with the following template:
172
+
173
+ ```handlebars
174
+ {{#three_times}}
175
+ hello
176
+ {{/three_times}}
177
+ ```
178
+
179
+ would result in the following output:
180
+ ```
181
+ hello
182
+ hello
183
+ hello
184
+ ```
185
+
186
+ Implementing a simple iterator is dead easy:
187
+
188
+ ```ruby
189
+ FS.register_helper(:list_people) do |people|
190
+ people.each do |person|
191
+ yield.contents person
192
+ end
193
+ end
194
+ ```
195
+
196
+ Which could be used like so:
197
+
198
+ ```handlebars
199
+ {{#list_people people}}
200
+ <b>{{name}}<b><br />
201
+ Age: {{age}}<br />
202
+ Sex: {{sex}}<br />
203
+ {{/list_people}}
204
+ ```
205
+
206
+ Block helpers can also contain an `{{else}}` statement, which, when used creates
207
+ a second set of block contents (called `inverse`) which can be yielded to the output:
208
+
209
+ ```ruby
210
+ FS.register_helper(:isFemale) do |person,&block|
211
+ if person.sex == 'female'
212
+ block.call.contents
213
+ else
214
+ block.call.inverse
215
+ end
216
+ end
217
+ ```
218
+
219
+ You can also register an existing method:
220
+
221
+ ```ruby
222
+ def isFemale(person)
223
+ if person.sex == 'female'
224
+ yield.contents
225
+ else
226
+ yield.inverse
227
+ end
228
+ end
229
+
230
+ FS.register_helper(method(:isFemale))
231
+ ```
232
+
233
+ Which could be used like so:
234
+
235
+ ```handlebars
236
+ {{#isFemale person}}
237
+ {{person.name}} is female.
238
+ {{else}}
239
+ {{person.name}} is male.
240
+ {{/isFemale}}
241
+ ```
242
+
243
+ ### Using Partials
244
+
245
+ Handlebars allows you to register a partial either as a function or a string template with
246
+ the engine before compiling, FlavourSaver retains this behaviour (with the notable exception
247
+ of within Rails - see below).
248
+
249
+ To register a partial you call `FlavourSaver.register_partial` with a name and a string:
250
+
251
+ ```ruby
252
+ FlavourSaver.register_partial(:my_partial, "{{this}} is a partial")
253
+ ```
254
+
255
+ You can then use this partial within your templates:
256
+
257
+ ```handlebars
258
+ {{#each people}}{{> my_partial this}}{{/each}}
259
+ ```
260
+
261
+ ## Using with Rails
262
+
263
+ One potential gotcha of using FlavourSaver with Rails is that FlavourSaver doesn't let you
264
+ have any access to the controller's instance variables. This is done to maintain compatibility
265
+ with the original JavaScript implementation of Handlebars so that templates can be used on
266
+ both the server and client side without any change.
267
+
268
+ When accessing controller instance variables you should access them by way of a helper method
269
+ or a presenter object.
270
+
271
+ For example, in `ApplicationController.rb` you may have a `before_filter` which authenticates
272
+ the current user's session cookie and stores it in the controller's `@current_user` instance
273
+ variable.
274
+
275
+ To access this variable you could create a simple helper method in `ApplicationHelpers`:
276
+
277
+ ```ruby
278
+ def current_user
279
+ @current_user
280
+ end
281
+ ```
282
+
283
+ Which would mean that you are able to access it in your template:
284
+
285
+ ```handlebars
286
+ {{#if current_user}}
287
+ Welcome back, {{current_user.first_name}}!
288
+ {{/if}}
289
+ ```
290
+
291
+ ## Using the Tilt Interface Directly
292
+
293
+ You can use the registered Tilt interface directly to render template strings with a hash of template variables.
294
+
295
+ The Tilt template's `render` method expects an object that can respond to messages using dot notation. In the following example, the template variable `{{foo}}` will result in a call to `.foo` on the `data` object. For this reason the `data` object can't be a simple hash. A model would work, but if you have a plain old Ruby hash, use it to create a new OpenStruct object, which will provide the dot notation needed.
296
+
297
+ ```ruby
298
+ template = Tilt['handlebars'].new { "{{foo}} {{bar}}" }
299
+ data = OpenStruct.new foo: "hello", bar: "world"
300
+
301
+ template.render data # => "hello world"
302
+ ```
303
+
304
+ ### Special behaviour of Handlebars' partial syntax
305
+
306
+ In Handlebars.js all partial templates must be pre-registered with the engine before they are
307
+ able to be used. When running inside Rails FlavourSaver modifies this behaviour to use Rails'
308
+ render partial helper:
309
+
310
+ ```handlebars
311
+ {{> my_partial}}
312
+ ```
313
+
314
+ Will be translated into:
315
+
316
+ ```ruby
317
+ render :partial => 'my_partial'
318
+ ```
319
+
320
+ Handlebars allows you to send a context object into the partial, which sets the execution
321
+ context of the partial. In Rails this behaviour would be confusing and non-standard, so
322
+ instead any argument passed to the partial is evaluated and passed to the partial's
323
+ `:object` argument:
324
+
325
+ ```handlebars
326
+ {{> my_partial my_context}}
327
+ ```
328
+
329
+ ```ruby
330
+ render :partial => 'my_partial', :object => my_context
331
+ ```
332
+
333
+ ## Contributing
334
+
335
+ 1. Fork it
336
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
337
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
338
+ 4. Push to the branch (`git push origin my-new-feature`)
339
+ 5. Create new Pull Request