flavour_saver 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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