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.
@@ -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