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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +14 -0
- data/.standard.yml +1 -0
- data/CHANGELOG.md +57 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +48 -5
- data/README.md +153 -271
- data/flavour_saver.gemspec +17 -19
- data/lib/flavour_saver/error.rb +5 -0
- data/lib/flavour_saver/helpers.rb +19 -18
- data/lib/flavour_saver/lexer.rb +123 -50
- data/lib/flavour_saver/nodes.rb +107 -19
- data/lib/flavour_saver/parser.rb +339 -161
- data/lib/flavour_saver/partial.rb +4 -6
- data/lib/flavour_saver/rails_partial.rb +10 -4
- data/lib/flavour_saver/runtime.rb +76 -81
- data/lib/flavour_saver/template.rb +3 -5
- data/lib/flavour_saver/version.rb +1 -1
- data/lib/flavour_saver.rb +17 -17
- data/spec/acceptance/backtrack_spec.rb +9 -9
- data/spec/acceptance/comment_spec.rb +7 -7
- data/spec/acceptance/custom_block_helper_spec.rb +12 -12
- data/spec/acceptance/custom_helper_spec.rb +8 -8
- data/spec/acceptance/ensure_no_rce_spec.rb +64 -53
- data/spec/acceptance/handlebars_qunit_spec.rb +295 -297
- data/spec/acceptance/if_else_spec.rb +6 -6
- data/spec/acceptance/multi_level_with_spec.rb +10 -11
- data/spec/acceptance/nesting_depth_spec.rb +34 -0
- data/spec/acceptance/one_character_identifier_spec.rb +8 -8
- data/spec/acceptance/raw_block_spec.rb +7 -7
- data/spec/acceptance/sections_spec.rb +17 -14
- data/spec/acceptance/segment_literals_spec.rb +5 -5
- data/spec/acceptance/simple_expression_spec.rb +9 -9
- data/spec/acceptance/subexpression_spec.rb +18 -17
- data/spec/acceptance/unless_spec.rb +19 -6
- data/spec/lib/flavour_saver/lexer_spec.rb +103 -89
- data/spec/lib/flavour_saver/nodes_spec.rb +85 -0
- data/spec/lib/flavour_saver/parser_spec.rb +155 -95
- data/spec/lib/flavour_saver/runtime_spec.rb +94 -96
- data/spec/lib/flavour_saver/template_spec.rb +1 -2
- metadata +19 -48
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "delegate"
|
|
2
2
|
|
|
3
3
|
module FlavourSaver
|
|
4
4
|
module Helpers
|
|
@@ -11,19 +11,19 @@ module FlavourSaver
|
|
|
11
11
|
r = []
|
|
12
12
|
count = 0
|
|
13
13
|
collection.each do |element|
|
|
14
|
-
locals ={
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
locals = {
|
|
15
|
+
"index" => count,
|
|
16
|
+
"last" => count == collection.size - 1,
|
|
17
|
+
"first" => count == 0
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
locals[
|
|
20
|
+
locals["key"], element = element if collection.is_a?(Hash)
|
|
21
21
|
|
|
22
22
|
r << yield.contents(element, locals)
|
|
23
23
|
count += 1
|
|
24
24
|
end
|
|
25
25
|
yield.rendered!
|
|
26
|
-
r.join
|
|
26
|
+
r.join ""
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def if(value)
|
|
@@ -44,7 +44,7 @@ module FlavourSaver
|
|
|
44
44
|
|
|
45
45
|
def log(message)
|
|
46
46
|
FS.logger.debug("FlavourSaver: #{message}")
|
|
47
|
-
|
|
47
|
+
""
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
private
|
|
@@ -59,11 +59,10 @@ module FlavourSaver
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
class Decorator < Defaults
|
|
62
|
-
|
|
63
62
|
def initialize(locals, source)
|
|
64
63
|
@source = source
|
|
65
64
|
mixin = Module.new do
|
|
66
|
-
locals.each do |name,impl|
|
|
65
|
+
locals.each do |name, impl|
|
|
67
66
|
define_method name, &impl
|
|
68
67
|
end
|
|
69
68
|
end
|
|
@@ -88,7 +87,9 @@ module FlavourSaver
|
|
|
88
87
|
super || @source.respond_to?(name)
|
|
89
88
|
end
|
|
90
89
|
|
|
91
|
-
|
|
90
|
+
# Defining respond_to_missing? would change what #method returns, which
|
|
91
|
+
# Runtime#forbidden_method? relies on. See the SECURITY note below.
|
|
92
|
+
def method_missing(name, *args, &b) # standard:disable Style/MissingRespondToMissing
|
|
92
93
|
# I would rather have it raise a NameError, but Moustache
|
|
93
94
|
# compatibility requires that missing helpers return
|
|
94
95
|
# nothing. A good place for bugs to hide.
|
|
@@ -116,7 +117,7 @@ module FlavourSaver
|
|
|
116
117
|
@registered_helpers ||= {}
|
|
117
118
|
end
|
|
118
119
|
|
|
119
|
-
def register_helper(method
|
|
120
|
+
def register_helper(method, &b)
|
|
120
121
|
if !method.is_a?(Symbol) && method.respond_to?(:name)
|
|
121
122
|
registered_helpers[method.name.to_sym] = method
|
|
122
123
|
elsif b
|
|
@@ -134,13 +135,13 @@ module FlavourSaver
|
|
|
134
135
|
@registered_helpers = {}
|
|
135
136
|
end
|
|
136
137
|
|
|
137
|
-
def decorate_with(context, helper_names=[], locals={})
|
|
138
|
+
def decorate_with(context, helper_names = [], locals = {})
|
|
138
139
|
helpers = if helper_names.any?
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
helper_symbols = helper_names.map(&:to_sym)
|
|
141
|
+
registered_helpers.select { |k, v| helper_symbols.member? k }
|
|
142
|
+
else
|
|
143
|
+
registered_helpers
|
|
144
|
+
end
|
|
144
145
|
helpers = helpers.merge(locals)
|
|
145
146
|
Decorator.new(helpers, context)
|
|
146
147
|
end
|
data/lib/flavour_saver/lexer.rb
CHANGED
|
@@ -1,124 +1,168 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "strscan"
|
|
2
|
+
require "flavour_saver/error"
|
|
2
3
|
|
|
3
4
|
module FlavourSaver
|
|
4
|
-
class Lexer
|
|
5
|
+
class Lexer
|
|
6
|
+
Token = Struct.new(:type, :value)
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
class LexingError < Error; end
|
|
9
|
+
|
|
10
|
+
# How much of the unmatched input a LexingError message quotes.
|
|
11
|
+
ERROR_EXCERPT_LENGTH = 50
|
|
12
|
+
|
|
13
|
+
Rule = Struct.new(:pattern, :action)
|
|
14
|
+
|
|
15
|
+
# Rules are grouped by lexer state. At each position every rule for the
|
|
16
|
+
# current state is tried and the longest match wins; on a tie the rule
|
|
17
|
+
# defined first wins. An action returns nil (no token), a token type, or
|
|
18
|
+
# a [type, value] pair.
|
|
19
|
+
def self.rules
|
|
20
|
+
@rules ||= Hash.new { |h, k| h[k] = [] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.rule(pattern, state = :default, &action)
|
|
24
|
+
rules[state] << Rule.new(pattern, action || proc {})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.lex(template)
|
|
28
|
+
new.lex(template)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def lex(template)
|
|
32
|
+
@states = [:default]
|
|
33
|
+
tokens = []
|
|
34
|
+
scanner = StringScanner.new(template)
|
|
35
|
+
|
|
36
|
+
until scanner.eos?
|
|
37
|
+
match, rule = longest_match(scanner)
|
|
38
|
+
unless rule
|
|
39
|
+
raise LexingError, "Unable to match string with any of the given rules: #{excerpt(scanner.rest)}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
scanner.pos += match.bytesize
|
|
43
|
+
type, value = instance_exec(match, &rule.action)
|
|
44
|
+
tokens << Token.new(type, value) if type
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
tokens << Token.new(:EOS)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# seems to have problem with hash symbol in regex
|
|
51
|
+
rule(/\{\{\{\{raw\}\}\}\}/, :default) do
|
|
8
52
|
push_state :raw
|
|
9
53
|
:RAWSTART
|
|
10
54
|
end
|
|
11
55
|
|
|
12
|
-
rule
|
|
13
|
-
[
|
|
56
|
+
rule(/.*?(?=\{\{\{\{\/raw\}\}\}\})/m, :raw) do |str|
|
|
57
|
+
[:RAWSTRING, str]
|
|
14
58
|
end
|
|
15
59
|
|
|
16
|
-
rule
|
|
60
|
+
rule(/\{\{\{\{\/raw\}\}\}\}/, :raw) do
|
|
17
61
|
pop_state
|
|
18
62
|
:RAWEND
|
|
19
63
|
end
|
|
20
64
|
|
|
21
|
-
rule
|
|
65
|
+
rule(/{{{/, :default) do
|
|
22
66
|
push_state :expression
|
|
23
67
|
:TEXPRST
|
|
24
68
|
end
|
|
25
69
|
|
|
26
|
-
rule
|
|
70
|
+
rule(/{{/, :default) do
|
|
27
71
|
push_state :expression
|
|
28
72
|
:EXPRST
|
|
29
73
|
end
|
|
30
74
|
|
|
31
|
-
rule
|
|
75
|
+
rule(/#/, :expression) do
|
|
32
76
|
:HASH
|
|
33
77
|
end
|
|
34
78
|
|
|
35
|
-
rule
|
|
79
|
+
rule(/\//, :expression) do
|
|
36
80
|
:FWSL
|
|
37
81
|
end
|
|
38
82
|
|
|
39
|
-
rule
|
|
83
|
+
rule(/&/, :expression) do
|
|
40
84
|
:AMP
|
|
41
85
|
end
|
|
42
86
|
|
|
43
|
-
rule
|
|
87
|
+
rule(/\^/, :expression) do
|
|
44
88
|
:HAT
|
|
45
89
|
end
|
|
46
90
|
|
|
47
|
-
rule
|
|
91
|
+
rule(/@/, :expression) do
|
|
48
92
|
:AT
|
|
49
93
|
end
|
|
50
94
|
|
|
51
|
-
rule
|
|
95
|
+
rule(/>/, :expression) do
|
|
52
96
|
:GT
|
|
53
97
|
end
|
|
54
98
|
|
|
55
|
-
rule
|
|
56
|
-
[
|
|
99
|
+
rule(/([0-9]+(\.[0-9]+)?)/, :expression) do |n|
|
|
100
|
+
[:NUMBER, n]
|
|
57
101
|
end
|
|
58
102
|
|
|
59
|
-
rule
|
|
60
|
-
[
|
|
103
|
+
rule(/true/, :expression) do |i|
|
|
104
|
+
[:BOOL, true]
|
|
61
105
|
end
|
|
62
106
|
|
|
63
|
-
rule
|
|
64
|
-
[
|
|
107
|
+
rule(/false/, :expression) do |i|
|
|
108
|
+
[:BOOL, false]
|
|
65
109
|
end
|
|
66
110
|
|
|
67
|
-
rule
|
|
111
|
+
rule(/!/, :expression) do
|
|
68
112
|
push_state :comment
|
|
69
113
|
:BANG
|
|
70
114
|
end
|
|
71
115
|
|
|
72
|
-
rule
|
|
116
|
+
rule(/([^}]*)/, :comment) do |comment|
|
|
73
117
|
pop_state
|
|
74
|
-
[
|
|
118
|
+
[:COMMENT, comment]
|
|
75
119
|
end
|
|
76
120
|
|
|
77
|
-
rule
|
|
121
|
+
rule(/else/, :expression) do
|
|
78
122
|
:ELSE
|
|
79
123
|
end
|
|
80
124
|
|
|
81
|
-
rule
|
|
82
|
-
[
|
|
125
|
+
rule(/([A-Za-z_]\w*)/, :expression) do |name|
|
|
126
|
+
[:IDENT, name]
|
|
83
127
|
end
|
|
84
128
|
|
|
85
|
-
rule
|
|
129
|
+
rule(/\./, :expression) do
|
|
86
130
|
:DOT
|
|
87
131
|
end
|
|
88
132
|
|
|
89
|
-
rule
|
|
133
|
+
rule(/\(/, :expression) do
|
|
90
134
|
:OPAR
|
|
91
135
|
end
|
|
92
136
|
|
|
93
|
-
rule
|
|
137
|
+
rule(/\)/, :expression) do
|
|
94
138
|
:CPAR
|
|
95
139
|
end
|
|
96
140
|
|
|
97
|
-
rule
|
|
141
|
+
rule(/=/, :expression) do
|
|
98
142
|
:EQ
|
|
99
143
|
end
|
|
100
144
|
|
|
101
|
-
rule
|
|
145
|
+
rule(/"/, :expression) do
|
|
102
146
|
push_state :string
|
|
103
147
|
end
|
|
104
148
|
|
|
105
|
-
rule
|
|
106
|
-
[
|
|
149
|
+
rule(/(\\"|[^"])*/, :string) do |str|
|
|
150
|
+
[:STRING, str]
|
|
107
151
|
end
|
|
108
152
|
|
|
109
|
-
rule
|
|
153
|
+
rule(/"/, :string) do
|
|
110
154
|
pop_state
|
|
111
155
|
end
|
|
112
156
|
|
|
113
|
-
rule
|
|
157
|
+
rule(/'/, :expression) do
|
|
114
158
|
push_state :s_string
|
|
115
159
|
end
|
|
116
160
|
|
|
117
|
-
rule
|
|
118
|
-
[
|
|
161
|
+
rule(/(\\'|[^'])*/, :s_string) do |str|
|
|
162
|
+
[:S_STRING, str]
|
|
119
163
|
end
|
|
120
164
|
|
|
121
|
-
rule
|
|
165
|
+
rule(/'/, :s_string) do
|
|
122
166
|
pop_state
|
|
123
167
|
end
|
|
124
168
|
|
|
@@ -130,38 +174,67 @@ module FlavourSaver
|
|
|
130
174
|
# Identifiers may be any unicode character except for the following:
|
|
131
175
|
# Whitespace ! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~
|
|
132
176
|
#
|
|
133
|
-
rule
|
|
134
|
-
[
|
|
177
|
+
rule(/([^\s!-#%-,.\/;->@\[-^`{-~]+)/, :expression) do |str|
|
|
178
|
+
[:LITERAL, str]
|
|
135
179
|
end
|
|
136
180
|
|
|
137
|
-
rule
|
|
181
|
+
rule(/\[/, :expression) do
|
|
138
182
|
push_state :segment_literal
|
|
139
183
|
end
|
|
140
184
|
|
|
141
|
-
rule
|
|
142
|
-
[
|
|
185
|
+
rule(/([^\]]+)/, :segment_literal) do |l|
|
|
186
|
+
[:LITERAL, l]
|
|
143
187
|
end
|
|
144
188
|
|
|
145
|
-
rule
|
|
189
|
+
rule(/]/, :segment_literal) do
|
|
146
190
|
pop_state
|
|
147
191
|
end
|
|
148
192
|
|
|
149
|
-
rule
|
|
193
|
+
rule(/\s+/, :expression) do
|
|
150
194
|
:WHITE
|
|
151
195
|
end
|
|
152
196
|
|
|
153
|
-
rule
|
|
197
|
+
rule(/}}}/, :expression) do
|
|
154
198
|
pop_state
|
|
155
199
|
:TEXPRE
|
|
156
200
|
end
|
|
157
201
|
|
|
158
|
-
rule
|
|
202
|
+
rule(/}}/, :expression) do
|
|
159
203
|
pop_state
|
|
160
204
|
:EXPRE
|
|
161
205
|
end
|
|
162
206
|
|
|
163
|
-
rule
|
|
164
|
-
[
|
|
207
|
+
rule(/.*?(?={{|\z)/m, :default) do |output|
|
|
208
|
+
[:OUT, output]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
private
|
|
212
|
+
|
|
213
|
+
def longest_match(scanner)
|
|
214
|
+
best = nil
|
|
215
|
+
self.class.rules[state].each do |rule|
|
|
216
|
+
text = scanner.check(rule.pattern)
|
|
217
|
+
best = [text, rule] if text && (best.nil? || best.first.length < text.length)
|
|
218
|
+
end
|
|
219
|
+
best
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def excerpt(rest)
|
|
223
|
+
(rest.length > ERROR_EXCERPT_LENGTH) ? "#{rest[0, ERROR_EXCERPT_LENGTH]}..." : rest
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def state
|
|
227
|
+
@states.last
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def push_state(state)
|
|
231
|
+
@states.push(state)
|
|
232
|
+
nil
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def pop_state
|
|
236
|
+
@states.pop
|
|
237
|
+
nil
|
|
165
238
|
end
|
|
166
239
|
end
|
|
167
240
|
end
|
data/lib/flavour_saver/nodes.rb
CHANGED
|
@@ -1,12 +1,99 @@
|
|
|
1
|
-
require 'rltk'
|
|
2
|
-
require 'rltk/ast'
|
|
3
|
-
require 'flavour_saver/nodes'
|
|
4
|
-
|
|
5
1
|
module FlavourSaver
|
|
6
|
-
class Node
|
|
2
|
+
class Node
|
|
3
|
+
class TypeMismatch < StandardError; end
|
|
4
|
+
|
|
5
|
+
# Fields are declared as either values (plain data) or children (nodes,
|
|
6
|
+
# or arrays of nodes, which get their #parent set). The constructor takes
|
|
7
|
+
# every value, then every child, positionally, inherited ones first.
|
|
8
|
+
# A field's type is a class (nil is also allowed) or a class in an array,
|
|
9
|
+
# such as [CallNode].
|
|
10
|
+
@value_names = []
|
|
11
|
+
@child_names = []
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
attr_reader :value_names, :child_names
|
|
15
|
+
|
|
16
|
+
# Copy the parent's fields now, not on first read, so they can't depend on read order.
|
|
17
|
+
def inherited(subclass)
|
|
18
|
+
super
|
|
19
|
+
subclass.instance_variable_set(:@value_names, value_names.dup)
|
|
20
|
+
subclass.instance_variable_set(:@child_names, child_names.dup)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def value(name, type)
|
|
24
|
+
if field_class(name, type) <= Node
|
|
25
|
+
raise ArgumentError, "#{self}##{name} is a value, so its type can't be a Node"
|
|
26
|
+
end
|
|
27
|
+
value_names << name
|
|
28
|
+
attr_reader name
|
|
29
|
+
ivar = :"@#{name}"
|
|
30
|
+
define_method(:"#{name}=") do |value|
|
|
31
|
+
check_field_type(name, type, value)
|
|
32
|
+
instance_variable_set(ivar, value)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def child(name, type)
|
|
37
|
+
unless field_class(name, type) <= Node
|
|
38
|
+
raise ArgumentError, "#{self}##{name} is a child, so its type must be a Node"
|
|
39
|
+
end
|
|
40
|
+
child_names << name
|
|
41
|
+
attr_reader name
|
|
42
|
+
ivar = :"@#{name}"
|
|
43
|
+
define_method(:"#{name}=") do |node|
|
|
44
|
+
check_field_type(name, type, node)
|
|
45
|
+
Array(node).each { |n| n.parent = self }
|
|
46
|
+
instance_variable_set(ivar, node)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def field_class(name, type)
|
|
53
|
+
klass = (type.is_a?(Array) && type.size == 1) ? type.first : type
|
|
54
|
+
return klass if klass.is_a?(Class)
|
|
55
|
+
raise ArgumentError, "#{self}##{name} must have a class, or a class in an array, as its type"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
attr_accessor :parent
|
|
60
|
+
|
|
61
|
+
def initialize(*fields)
|
|
62
|
+
names = self.class.value_names + self.class.child_names
|
|
63
|
+
raise ArgumentError, "#{self.class} takes at most #{names.size} fields" if fields.size > names.size
|
|
64
|
+
names.each_with_index { |name, i| public_send(:"#{name}=", fields[i]) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def values
|
|
68
|
+
self.class.value_names.map { |name| public_send(name) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def children
|
|
72
|
+
self.class.child_names.map { |name| public_send(name) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ==(other)
|
|
76
|
+
other.class == self.class && other.values == values && other.children == children
|
|
77
|
+
end
|
|
78
|
+
|
|
7
79
|
def inspect
|
|
8
80
|
to_s.inspect
|
|
9
81
|
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def check_field_type(name, type, value)
|
|
86
|
+
valid =
|
|
87
|
+
if type.is_a?(Array)
|
|
88
|
+
value.is_a?(Array) && value.all? { |item| item.is_a?(type.first) }
|
|
89
|
+
else
|
|
90
|
+
value.nil? || value.is_a?(type)
|
|
91
|
+
end
|
|
92
|
+
return if valid
|
|
93
|
+
|
|
94
|
+
actual = (value.is_a?(Array) && value.any?) ? "an Array of #{value.map(&:class).uniq.join(", ")}" : value.class
|
|
95
|
+
raise TypeMismatch, "#{self.class}##{name} must be #{type.inspect}, not #{actual}"
|
|
96
|
+
end
|
|
10
97
|
end
|
|
11
98
|
|
|
12
99
|
class TemplateItemNode < Node; end
|
|
@@ -15,7 +102,7 @@ module FlavourSaver
|
|
|
15
102
|
child :items, [TemplateItemNode]
|
|
16
103
|
|
|
17
104
|
def to_s
|
|
18
|
-
items.
|
|
105
|
+
items.join ""
|
|
19
106
|
end
|
|
20
107
|
end
|
|
21
108
|
|
|
@@ -60,14 +147,14 @@ module FlavourSaver
|
|
|
60
147
|
value :name, String
|
|
61
148
|
value :arguments, Array
|
|
62
149
|
|
|
63
|
-
def arguments_to_str(str=
|
|
150
|
+
def arguments_to_str(str = "")
|
|
64
151
|
str = str.dup # RLTK magic?
|
|
65
152
|
arguments.each do |arg|
|
|
66
|
-
str <<
|
|
153
|
+
str << " "
|
|
67
154
|
if arg.respond_to? :join
|
|
68
|
-
str << arg.join(
|
|
155
|
+
str << arg.join(".")
|
|
69
156
|
elsif arg.respond_to? :keys
|
|
70
|
-
arg.each do |k,v|
|
|
157
|
+
arg.each do |k, v|
|
|
71
158
|
str << "#{k}: #{v.inspect}"
|
|
72
159
|
end
|
|
73
160
|
else
|
|
@@ -98,34 +185,35 @@ module FlavourSaver
|
|
|
98
185
|
value :depth, Integer
|
|
99
186
|
|
|
100
187
|
def to_callnode
|
|
101
|
-
CallNode.new(name,arguments)
|
|
188
|
+
CallNode.new(name, arguments)
|
|
102
189
|
end
|
|
190
|
+
|
|
103
191
|
def to_s
|
|
104
|
-
"#{
|
|
192
|
+
"#{"../" * depth}#{super}"
|
|
105
193
|
end
|
|
106
194
|
end
|
|
107
195
|
|
|
108
196
|
class ExpressionNode < TemplateItemNode
|
|
109
197
|
child :method, [CallNode]
|
|
110
198
|
def to_s
|
|
111
|
-
"{{#{method.
|
|
199
|
+
"{{#{method.join "."}}}"
|
|
112
200
|
end
|
|
113
201
|
end
|
|
114
202
|
|
|
115
203
|
class BlockExpressionNode < ExpressionNode
|
|
116
204
|
child :contents, TemplateNode
|
|
117
|
-
child :closer,
|
|
205
|
+
child :closer, CallNode
|
|
118
206
|
|
|
119
207
|
def name
|
|
120
208
|
method.first.name
|
|
121
209
|
end
|
|
122
210
|
|
|
123
211
|
def to_s
|
|
124
|
-
"{{##{method.
|
|
212
|
+
"{{##{method.join ""}}}#{contents}{{/#{closer.name}}}"
|
|
125
213
|
end
|
|
126
214
|
|
|
127
215
|
def inspect
|
|
128
|
-
r = "{{##{method.
|
|
216
|
+
r = "{{##{method.join ""}}}\n"
|
|
129
217
|
r << " "
|
|
130
218
|
r << contents.inspect.split("\n").join("\n ")
|
|
131
219
|
r
|
|
@@ -136,11 +224,11 @@ module FlavourSaver
|
|
|
136
224
|
child :alternate, TemplateNode
|
|
137
225
|
|
|
138
226
|
def to_s
|
|
139
|
-
"{{##{method.
|
|
227
|
+
"{{##{method.join ""}}}#{contents}{{else}}#{alternate}{{/#{closer.name}}}"
|
|
140
228
|
end
|
|
141
229
|
|
|
142
230
|
def inspect
|
|
143
|
-
r = "{{##{method.
|
|
231
|
+
r = "{{##{method.join ""}}}\n"
|
|
144
232
|
r << contents.inspect.split("\n").join("\n ")
|
|
145
233
|
r << "\n {{else}}\n"
|
|
146
234
|
r << alternate.inspect.split("\n").join("\n ")
|
|
@@ -150,7 +238,7 @@ module FlavourSaver
|
|
|
150
238
|
|
|
151
239
|
class SafeExpressionNode < ExpressionNode
|
|
152
240
|
def to_s
|
|
153
|
-
"{{{#{method.
|
|
241
|
+
"{{{#{method.join "."}}}}"
|
|
154
242
|
end
|
|
155
243
|
end
|
|
156
244
|
|