cadenza 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/cadenza +9 -1
- data/lib/cadenza.rb +15 -0
- data/lib/cadenza/base_renderer.rb +0 -8
- data/lib/cadenza/block_hierarchy.rb +42 -0
- data/lib/cadenza/cli.rb +2 -2
- data/lib/cadenza/context.rb +44 -313
- data/lib/cadenza/context/blocks.rb +60 -0
- data/lib/cadenza/context/filters.rb +60 -0
- data/lib/cadenza/context/functional_variables.rb +58 -0
- data/lib/cadenza/context/loaders.rb +116 -0
- data/lib/cadenza/context/stack.rb +85 -0
- data/lib/cadenza/context_object.rb +92 -2
- data/lib/cadenza/filters/standard_filters.rb +82 -23
- data/lib/cadenza/lexer.rb +0 -3
- data/lib/cadenza/nodes/filter_node.rb +1 -2
- data/lib/cadenza/nodes/filtered_value_node.rb +48 -0
- data/lib/cadenza/nodes/variable_node.rb +17 -4
- data/lib/cadenza/racc_parser.rb +329 -339
- data/lib/cadenza/source_renderer.rb +225 -0
- data/lib/cadenza/text_renderer.rb +25 -10
- data/lib/cadenza/version.rb +2 -2
- metadata +10 -3
- data/lib/cadenza/nodes/inject_node.rb +0 -57
@@ -0,0 +1,225 @@
|
|
1
|
+
|
2
|
+
module Cadenza
|
3
|
+
|
4
|
+
# SourceRenderer is a rendering implementation that turns a Cadenza AST back
|
5
|
+
# into Cadenza source code.
|
6
|
+
#
|
7
|
+
# This is mainly intended for users who wish to migrate templates stored in
|
8
|
+
# databases, or other storage devices, as their product progresses.
|
9
|
+
#
|
10
|
+
# For example: if in v1.0 of your app you define a filter X and deprecate it
|
11
|
+
# in favour of filter Y you can use this renderer to automatically replace
|
12
|
+
# instances of filter X with filter Y
|
13
|
+
#
|
14
|
+
# I'm sure there are many other exciting use cases for the imaginitive user,
|
15
|
+
# feel free to let me know what else you do with it!
|
16
|
+
#
|
17
|
+
class SourceRenderer < BaseRenderer
|
18
|
+
# This exception is raised when you try to transition to an undefined state
|
19
|
+
IllegalStateError = Class.new(RuntimeError)
|
20
|
+
|
21
|
+
# This exception is raised when you try to transition from one state to
|
22
|
+
# another which is not allowed
|
23
|
+
IllegalStateTransitionError = Class.new(RuntimeError)
|
24
|
+
|
25
|
+
# A list of all valid states for the renderer
|
26
|
+
ValidStates = [:text, :var, :tag]
|
27
|
+
|
28
|
+
# returns the current state of the renderer (see {#ValidStates})
|
29
|
+
attr_reader :state
|
30
|
+
|
31
|
+
# Renders the document given with the given context directly to a string
|
32
|
+
# returns.
|
33
|
+
# @param [DocumentNode] document_node the root of the AST you want to render.
|
34
|
+
# @param [Context] context the context object to render the document with
|
35
|
+
def self.render(document_node, context={})
|
36
|
+
io = StringIO.new
|
37
|
+
new(io).render(document_node, context)
|
38
|
+
io.string
|
39
|
+
end
|
40
|
+
|
41
|
+
# creates a new {SourceRenderer} and places it into the :text state
|
42
|
+
def initialize(*args)
|
43
|
+
@state = :text
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
# transitions from the current state into the new state and emits opening
|
48
|
+
# and closing tag markers appropriately during the transition.
|
49
|
+
#
|
50
|
+
# @raise [IllegalStateError] if you try to transition to an invalid state
|
51
|
+
# @raise [IllegalStateTransitionError] if you try to transition from one
|
52
|
+
# one state to another which is not allowed
|
53
|
+
def state=(new_state)
|
54
|
+
# if trying to transition to a new state raise an exception
|
55
|
+
raise IllegalStateError.new(new_state) unless ValidStates.include?(new_state)
|
56
|
+
|
57
|
+
# no special transition for the same state
|
58
|
+
return if @state == new_state
|
59
|
+
|
60
|
+
# handle any actions that occur on that state transition
|
61
|
+
case @state
|
62
|
+
when :text
|
63
|
+
output << "{{ " if new_state == :var
|
64
|
+
output << "{% " if new_state == :tag
|
65
|
+
when :var
|
66
|
+
output << " }}" if new_state == :text
|
67
|
+
raise IllegalStateTransitionError if new_state == :tag
|
68
|
+
when :tag
|
69
|
+
output << " %}" if new_state == :text
|
70
|
+
raise IllegalStateTransitionError if new_state == :var
|
71
|
+
end
|
72
|
+
|
73
|
+
# update to the new state
|
74
|
+
@state = new_state
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def render_document(node, context, blocks)
|
79
|
+
output << %Q[{% extends "#{node.extends}" %}] if node.extends
|
80
|
+
|
81
|
+
node.children.each {|child| render(child, context, blocks) }
|
82
|
+
|
83
|
+
self.state = :text
|
84
|
+
end
|
85
|
+
|
86
|
+
def render_text(node, context, blocks)
|
87
|
+
self.state = :text
|
88
|
+
output << node.text
|
89
|
+
end
|
90
|
+
|
91
|
+
def render_constant(node, context, blocks)
|
92
|
+
self.state = :var unless self.state == :tag
|
93
|
+
if node.value.is_a?(String)
|
94
|
+
output << node.value.inspect
|
95
|
+
else
|
96
|
+
output << node.value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def render_variable(node, context, blocks)
|
101
|
+
self.state = :var unless self.state == :tag
|
102
|
+
|
103
|
+
output << node.identifier
|
104
|
+
|
105
|
+
node.parameters.each_with_index do |param_node, i|
|
106
|
+
output << " "
|
107
|
+
render(param_node, context, blocks)
|
108
|
+
output << "," if i < node.parameters.length - 1
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def render_filtered_value(node, context, blocks)
|
113
|
+
self.state == :var unless self.state == :tag
|
114
|
+
|
115
|
+
render(node.value, context, blocks)
|
116
|
+
|
117
|
+
node.filters.each do |filter|
|
118
|
+
output << " | #{filter.identifier}"
|
119
|
+
|
120
|
+
output << ": " if filter.parameters.any?
|
121
|
+
|
122
|
+
filter.parameters.each_with_index do |param, i|
|
123
|
+
render(param, context, blocks)
|
124
|
+
|
125
|
+
output << ", " if i < filter.parameters.length - 1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def render_operation(node, context, blocks)
|
131
|
+
self.state = :var unless self.state == :tag
|
132
|
+
|
133
|
+
# calculate the operator precedence of the left, right and parent node
|
134
|
+
node_precedence = calculate_precedence(node)
|
135
|
+
left_precedence = calculate_precedence(node.left)
|
136
|
+
right_precedence = calculate_precedence(node.right)
|
137
|
+
|
138
|
+
need_left_brackets = left_precedence < node_precedence
|
139
|
+
need_right_brackets = right_precedence <= node_precedence && !(node.right.is_a?(OperationNode) && node.right.operator == node.operator)
|
140
|
+
|
141
|
+
# render the left node, wrapping in brackets if it is lower precedence
|
142
|
+
output << "(" if need_left_brackets
|
143
|
+
render(node.left, context, blocks)
|
144
|
+
output << ")" if need_left_brackets
|
145
|
+
|
146
|
+
# render the parent node's operator
|
147
|
+
output << " #{node.operator} "
|
148
|
+
|
149
|
+
# render the right node, wrapping it brackets if it is lower precedences
|
150
|
+
output << "(" if need_right_brackets
|
151
|
+
render(node.right, context, blocks)
|
152
|
+
output << ")" if need_right_brackets
|
153
|
+
end
|
154
|
+
|
155
|
+
def render_if(node, context, blocks)
|
156
|
+
self.state = :tag
|
157
|
+
|
158
|
+
output << "if "
|
159
|
+
|
160
|
+
render(node.expression, context, blocks)
|
161
|
+
|
162
|
+
self.state = :text
|
163
|
+
|
164
|
+
node.true_children.each {|n| render(n, context, blocks) }
|
165
|
+
|
166
|
+
output << "{% endif %}"
|
167
|
+
end
|
168
|
+
|
169
|
+
def render_for(node, context, blocks)
|
170
|
+
self.state = :tag
|
171
|
+
|
172
|
+
output << "for #{node.iterator.identifier} in "
|
173
|
+
|
174
|
+
render(node.iterable, context, blocks)
|
175
|
+
|
176
|
+
self.state = :text
|
177
|
+
|
178
|
+
node.children.each {|n| render(n, context, blocks) }
|
179
|
+
|
180
|
+
output << "{% endfor %}"
|
181
|
+
end
|
182
|
+
|
183
|
+
def render_block(node, context, blocks)
|
184
|
+
self.state = :tag
|
185
|
+
|
186
|
+
output << "block #{node.name}"
|
187
|
+
|
188
|
+
self.state = :text
|
189
|
+
|
190
|
+
node.children.each {|n| render(n, context, blocks) }
|
191
|
+
|
192
|
+
output << "{% endblock %}"
|
193
|
+
end
|
194
|
+
|
195
|
+
def render_generic_block(node, context, blocks)
|
196
|
+
self.state = :tag
|
197
|
+
|
198
|
+
output << node.identifier
|
199
|
+
|
200
|
+
output << " " if node.parameters.any?
|
201
|
+
|
202
|
+
node.parameters.each_with_index do |param, i|
|
203
|
+
render(param, context, blocks)
|
204
|
+
|
205
|
+
output << ", " if i < node.parameters.length - 1
|
206
|
+
end
|
207
|
+
|
208
|
+
self.state = :text
|
209
|
+
|
210
|
+
node.children.each {|n| render(n, context, blocks) }
|
211
|
+
|
212
|
+
output << "{% end %}"
|
213
|
+
end
|
214
|
+
|
215
|
+
def calculate_precedence(node)
|
216
|
+
return 4 unless node.is_a?(OperationNode)
|
217
|
+
|
218
|
+
case node.operator
|
219
|
+
when '+', '-' then 2
|
220
|
+
when '*', '/' then 3
|
221
|
+
else 1
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -16,19 +16,23 @@ module Cadenza
|
|
16
16
|
io.string
|
17
17
|
end
|
18
18
|
|
19
|
+
def render(node, context, blocks={})
|
20
|
+
passed_blocks = blocks.is_a?(BlockHierarchy) ? blocks : BlockHierarchy.new(blocks)
|
21
|
+
|
22
|
+
super(node, context, passed_blocks)
|
23
|
+
end
|
24
|
+
|
19
25
|
private
|
20
26
|
|
21
27
|
def render_document(node, context, blocks)
|
22
28
|
if node.extends
|
23
29
|
# merge the inherited blocks onto this document's blocks to
|
24
30
|
# determine what to pass to the layout template
|
25
|
-
blocks
|
31
|
+
blocks.merge(node.blocks)
|
26
32
|
|
27
33
|
# load the template of the document and render it to the same output stream
|
28
34
|
template = context.load_template!(node.extends)
|
29
35
|
|
30
|
-
document = template
|
31
|
-
|
32
36
|
render(template, context, blocks)
|
33
37
|
else
|
34
38
|
node.children.each {|x| render(x, context, blocks) }
|
@@ -36,17 +40,29 @@ module Cadenza
|
|
36
40
|
end
|
37
41
|
|
38
42
|
def render_block(node, context, blocks)
|
39
|
-
|
43
|
+
# create the full inheritance chain with this node on top, making sure
|
44
|
+
# not to mutate the block hierarchy's internals
|
45
|
+
chain = blocks[node.name].dup << node
|
46
|
+
|
47
|
+
super_fn = lambda do |params|
|
48
|
+
parent_node = chain.shift
|
49
|
+
|
50
|
+
parent_node.children.each {|x| render(x, context, blocks) } if parent_node
|
51
|
+
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
context.push('super' => super_fn)
|
56
|
+
|
57
|
+
chain.shift.children.each {|x| render(x, context, blocks) }
|
58
|
+
|
59
|
+
context.pop
|
40
60
|
end
|
41
61
|
|
42
62
|
def render_text(node, context, blocks)
|
43
63
|
output << node.text
|
44
64
|
end
|
45
65
|
|
46
|
-
def render_inject(node, context, blocks)
|
47
|
-
output << node.evaluate(context).to_s
|
48
|
-
end
|
49
|
-
|
50
66
|
def render_if(node, context, blocks)
|
51
67
|
node.evaluate_expression_for_children(context).each {|x| render(x, context, blocks) }
|
52
68
|
end
|
@@ -86,8 +102,6 @@ module Cadenza
|
|
86
102
|
output << context.evaluate_block(node.identifier, node.children, node.parameters)
|
87
103
|
end
|
88
104
|
|
89
|
-
# none of these should appear directly inside the body of the
|
90
|
-
# document but for safety we will render them anyways
|
91
105
|
def render_constant(node, context, blocks)
|
92
106
|
output << node.eval(context).to_s
|
93
107
|
end
|
@@ -95,6 +109,7 @@ module Cadenza
|
|
95
109
|
alias :render_variable :render_constant
|
96
110
|
alias :render_operation :render_constant
|
97
111
|
alias :render_boolean_inverse :render_constant
|
112
|
+
alias :render_filtered_value :render_constant
|
98
113
|
|
99
114
|
end
|
100
115
|
end
|
data/lib/cadenza/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cadenza
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-12-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -152,6 +152,12 @@ files:
|
|
152
152
|
- lib/cadenza/filesystem_loader.rb
|
153
153
|
- lib/cadenza/cli.rb
|
154
154
|
- lib/cadenza/blocks/standard_blocks.rb
|
155
|
+
- lib/cadenza/context/blocks.rb
|
156
|
+
- lib/cadenza/context/loaders.rb
|
157
|
+
- lib/cadenza/context/functional_variables.rb
|
158
|
+
- lib/cadenza/context/stack.rb
|
159
|
+
- lib/cadenza/context/filters.rb
|
160
|
+
- lib/cadenza/block_hierarchy.rb
|
155
161
|
- lib/cadenza/context_object.rb
|
156
162
|
- lib/cadenza/filters/standard_filters.rb
|
157
163
|
- lib/cadenza/error.rb
|
@@ -161,8 +167,8 @@ files:
|
|
161
167
|
- lib/cadenza/nodes/for_node.rb
|
162
168
|
- lib/cadenza/nodes/if_node.rb
|
163
169
|
- lib/cadenza/nodes/generic_block_node.rb
|
164
|
-
- lib/cadenza/nodes/inject_node.rb
|
165
170
|
- lib/cadenza/nodes/variable_node.rb
|
171
|
+
- lib/cadenza/nodes/filtered_value_node.rb
|
166
172
|
- lib/cadenza/nodes/constant_node.rb
|
167
173
|
- lib/cadenza/nodes/block_node.rb
|
168
174
|
- lib/cadenza/nodes/boolean_inverse_node.rb
|
@@ -171,6 +177,7 @@ files:
|
|
171
177
|
- lib/cadenza/version.rb
|
172
178
|
- lib/cadenza/text_renderer.rb
|
173
179
|
- lib/cadenza/cli/options.rb
|
180
|
+
- lib/cadenza/source_renderer.rb
|
174
181
|
- lib/cadenza/parser.rb
|
175
182
|
- lib/cadenza/base_renderer.rb
|
176
183
|
- lib/cadenza/token.rb
|
@@ -1,57 +0,0 @@
|
|
1
|
-
module Cadenza
|
2
|
-
# The {InjectNode} is intended to write the given variable into the rendered
|
3
|
-
# output by evaluating it in the given {Context} and passing it through an
|
4
|
-
# optional series of {FilterNode}s.
|
5
|
-
class InjectNode
|
6
|
-
# @return [VariableNode|OperationNode|BooleanInverseNode|ConstantNode] the value being evaluated
|
7
|
-
attr_accessor :value
|
8
|
-
|
9
|
-
# @return [Array] a list of {FilterNode} to evaluate the value with, once the
|
10
|
-
# value has itself been evaluated.
|
11
|
-
attr_accessor :filters
|
12
|
-
|
13
|
-
# @return [Array] a list of Node objects passed to the {#value} for use in a
|
14
|
-
# functional variable. See {Context#define_functional_variable}.
|
15
|
-
attr_accessor :parameters
|
16
|
-
|
17
|
-
# creates a new {InjectNode} with the given value, filters and parameters
|
18
|
-
# @param [VariableNode|OperationNode|BooleanInverseNode|ConstantNode] value see {#value}
|
19
|
-
# @param [Array] filters see {#filters}
|
20
|
-
# @param [Array] parameters see {#parameters}
|
21
|
-
def initialize(value, filters=[], parameters=[])
|
22
|
-
@value = value
|
23
|
-
@filters = filters
|
24
|
-
@parameters = parameters
|
25
|
-
end
|
26
|
-
|
27
|
-
# @param [InjectNode] rhs
|
28
|
-
# @return [Boolean] true if the given InjectNode is equivalent by value to this node.
|
29
|
-
def ==(rhs)
|
30
|
-
self.value == rhs.value and
|
31
|
-
self.filters == rhs.filters and
|
32
|
-
self.parameters == rhs.parameters
|
33
|
-
end
|
34
|
-
|
35
|
-
# @return [Array] a list of variable names implied to be global by this node
|
36
|
-
def implied_globals
|
37
|
-
(@value.implied_globals + @filters.map(&:implied_globals).flatten).uniq
|
38
|
-
end
|
39
|
-
|
40
|
-
# @param [Context] context
|
41
|
-
# @return [String] returns the evaluated {#value} of this node in the given
|
42
|
-
# {Context} with any applicable {#parameters} after passed through
|
43
|
-
# the given {#filters}.
|
44
|
-
def evaluate(context)
|
45
|
-
value = @value.eval(context)
|
46
|
-
|
47
|
-
if value.is_a? Proc
|
48
|
-
args = parameters.map {|p| p.eval(context) }
|
49
|
-
value = value.call(context, *args)
|
50
|
-
end
|
51
|
-
|
52
|
-
@filters.each {|filter| value = filter.evaluate(context, value) }
|
53
|
-
|
54
|
-
value
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|