cadenza 0.7.2 → 0.8.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.
- 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
|