flavour_saver 4.0.2 → 5.0.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +14 -0
  3. data/.standard.yml +1 -0
  4. data/CHANGELOG.md +57 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +48 -5
  7. data/README.md +153 -271
  8. data/flavour_saver.gemspec +17 -19
  9. data/lib/flavour_saver/error.rb +5 -0
  10. data/lib/flavour_saver/helpers.rb +19 -18
  11. data/lib/flavour_saver/lexer.rb +123 -50
  12. data/lib/flavour_saver/nodes.rb +107 -19
  13. data/lib/flavour_saver/parser.rb +339 -161
  14. data/lib/flavour_saver/partial.rb +4 -6
  15. data/lib/flavour_saver/rails_partial.rb +10 -4
  16. data/lib/flavour_saver/runtime.rb +76 -81
  17. data/lib/flavour_saver/template.rb +3 -5
  18. data/lib/flavour_saver/version.rb +1 -1
  19. data/lib/flavour_saver.rb +17 -17
  20. data/spec/acceptance/backtrack_spec.rb +9 -9
  21. data/spec/acceptance/comment_spec.rb +7 -7
  22. data/spec/acceptance/custom_block_helper_spec.rb +12 -12
  23. data/spec/acceptance/custom_helper_spec.rb +8 -8
  24. data/spec/acceptance/ensure_no_rce_spec.rb +64 -53
  25. data/spec/acceptance/handlebars_qunit_spec.rb +295 -297
  26. data/spec/acceptance/if_else_spec.rb +6 -6
  27. data/spec/acceptance/multi_level_with_spec.rb +10 -11
  28. data/spec/acceptance/nesting_depth_spec.rb +34 -0
  29. data/spec/acceptance/one_character_identifier_spec.rb +8 -8
  30. data/spec/acceptance/raw_block_spec.rb +7 -7
  31. data/spec/acceptance/sections_spec.rb +17 -14
  32. data/spec/acceptance/segment_literals_spec.rb +5 -5
  33. data/spec/acceptance/simple_expression_spec.rb +9 -9
  34. data/spec/acceptance/subexpression_spec.rb +18 -17
  35. data/spec/acceptance/unless_spec.rb +19 -6
  36. data/spec/lib/flavour_saver/lexer_spec.rb +103 -89
  37. data/spec/lib/flavour_saver/nodes_spec.rb +85 -0
  38. data/spec/lib/flavour_saver/parser_spec.rb +155 -95
  39. data/spec/lib/flavour_saver/runtime_spec.rb +94 -96
  40. data/spec/lib/flavour_saver/template_spec.rb +1 -2
  41. metadata +19 -48
@@ -1,202 +1,380 @@
1
- require 'rltk'
2
- require 'rltk/ast'
3
- require 'flavour_saver/nodes'
1
+ require "flavour_saver/error"
2
+ require "flavour_saver/nodes"
4
3
 
5
4
  module FlavourSaver
6
- class Parser < RLTK::Parser
7
-
8
- class UnbalancedBlockError < StandardError; end
9
-
10
- class Environment < RLTK::Parser::Environment
11
- def push_block block
12
- blocks.push(block.name)
13
- block
5
+ # Recursive descent parser for the token stream produced by Lexer.
6
+ #
7
+ # template := (OUT | raw_block | expression)*
8
+ # raw_block := RAWSTART RAWSTRING RAWEND
9
+ # expression := block | expr | comment | safe_expr | partial
10
+ # block := block_start template [else template] block_end
11
+ # block_start := EXPRST (HASH | HAT) WHITE? IDENT [WHITE arguments] WHITE? EXPRE
12
+ # else := EXPRST WHITE? (ELSE | HAT) WHITE? EXPRE
13
+ # block_end := EXPRST FWSL WHITE? IDENT WHITE? EXPRE
14
+ # expr := EXPRST contents EXPRE
15
+ # safe_expr := TEXPRST contents TEXPRE | EXPRST AMP contents EXPRE
16
+ # comment := EXPRST BANG COMMENT EXPRE
17
+ # partial := EXPRST WHITE? GT WHITE? (STRING | (IDENT | LITERAL) [WHITE? (call | lit)]) WHITE? EXPRE
18
+ # contents := WHITE? call WHITE?
19
+ # call := DOT | object_path [WHITE arguments]
20
+ # arguments := argument (WHITE argument)* [WHITE hash] | hash
21
+ # argument := object_path | lit | OPAR contents CPAR
22
+ # hash := IDENT EQ hash_value (WHITE IDENT EQ hash_value)*
23
+ # hash_value := OPAR contents CPAR | string | NUMBER | object_path
24
+ # object_path := object ((DOT | FWSL) (object | NUMBER))*
25
+ # object := AT IDENT | IDENT | LITERAL | (DOT DOT FWSL)+ (IDENT | LITERAL)
26
+ # lit := STRING | S_STRING | NUMBER | BOOL
27
+ class Parser
28
+ class UnbalancedBlockError < Error; end
29
+
30
+ class NotInLanguage < Error
31
+ def initialize(message = "String not in language.")
32
+ super
14
33
  end
