cadenza 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = node.blocks.merge(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
- (blocks[node.name] || node).children.each {|x| render(x, context, blocks) }
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
@@ -2,8 +2,8 @@
2
2
  module Cadenza
3
3
  module Version
4
4
  MAJOR = 0
5
- MINOR = 7
6
- PATCH = 2
5
+ MINOR = 8
6
+ PATCH = 0
7
7
  BUILD = nil
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
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.7.2
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-10-29 00:00:00.000000000 Z
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