34
+ end
15
35
 
16
- def pop_block block
17
- b = blocks.pop
18
- raise UnbalancedBlockError, "Unable to find matching opening for {{/#{block.name}}}" if b != block.name
19
- block
36
+ STRINGS = [:STRING, :S_STRING]
37
+ LITERALS = [*STRINGS, :NUMBER, :BOOL]
38
+ OBJECT_STARTS = [:AT, :IDENT, :LITERAL, :DOT]
39
+ ARGUMENT_STARTS = [*OBJECT_STARTS, *LITERALS, :OPAR]
40
+
41
+ # Rendering overflows a Fiber's stack at around 80 levels, and
42
+ # SystemStackError isn't a FlavourSaver::Error.
43
+ MAX_DEPTH = 32
44
+
45
+ def self.parse(tokens)
46
+ new.parse(tokens)
47
+ end
48
+
49
+ def parse(tokens)
50
+ @tokens = tokens
51
+ @pos = 0
52
+ @depth = 0
53
+ template = parse_template
54
+ expect(:EOS)
55
+ raise NotInLanguage unless @pos == @tokens.size
56
+ template
57
+ end
58
+
59
+ private
60
+
61
+ def parse_template
62
+ items = []
63
+ loop do
64
+ case peek
65
+ when :OUT
66
+ items << OutputNode.new(advance.value)
67
+ when :RAWSTART
68
+ items << parse_raw_block
69
+ when :TEXPRST
70
+ items << parse_triple_stash
71
+ when :EXPRST
72
+ break if else? || block_end?
73
+ items << parse_expression
74
+ else
75
+ break
76
+ end
20
77
  end
21
-
22
- def blocks
23
- @blocks ||= []
78
+ TemplateNode.new(items)
79
+ end
80
+
81
+ def parse_raw_block
82
+ expect(:RAWSTART)
83
+ raw = expect(:RAWSTRING).value
84
+ expect(:RAWEND)
85
+ OutputNode.new(raw)
86
+ end
87
+
88
+ def parse_triple_stash
89
+ expect(:TEXPRST)
90
+ call = parse_contents
91
+ expect(:TEXPRE)
92
+ SafeExpressionNode.new(call)
93
+ end
94
+
95
+ def parse_expression
96
+ case peek(1)
97
+ when :HASH then parse_block
98
+ when :HAT then parse_block
99
+ when :BANG then parse_comment
100
+ when :AMP
101
+ expect(:EXPRST)
102
+ expect(:AMP)
103
+ call = parse_contents
104
+ expect(:EXPRE)
105
+ SafeExpressionNode.new(call)
106
+ else
107
+ if peek(skip_white(1)) == :GT
108
+ parse_partial
109
+ else
110
+ expect(:EXPRST)
111
+ call = parse_contents
112
+ expect(:EXPRE)
113
+ ExpressionNode.new(call)
114
+ end
24
115
  end
25
116
  end
26
117
 
27
- left :DOT
28
- right :EQ
29
-
30
- production(:template) do
31
- clause('template_items') { |i| TemplateNode.new(i) }
32
- clause('') { TemplateNode.new([]) }
33
- end
34
-
35
- # empty_list(:template_items, [:output, :expression], 'WHITE?')
36
- production(:template_items) do
37
- clause('template_item') { |i| [i] }
38
- clause('template_items template_item') { |i0,i1| i0 << i1 }
39
- end
40
-
41
- production(:template_item) do
42
- clause('raw_bl') { |e| e }
43
- clause('output') { |e| e }
44
- clause('expression') { |e| e }
45
- end
46
-
47
- production(:output) do
48
- clause('OUT') { |o| OutputNode.new(o) }
49
- end
50
-
51
- production(:raw_bl) do
52
- clause('RAWSTART RAWSTRING RAWEND') { |_,e,_| OutputNode.new(e) }
53
- end
54
-
55
- production(:expression) do
56
- clause('block_expression') { |e| e }
57
- clause('expr') { |e| ExpressionNode.new(e) }
58
- clause('expr_comment') { |e| CommentNode.new(e) }
59
- clause('expr_safe') { |e| SafeExpressionNode.new(e) }
60
- clause('partial') { |e| e }
61
- end
62
-
63
- production(:partial) do
64
- clause('EXPRST WHITE? GT WHITE? STRING WHITE? EXPRE') { |_,_,_,_,e,_,_| PartialNode.new(e,[]) }
65
- clause('EXPRST WHITE? GT WHITE? IDENT WHITE? EXPRE') { |_,_,_,_,e,_,_| PartialNode.new(e,[]) }
66
- clause('EXPRST WHITE? GT WHITE? IDENT WHITE? call WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,e1,nil) }
67
- clause('EXPRST WHITE? GT WHITE? IDENT WHITE? lit WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,[],e1) }
68
- clause('EXPRST WHITE? GT WHITE? LITERAL WHITE? EXPRE') { |_,_,_,_,e,_,_| PartialNode.new(e,[]) }
69
- clause('EXPRST WHITE? GT WHITE? LITERAL WHITE? call WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,e1,nil) }
70
- clause('EXPRST WHITE? GT WHITE? LITERAL WHITE? lit WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,[],e1) }
71
- end
72
-
73
- production(:block_expression) do
74
- clause('expr_bl_start template expr_else template expr_bl_end') { |e0,e1,_,e3,e2| BlockExpressionNodeWithElse.new([e0], e1,e2,e3) }
75
- clause('expr_bl_start template expr_bl_end') { |e0,e1,e2| BlockExpressionNode.new([e0],e1,e2) }
76
- clause('expr_bl_inv_start template expr_else template expr_bl_end') { |e0,e1,_,e3,e2| BlockExpressionNodeWithElse.new([e0], e2,e2,e1) }
77
- clause('expr_bl_inv_start template expr_bl_end') { |e0,e1,e2| BlockExpressionNodeWithElse.new([e0],TemplateNode.new([]),e2,e1) }
78
- end
79
-
80
- production(:expr_else) do
81
- clause('EXPRST WHITE? ELSE WHITE? EXPRE') { |_,_,_,_,_| }
82
- clause('EXPRST WHITE? HAT WHITE? EXPRE') { |_,_,_,_,_| }
83
- end
84
-
85
- production(:expr) do
86
- clause('EXPRST expression_contents EXPRE') { |_,e,_| e }
87
- end
88
-
89
- production(:subexpr) do
90
- clause('OPAR expression_contents CPAR') { |_,e,_| e }
91
- end
92
-
93
- production(:expr_comment) do
94
- clause('EXPRST BANG COMMENT EXPRE') { |_,_,e,_| e }
95
- end
96
-
97
- production(:expr_safe) do
98
- clause('TEXPRST expression_contents TEXPRE') { |_,e,_| e }
99
- clause('EXPRST AMP expression_contents EXPRE') { |_,_,e,_| e }
100
- end
101
-
102
- production(:expr_bl_start) do
103
- clause('EXPRST HASH WHITE? IDENT WHITE? EXPRE') { |_,_,_,e,_,_| push_block CallNode.new(e,[]) }
104
- clause('EXPRST HASH WHITE? IDENT WHITE arguments WHITE? EXPRE') { |_,_,_,e,_,a,_,_| push_block CallNode.new(e,a) }
105
- end
106
-
107
- production(:expr_bl_inv_start) do
108
- clause('EXPRST HAT WHITE? IDENT WHITE? EXPRE') { |_,_,_,e,_,_| push_block CallNode.new(e,[]) }
109
- clause('EXPRST HAT WHITE? IDENT WHITE arguments WHITE? EXPRE') { |_,_,_,e,_,a,_,_| push_block CallNode.new(e,a) }
118
+ def parse_block
119
+ nested do
120
+ expect(:EXPRST)
121
+ inverted = advance.type == :HAT
122
+ skip(:WHITE)
123
+ name = expect(:IDENT).value
124
+ arguments = []
125
+ if peek == :WHITE && peek(1) != :EXPRE
126
+ advance
127
+ arguments = parse_arguments
128
+ end
129
+ skip(:WHITE)
130
+ expect(:EXPRE)
131
+ opener = CallNode.new(name, arguments)
132
+
133
+ contents = parse_template
134
+ alternate = nil
135
+ if else?
136
+ parse_else
137
+ alternate = parse_template
138
+ end
139
+ closer = parse_block_end(opener)
140
+
141
+ if inverted
142
+ # An inverted section renders its body when the value is falsy, so
143
+ # the body is the alternate and the {{else}} part (if any) the contents.
144
+ BlockExpressionNodeWithElse.new([opener], alternate || TemplateNode.new([]), closer, contents)
145
+ elsif alternate
146
+ BlockExpressionNodeWithElse.new([opener], contents, closer, alternate)
147
+ else
148
+ BlockExpressionNode.new([opener], contents, closer)
149
+ end
150
+ end
110
151
  end
111
152
 
112
- production(:expr_bl_end) do
113
- clause('EXPRST FWSL WHITE? IDENT WHITE? EXPRE') { |_,_,_,e,_,_| pop_block CallNode.new(e,[]) }
114
- end
153
+ def parse_else
154
+ expect(:EXPRST)
155
+ skip(:WHITE)
156
+ raise NotInLanguage unless [:ELSE, :HAT].include?(peek)
157
+ advance
158
+ skip(:WHITE)
159
+ expect(:EXPRE)
160
+ end
161
+
162
+ def parse_block_end(opener)
163
+ expect(:EXPRST)
164
+ expect(:FWSL)
165
+ skip(:WHITE)
166
+ name = expect(:IDENT).value
167
+ skip(:WHITE)
168
+ expect(:EXPRE)
169
+ if name != opener.name
170
+ raise UnbalancedBlockError, "Unable to find matching opening for {{/#{name}}}"
171
+ end
172
+ CallNode.new(name, [])
173
+ end
174
+
175
+ def parse_comment
176
+ expect(:EXPRST)
177
+ expect(:BANG)
178
+ comment = expect(:COMMENT).value
179
+ expect(:EXPRE)
180
+ CommentNode.new(comment)
181
+ end
182
+
183
+ def parse_partial
184
+ expect(:EXPRST)
185
+ skip(:WHITE)
186
+ expect(:GT)
187
+ skip(:WHITE)
188
+ if peek == :STRING
189
+ name = advance.value
190
+ skip(:WHITE)
191
+ expect(:EXPRE)
192
+ return PartialNode.new(name, [])
193
+ end
115
194
 
116
- production(:expression_contents) do
117
- clause('WHITE? call WHITE?') { |_,e,_| e }
118
- end
195
+ raise NotInLanguage unless [:IDENT, :LITERAL].include?(peek)
196
+ name = advance.value
197
+ skip(:WHITE)
198
+ partial =
199
+ if peek == :EXPRE
200
+ PartialNode.new(name, [])
201
+ elsif LITERALS.include?(peek)
202
+ PartialNode.new(name, [], parse_literal)
203
+ else
204
+ PartialNode.new(name, parse_call, nil)
205
+ end
206
+ skip(:WHITE)
207
+ expect(:EXPRE)
208
+ partial
209
+ end
210
+
211
+ def parse_contents
212
+ skip(:WHITE)
213
+ call = parse_call
214
+ skip(:WHITE)
215
+ call
216
+ end
217
+
218
+ def parse_call
219
+ if peek == :DOT && peek(1) != :DOT
220
+ advance
221
+ return [CallNode.new("this", [])]
222
+ end
119
223
 
120
- production(:call) do
121
- clause('object_path') { |e| e }
122
- clause('object_path WHITE arguments') { |e0,_,e1| e0.last.arguments = e1; e0 }
123
- clause('DOT') { |_| [CallNode.new('this', [])] }
224
+ path = parse_object_path
225
+ if peek == :WHITE && ARGUMENT_STARTS.include?(peek(1))
226
+ advance
227
+ path.last.arguments = parse_arguments
228
+ end
229
+ path
230
+ end
231
+
232
+ def parse_arguments
233
+ arguments = []
234
+ loop do
235
+ if hash_item?
236
+ arguments << parse_hash
237
+ break
238
+ end
239
+ arguments << parse_argument
240
+ break unless peek == :WHITE && ARGUMENT_STARTS.include?(peek(1))
241
+ advance
242
+ end
243
+ arguments
124
244
  end
125
245
 
126
- production('arguments') do
127
- clause('argument_list') { |e| e }
128
- clause('argument_list WHITE hash') { |e0,_,e1| e0 + [e1] }
129
- clause('hash') { |e| [e] }
246
+ def parse_argument
247
+ if peek == :OPAR
248
+ parse_subexpression
249
+ elsif LITERALS.include?(peek)
250
+ parse_literal
251
+ else
252
+ parse_object_path
253
+ end
130
254
  end
131
255
 
132
- nonempty_list(:argument_list, [:object_path, :lit, :subexpr], :WHITE)
133
-
134
- production(:lit) do
135
- clause('string') { |e| e }
136
- clause('number') { |e| e }
137
- clause('boolean') { |e| e }
256
+ def parse_subexpression
257
+ nested do
258
+ expect(:OPAR)
259
+ call = parse_contents
260
+ expect(:CPAR)
261
+ call
262
+ end
138
263
  end
139
264
 
140
- production(:string) do
141
- clause('STRING') { |e| StringNode.new(e) }
142
- clause('S_STRING') { |e| StringNode.new(e) }
265
+ def parse_hash
266
+ hash = {}
267
+ loop do
268
+ key = expect(:IDENT).value.to_sym
269
+ expect(:EQ)
270
+ hash[key] =
271
+ case peek
272
+ when :OPAR then parse_subexpression
273
+ when *STRINGS then StringNode.new(advance.value)
274
+ when :NUMBER then NumberNode.new(advance.value)
275
+ else parse_object_path
276
+ end
277
+ break unless peek == :WHITE && hash_item?(1)
278
+ advance
279
+ end
280
+ hash
143
281
  end
144
282
 
145
- production(:number) do
146
- clause('NUMBER') { |n| NumberNode.new(n) }
283
+ def parse_literal
284
+ token = advance
285
+ case token.type
286
+ when *STRINGS then StringNode.new(token.value)
287
+ when :NUMBER then NumberNode.new(token.value)
288
+ when :BOOL then token.value ? TrueNode.new(true) : FalseNode.new(false)
289
+ else raise NotInLanguage
290
+ end
147
291
  end
148
292
 
149
- production(:boolean) do
150
- clause('BOOL') { |b| b ? TrueNode.new(true) : FalseNode.new(false) }
293
+ def parse_object_path
294
+ path = [parse_object]
295
+ while [:DOT, :FWSL].include?(peek)
296
+ advance
297
+ # Accomodates objects dereferenced with a number like foo.0.text
298
+ path << ((peek == :NUMBER) ? LiteralCallNode.new(advance.value, []) : parse_object)
299
+ end
300
+ path
301
+ end
302
+
303
+ def parse_object
304
+ case peek
305
+ when :AT
306
+ advance
307
+ LocalVarNode.new(expect(:IDENT).value)
308
+ when :IDENT
309
+ CallNode.new(advance.value, [])
310
+ when :LITERAL
311
+ LiteralCallNode.new(advance.value, [])
312
+ when :DOT
313
+ depth = 0
314
+ while peek == :DOT
315
+ expect(:DOT)
316
+ expect(:DOT)
317
+ expect(:FWSL)
318
+ depth += 1
319
+ end
320
+ raise NotInLanguage unless [:IDENT, :LITERAL].include?(peek)
321
+ ParentCallNode.new(advance.value, [], depth)
322
+ else
323
+ raise NotInLanguage
324
+ end
151
325
  end
152
326
 
153
- production(:hash) do
154
- clause('hash_item') { |e| e }
155
- clause('hash WHITE hash_item') { |e0,_,e1| e0.merge(e1) }
327
+ def nested
328
+ @depth += 1
329
+ if @depth > MAX_DEPTH
330
+ raise NotInLanguage, "Blocks and subexpressions can't be nested more than #{MAX_DEPTH} levels deep."
331
+ end
332
+ yield
333
+ ensure
334
+ @depth -= 1
335
+ end
336
+
337
+ # {{else}}, {{^}} or {{ ^ }}, but not the inverted section {{^foo}}.
338
+ def else?
339
+ return false unless peek == :EXPRST
340
+ i = skip_white(1)
341
+ case peek(i)
342
+ when :ELSE then true
343
+ when :HAT then i > 1 || peek(skip_white(i + 1)) != :IDENT
344
+ else false
345
+ end
156
346
  end
157
347
 
158
- production(:hash_item) do
159
- clause('IDENT EQ subexpr') { |e0,_,e1| { e0.to_sym => e1 } }
160
- clause('IDENT EQ string') { |e0,_,e1| { e0.to_sym => e1 } }
161
- clause('IDENT EQ number') { |e0,_,e1| { e0.to_sym => e1 } }
162
- clause('IDENT EQ object_path') { |e0,_,e1| { e0.to_sym => e1 } }
348
+ def block_end?
349
+ peek == :EXPRST && peek(1) == :FWSL
163
350
  end
164
351
 
165
- production(:object_sep) do
166
- clause('DOT') { |_| }
167
- clause('FWSL') { |_| }
352
+ def hash_item?(offset = 0)
353
+ peek(offset) == :IDENT && peek(offset + 1) == :EQ
168
354
  end
169
355
 
170
- production(:object_path) do
171
- clause('object') { |e| [e] }
172
- clause('object_path object_sep object') { |e0,_,e1| e0 + [e1] }
173
-
174
- # Accomodates objects dereferenced with a number like foo.0.text
175
- clause('object_path object_sep number_object') { |e0,_,e1| e0 + [e1] }
356
+ def peek(offset = 0)
357
+ token = @tokens[@pos + offset]
358
+ token&.type
176
359
  end
177
360
 
178
- production(:number_object) do
179
- clause('NUMBER') { |e| LiteralCallNode.new(e, []) }
361
+ def skip_white(offset)
362
+ (peek(offset) == :WHITE) ? offset + 1 : offset
180
363
  end
181
364
 
182
- production(:object) do
183
- clause('AT IDENT') { |_,e| LocalVarNode.new(e) }
184
- clause('IDENT') { |e| CallNode.new(e, []) }
185
- clause('LITERAL') { |e| LiteralCallNode.new(e, []) }
186
- clause('parent_call') { |e| e }
365
+ def advance
366
+ token = @tokens[@pos] or raise NotInLanguage
367
+ @pos += 1
368
+ token
187
369
  end
188
370
 
189
- production(:parent_call) do
190
- clause('backtrack IDENT') { |i,e| ParentCallNode.new(e,[],i) }
191
- clause('backtrack LITERAL') { |i,e| ParentCallNode.new(e,[],i) }
371
+ def expect(type)
372
+ raise NotInLanguage unless peek == type
373
+ advance
192
374
  end
193
375
 
194
- production(:backtrack) do
195
- clause('DOT DOT FWSL') { |_,_,_| 1 }
196
- clause('backtrack DOT DOT FWSL') { |i,_,_,_| i += 1 }
376
+ def skip(type)
377
+ advance if peek == type
197
378
  end
198
-
199
- finalize
200
-
201
379
  end
202
380
  end
@@ -2,12 +2,11 @@ module FlavourSaver
2
2
  UnknownPartialException = Class.new(StandardError)
3
3
 
4
4
  class Partial
5
-
6
- def self.register_partial(name, content=nil, &block)
7
- if block.respond_to? :call
8
- partials[name.to_s] = block
5
+ def self.register_partial(name, content = nil, &block)
6
+ partials[name.to_s] = if block.respond_to? :call
7
+ block
9
8
  else
10
- partials[name.to_s] = Parser.parse(Lexer.lex(content))
9
+ Parser.parse(Lexer.lex(content))
11
10
  end
12
11
  end
13
12
 
@@ -24,6 +23,5 @@ module FlavourSaver
24
23
  raise UnknownPartialException, "I can't find the partial named #{name.inspect}" unless p
25
24
  p
26
25
  end
27
-
28
26
  end
29
27
  end
@@ -1,10 +1,16 @@
1
1
  module FlavourSaver
2
2
  class RailsPartial
3
3
  def self.register_partial(*args)
4
- raise RuntimeError, "No need to register partials inside Rails."
4
+ raise "No need to register partials inside Rails."
5
+ end
6
+
7
+ def self.reset_partials
8
+ end
9
+
10
+ def self.partials
11
+ end
12
+
13
+ def self.fetch
5
14
  end
6
- def self.reset_partials; end
7
- def self.partials; end
8
- def self.fetch; end
9
15
  end
10
16
  end