opulent 0.0.9 → 1.0.2

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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.libold/opulent.rb +96 -0
  4. data/.libold/opulent/context.rb +80 -0
  5. data/.libold/opulent/engine.rb +88 -0
  6. data/.libold/opulent/filter.rb +101 -0
  7. data/.libold/opulent/logger.rb +67 -0
  8. data/.libold/opulent/nodes.rb +13 -0
  9. data/.libold/opulent/nodes/block.rb +29 -0
  10. data/.libold/opulent/nodes/comment.rb +35 -0
  11. data/.libold/opulent/nodes/control.rb +230 -0
  12. data/.libold/opulent/nodes/define.rb +42 -0
  13. data/.libold/opulent/nodes/eval.rb +41 -0
  14. data/.libold/opulent/nodes/expression.rb +28 -0
  15. data/.libold/opulent/nodes/filter.rb +70 -0
  16. data/.libold/opulent/nodes/helper.rb +69 -0
  17. data/.libold/opulent/nodes/node.rb +101 -0
  18. data/.libold/opulent/nodes/root.rb +62 -0
  19. data/.libold/opulent/nodes/text.rb +54 -0
  20. data/.libold/opulent/nodes/theme.rb +36 -0
  21. data/.libold/opulent/parser.rb +252 -0
  22. data/.libold/opulent/parser/block.rb +70 -0
  23. data/.libold/opulent/parser/comment.rb +32 -0
  24. data/.libold/opulent/parser/control.rb +83 -0
  25. data/.libold/opulent/parser/define.rb +39 -0
  26. data/.libold/opulent/parser/eval.rb +33 -0
  27. data/.libold/opulent/parser/expression.rb +350 -0
  28. data/.libold/opulent/parser/filter.rb +41 -0
  29. data/.libold/opulent/parser/node.rb +232 -0
  30. data/.libold/opulent/parser/root.rb +96 -0
  31. data/.libold/opulent/parser/text.rb +114 -0
  32. data/.libold/opulent/parser/theme.rb +36 -0
  33. data/.libold/opulent/preprocessor.rb +102 -0
  34. data/.libold/opulent/runtime.rb +144 -0
  35. data/.libold/opulent/template.rb +43 -0
  36. data/.libold/opulent/tokens.rb +276 -0
  37. data/.libold/opulent/version.rb +5 -0
  38. data/.rspec +2 -0
  39. data/.travis.yml +4 -0
  40. data/Gemfile +2 -0
  41. data/LICENSE +21 -0
  42. data/README.md +102 -0
  43. data/Rakefile +6 -0
  44. data/benchmark/benchmark.rb +46 -0
  45. data/benchmark/cases/node/node.haml +17 -0
  46. data/benchmark/cases/node/node.op +14 -0
  47. data/benchmark/cases/node/node.slim +19 -0
  48. data/bin/console +14 -0
  49. data/bin/setup +7 -0
  50. data/docs/syntax.md +3 -0
  51. data/docs/usage.md +3 -0
  52. data/lib/opulent.rb +11 -64
  53. data/lib/opulent/compiler.rb +132 -0
  54. data/lib/opulent/compiler/block.rb +31 -0
  55. data/lib/opulent/compiler/comment.rb +22 -0
  56. data/lib/opulent/compiler/control.rb +152 -0
  57. data/lib/opulent/compiler/define.rb +88 -0
  58. data/lib/opulent/compiler/eval.rb +15 -0
  59. data/lib/opulent/compiler/filter.rb +54 -0
  60. data/lib/opulent/compiler/node.rb +232 -0
  61. data/lib/opulent/compiler/root.rb +19 -0
  62. data/lib/opulent/compiler/text.rb +60 -0
  63. data/lib/opulent/context.rb +88 -0
  64. data/lib/opulent/engine.rb +60 -0
  65. data/lib/opulent/filters.rb +222 -0
  66. data/lib/opulent/logger.rb +47 -0
  67. data/lib/opulent/parser.rb +196 -0
  68. data/lib/opulent/parser/block.rb +56 -0
  69. data/lib/opulent/parser/comment.rb +27 -0
  70. data/lib/opulent/parser/control.rb +112 -0
  71. data/lib/opulent/parser/define.rb +30 -0
  72. data/lib/opulent/parser/eval.rb +21 -0
  73. data/lib/opulent/parser/expression.rb +344 -0
  74. data/lib/opulent/parser/filter.rb +25 -0
  75. data/lib/opulent/parser/node.rb +246 -0
  76. data/lib/opulent/parser/root.rb +48 -0
  77. data/lib/opulent/parser/text.rb +127 -0
  78. data/lib/opulent/settings.rb +79 -0
  79. data/lib/opulent/template.rb +67 -0
  80. data/lib/opulent/tokens.rb +166 -0
  81. data/lib/opulent/version.rb +4 -0
  82. data/opulent.gemspec +36 -0
  83. metadata +160 -7
@@ -0,0 +1,31 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Compiler
4
+ class Compiler
5
+ # Generate the code for a while control structure
6
+ #
7
+ # @param node [Array] Node code generation data
8
+ # @param indent [Fixnum] Size of the indentation to be added
9
+ # @param context [Context] Processing environment data
10
+ #
11
+ def yield_node(node, indent, context)
12
+ if @block_stack[-1].has_key? node[@value]
13
+ @block_stack[-1][node[@value]].each do |child|
14
+ root child, indent, context.parent
15
+ end
16
+ end
17
+ end
18
+
19
+ # Generate the code for a while control structure
20
+ #
21
+ # @param node [Array] Node code generation data
22
+ # @param indent [Fixnum] Size of the indentation to be added
23
+ # @param context [Context] Processing environment data
24
+ #
25
+ def block_node(node, indent, context)
26
+ node[@children].each do |child|
27
+ root child, indent, context
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Compiler
4
+ class Compiler
5
+ # Generate the code for a while control structure
6
+ #
7
+ # @param node [Array] Node code generation data
8
+ # @param indent [Fixnum] Size of the indentation to be added
9
+ # @param context [Context] Processing environment data
10
+ #
11
+ def comment(node, indent, context)
12
+ indentation = " " * indent
13
+
14
+ value = context.evaluate "\"#{node[@value]}\""
15
+
16
+ comment_tag = "#{indentation}<!-- #{value} -->\n"
17
+
18
+ @node_stack << :comment
19
+ @code += comment_tag
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,152 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Compiler
4
+ class Compiler
5
+ # Generate the code for a if-elsif-else control structure
6
+ #
7
+ # @param node [Array] Node code generation data
8
+ # @param indent [Fixnum] Size of the indentation to be added
9
+ # @param context [Context] Processing environment data
10
+ #
11
+ def if_node(node, indent, context)
12
+ # Check if we have any condition met, or an else branch
13
+ index = node[@value].index do |value|
14
+ value.empty? || context.evaluate(value)
15
+ end
16
+
17
+ # If we have a branch that meets the condition, generate code for the
18
+ # children related to that specific branch
19
+ if index
20
+ node[@children][index].each do |child|
21
+ root child, indent, context
22
+ end
23
+ end
24
+ end
25
+
26
+ # Generate the code for a case-when-else control structure
27
+ #
28
+ # @param node [Array] Node code generation data
29
+ # @param indent [Fixnum] Size of the indentation to be added
30
+ # @param context [Context] Processing environment data
31
+ #
32
+ def case_node(node, indent, context)
33
+ # Evaluate the switching condition
34
+ switch_case = context.evaluate node[@options][:condition]
35
+
36
+ # Check if we have any condition met, or an else branch
37
+ index = node[@value].index do |value|
38
+ value.empty? || switch_case == context.evaluate(value)
39
+ end
40
+
41
+ # If we have a branch that meets the condition, generate code for the
42
+ # children related to that specific branch
43
+ if index
44
+ node[@children][index].each do |child|
45
+ root child, indent, context
46
+ end
47
+ end
48
+ end
49
+
50
+ # Generate the code for a while control structure
51
+ #
52
+ # @param node [Array] Node code generation data
53
+ # @param indent [Fixnum] Size of the indentation to be added
54
+ # @param context [Context] Processing environment data
55
+ #
56
+ def while_node(node, indent, context)
57
+ # While we have a branch that meets the condition, generate code for the
58
+ # children related to that specific branch
59
+ while context.evaluate node[@value]
60
+ node[@children].each do |child|
61
+ root child, indent, context
62
+ end
63
+ end
64
+ end
65
+
66
+ # Generate the code for a while control structure
67
+ #
68
+ # @param node [Array] Node code generation data
69
+ # @param indent [Fixnum] Size of the indentation to be added
70
+ # @param context [Context] Processing environment data
71
+ #
72
+ def until_node(node, indent, context)
73
+ # Until we have a branch that doesn't meet the condition, generate code for the
74
+ # children related to that specific branch
75
+ until context.evaluate node[@value]
76
+ node[@children].each do |child|
77
+ root child, indent, context
78
+ end
79
+ end
80
+ end
81
+
82
+ # Generate the code for a while control structure
83
+ #
84
+ # @param node [Array] Node code generation data
85
+ # @param indent [Fixnum] Size of the indentation to be added
86
+ # @param context [Context] Processing environment data
87
+ #
88
+ def each_node(node, indent, context)
89
+ result = []
90
+
91
+ # Process named variables for each structure
92
+ variables = node[@value][0].clone
93
+
94
+ # The each structure accept missing arguments as well, therefore we need to
95
+ # substitute them with our defaults
96
+ #
97
+ # each in iterable
98
+ # each value in iterable
99
+ # each key, value in iterable
100
+
101
+ # Value argument name provided only
102
+ if variables.length == 1
103
+ variables.unshift Settings::DefaultEachKey
104
+
105
+ # Missing key and value arguments
106
+ elsif variables.empty?
107
+ variables[0] = Settings::DefaultEachKey
108
+ variables[1] = Settings::DefaultEachValue
109
+ end
110
+
111
+ # Evaluate in current context and add to results
112
+ evaluate_children = Proc.new do |key, value, context|
113
+ # Update the local variables in the each context with the values from the
114
+ # current loop iteration
115
+ locals = {
116
+ variables[0] => key,
117
+ variables[1] => value
118
+ }
119
+ context.extend_locals locals
120
+
121
+ # Add the mapped child elements
122
+ node[@children].each do |child|
123
+ root child, indent, context
124
+ end
125
+ end
126
+
127
+ # Create a new context based on the parent context and progressively update
128
+ # variables in the new context
129
+ each_context = Context.new Hash.new, context.binding.clone
130
+ each_context.parent = context
131
+
132
+ # Evaluate the iterable object
133
+ enumerable = each_context.evaluate(node[@value][1])
134
+
135
+ # Check if input can be iterated
136
+ error :enumerable, node[@value][1] unless enumerable.respond_to? :each
137
+
138
+ # Selectively iterate through the input and add the result using the previously
139
+ # defined proc object
140
+ case enumerable
141
+ when Hash
142
+ enumerable.each do |key, value|
143
+ evaluate_children[key, value, context]
144
+ end
145
+ else
146
+ enumerable.each_with_index do |value, key|
147
+ evaluate_children[key, value, context]
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,88 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Compiler
4
+ class Compiler
5
+ # Generate code for all nodes by calling the method with their type name
6
+ #
7
+ # @param current [Array] Current node data with options
8
+ # @param indent [Fixnum] Indentation size for current node
9
+ # @param context [Context] Context holding environment variables
10
+ #
11
+ def def_node(node, indent, context)
12
+ # Create a new definition context
13
+ definition_context = Context.new
14
+ definition_context.extend_nonlocals context.binding
15
+ definition_context.name = node[@value]
16
+ definition_context.parent = context
17
+
18
+ # Set call node
19
+ call_node = node[@options][:call]
20
+
21
+ # Get call node attributes
22
+ attributes = call_node[@options][:attributes]
23
+
24
+ # Evaluate node extension in the current context
25
+ if call_node[@options][:extension]
26
+ extension = context.evaluate call_node[@options][:extension][@value]
27
+ else
28
+ extension = {}
29
+ end
30
+
31
+ # Evaluate and generate node attributes, then process each one to
32
+ # by generating the required attribute code
33
+ attributes = {}
34
+ call_node[@options][:attributes].each do |key, attribute|
35
+ attributes[key] = map_attribute key, attribute, context
36
+ end
37
+
38
+ # Go through each extension attribute and use the value where applicable
39
+ extend_attributes attributes, extension
40
+
41
+ # Definition call arguments
42
+ arguments = {}
43
+
44
+ # Extract values which appear as definition parameters. If we have the
45
+ # key passed as argument, get its value. Otherwise, set the default
46
+ # parameter value
47
+ node[@options][:parameters].each do |key, value|
48
+ if attributes[key]
49
+ arguments[key] = attributes.delete key
50
+ else
51
+ arguments[key] = definition_context.evaluate value[@value]
52
+ end
53
+ end
54
+
55
+ # Set the remaining attributes as a value in the arguments
56
+ arguments[:attributes] = attributes
57
+
58
+ # Add call children to the block stack, depending on whether they're
59
+ # block elements or child elements
60
+ @block_stack << { @default_yield => [] }
61
+
62
+ # If we have a direct child, add it to the default yield (children)
63
+ # block and allow same block multiple times by appending nodes
64
+ call_node[@children].each do |child|
65
+ if child[@type] == :block
66
+ @block_stack[-1][child[@value]] ||= []
67
+ @block_stack[-1][child[@value]] += child[@children]
68
+ else
69
+ @block_stack[-1][@default_yield] << child
70
+ end
71
+ end
72
+
73
+ # Set variable to determine available blocks
74
+ #
75
+ arguments[:blocks] = Hash[@block_stack[-1].keys.map{|key| [key, true]}]
76
+
77
+ # Create local variables from argument variables
78
+ definition_context.extend_locals arguments
79
+
80
+ # Evaluate the model using the new context
81
+ node[@children].each do |child|
82
+ root child, indent, definition_context
83
+ end
84
+
85
+ @block_stack.pop
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,15 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Compiler
4
+ class Compiler
5
+ # Evaluate the embedded ruby code using the current context
6
+ #
7
+ # @param node [Array] Node code generation data
8
+ # @param indent [Fixnum] Size of the indentation to be added
9
+ # @param context [Context] Processing environment data
10
+ #
11
+ def evaluate(node, indent, context)
12
+ context.evaluate node[@value]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,54 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Compiler
4
+ class Compiler
5
+ # Generate the code for a while control structure
6
+ #
7
+ # @param node [Array] Node code generation data
8
+ # @param indent [Fixnum] Size of the indentation to be added
9
+ # @param context [Context] Processing environment data
10
+ #
11
+ def filter(node, indent, context)
12
+ # Evaluate and generate node attributes, then process each one to
13
+ # by generating the required attribute code
14
+ attributes = {}
15
+ node[@options].each do |key, attribute|
16
+ attributes[key] = map_attribute key, attribute, context
17
+ end
18
+
19
+ # Get registered filter name
20
+ name = node[@value]
21
+
22
+ # Check if filter is registered
23
+ error :filter_registered, name unless Filters.filters.has_key? name
24
+
25
+ # Load the required filter
26
+ Filters.filters[name].load_filter
27
+
28
+ # Render output using the chosen engine
29
+ output = Filters.filters[name].render node[@children]
30
+
31
+ # Main output node which contains filter rendered value
32
+ text_node = [:plain, :text, {value: output.rstrip, escaped: false, evaluate: false}, [], nil]
33
+
34
+ # If we have a provided filter tag, wrap the text node in the wrapper
35
+ # node tag and further indent
36
+ if (wrapper_tag = Filters.filters[name].options[:tag])
37
+ # Set wrapper tag attributes as evaluable expressions
38
+ atts = {}
39
+ Filters.filters[name].options[:attributes].each do |key, value|
40
+ atts[key] = [:expression, value.inspect, {evaluate: false, escaped: false}]
41
+ end
42
+
43
+ # Create the wrapper node containing the output text node as a child
44
+ wrapper_node = [:node, wrapper_tag, {attributes: atts}, [text_node], indent]
45
+
46
+ # Begin code generation from the wrapper node
47
+ root wrapper_node, indent, context
48
+ else
49
+ # Generate code for output text node
50
+ root text_node, indent, context
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,232 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Compiler
4
+ class Compiler
5
+ # Generate the code for a standard node element, with closing tags or
6
+ # self enclosing elements
7
+ #
8
+ # @param node [Array] Node code generation data
9
+ # @param indent [Fixnum] Size of the indentation to be added
10
+ # @param context [Context] Processing environment data
11
+ #
12
+ def node(node, indent, context)
13
+ indentation = " " * indent
14
+
15
+ # Check if the current node and last node should be displayed inline
16
+ inline_current = @inline_node.include? node[@value]
17
+ inline_last = @inline_node.include? @node_stack.last
18
+
19
+ # Check if the node is a special node which can be either inline or
20
+ # block structure. Write the special node as inline if its children
21
+ # are all inline nodes
22
+ if @multi_node.include?(node[@value])
23
+ # First condition should be removed to ignore preceding node and make
24
+ # it be inline no matter what. Using the first check, we write it
25
+ # inline only if the element before it was inline
26
+ unless @sibling_stack.last > 1 && node[@children].all? do |child|
27
+ @inline_node.include?(child[@value])
28
+ end
29
+ inline_current = false
30
+ multi = true
31
+ end
32
+ end
33
+
34
+ # If we have an inline node, we remove the trailing newline character
35
+ # and write the tag code directly. Otherwise we add the tag code with
36
+ # normal indentation
37
+ if inline_last && inline_current
38
+ remove_trailing_newline
39
+ else
40
+ @code += indentation
41
+ end
42
+
43
+ # Add the tag opening, with leading whitespace to the code buffer
44
+ tag_open = "<#{node[@value]}"
45
+ @code += " " if node[@options][:leading_whitespace]
46
+ @code += tag_open
47
+
48
+ # Evaluate node extension in the current context
49
+ if node[@options][:extension]
50
+ extension = context.evaluate node[@options][:extension][@value]
51
+ else
52
+ extension = {}
53
+ end
54
+
55
+ # Evaluate and generate node attributes, then process each one to
56
+ # by generating the required attribute code
57
+ attributes = {}
58
+ node[@options][:attributes].each do |key, attribute|
59
+ attributes[key] = map_attribute key, attribute, context
60
+ end
61
+
62
+ # Go through each extension attribute and use the value where applicable
63
+ extend_attributes attributes, extension
64
+
65
+ # Join arrays, create new attributes by hash and set the
66
+ # value otherwise
67
+ attributes.each do |key, value|
68
+ @code += attribute_code key, value
69
+ end
70
+
71
+ # Set the current node as a parent for the node elements to follow
72
+ @node_stack << (multi ? :multi : node[@value])
73
+
74
+ # Check if the current node is self enclosing. Self enclosing nodes
75
+ # do not have any child elements
76
+ if node[@options][:self_enclosing]
77
+ # If the tag is self enclosing, it cannot have any child elements.
78
+ tag_close = ">"
79
+ tag_close += "\n"
80
+
81
+ @code += tag_close
82
+ else
83
+ # Set tag ending code
84
+ tag_end = ">"
85
+
86
+ # If the node is an inline node and doesn't have any child elements,
87
+ # we close it on the same line, without adding indentation
88
+ tag_end += "\n" unless inline_current || node[@children].empty?
89
+
90
+ # Set tag closing code
91
+ tag_close = "</#{node[@value]}>"
92
+ tag_close += " " if node[@options][:trailing_whitespace]
93
+ tag_close += "\n"
94
+
95
+ # Add tag ending to the buffer
96
+ @code += tag_end
97
+
98
+ # Get number of siblings
99
+ @sibling_stack << node[@children].size
100
+
101
+ # Process each child element recursively, increasing indentation
102
+ node[@children].each do |child|
103
+ root child, indent + Settings[:indent], context
104
+ end
105
+
106
+ # Remove the current node children count from the sibling stack
107
+ @sibling_stack.pop
108
+
109
+ # Remove all child nodes of the current node from the node stack
110
+ @node_stack.pop(node[@children].size)
111
+
112
+ # If we have an inline node, we remove the trailing newline from
113
+ # our buffer, otherwise add indentation
114
+ if inline_current
115
+ remove_trailing_newline
116
+
117
+ # If the node doesn't have any child elements, we close it on the same
118
+ # line, without adding indentation
119
+ elsif node[@children].any?
120
+ @code += indentation
121
+ end
122
+
123
+ # Close the current tag
124
+ @code += tag_close
125
+ end
126
+ end
127
+
128
+ # Map attributes by evaluating them in the current working context
129
+ #
130
+ # @param key [Symbol] Name of the attribute being processed
131
+ # @param attribute [Array] Attribute instance data
132
+ # @param context [Context] Processing environment data
133
+ #
134
+ def map_attribute(key, attribute, context)
135
+ # Process input value depending on its type. When array or hash, iterate
136
+ # and escape each string value.
137
+ process = Proc.new do |value|
138
+ case value
139
+ when Array
140
+ value.map do |v|
141
+ v.is_a?(String) ? escape(v) : v
142
+ end
143
+ when Hash
144
+ value.each do |k,v|
145
+ value[k] = value[k].is_a?(String) ? escape(v) : v
146
+ end
147
+ when String
148
+ escape value
149
+ else
150
+ value
151
+ end
152
+ end
153
+
154
+ # Process each attribute depending on whether it's an array of values,
155
+ # exclusive to the class attribute, or an individual attribute value
156
+ if key == :class
157
+ attribute.map do |attrib|
158
+ value = context.evaluate attrib[@value]
159
+ attrib[@options][:escaped] ? process[value] : value
160
+ end
161
+ else
162
+ value = context.evaluate attribute[@value]
163
+ attribute[@options][:escaped] ? process[value] : value
164
+ end
165
+ end
166
+
167
+ # Extend attributes using the extension directive where applicable.
168
+ # Concatenate arrays, merge hashes and replace otherwise
169
+ #
170
+ # @param attributes [Hash] Evaluated node attributes
171
+ # @param extension [Hash] Node extension input
172
+ #
173
+ def extend_attributes(attributes, extension)
174
+ extension.each do |key, value|
175
+ case attributes[key]
176
+ when Array
177
+ if key == :class
178
+ attributes[key] << value
179
+ attributes[key].flatten!
180
+ else
181
+ attributes[key] = value
182
+ end
183
+ when Hash
184
+ if value.is_a? Hash
185
+ attributes[key].merge! value
186
+ else
187
+ attributes[key] = value
188
+ end
189
+ else
190
+ attributes[key] = value
191
+ end
192
+ end
193
+ end
194
+
195
+ # Generate attribute code for the current key value pair. For string
196
+ # values, generate a key value pair. For false values, remove the
197
+ # attribute. For true values, generate a standalone attribute key
198
+ #
199
+ # @param key [Symbol] Name of the attribute being generated
200
+ # @param value [Object] Value of the attribute
201
+ #
202
+ def attribute_code(key, value)
203
+ attribute_code = ""
204
+
205
+ case value
206
+ when Array
207
+ if key == :class
208
+ attribute_value = value.join ' '
209
+ else
210
+ attribute_value = value.join '_'
211
+ end
212
+
213
+ attribute_code += " #{key}"
214
+ attribute_code += "=\"#{attribute_value}\"" unless attribute_value.empty?
215
+ when Hash
216
+ value.each do |k,v|
217
+ if v
218
+ attribute_code += " #{key}-#{k}"
219
+ attribute_code += "=\"#{v.to_s}\"" unless v == true
220
+ end
221
+ end
222
+ else
223
+ if value
224
+ attribute_code += " #{key}"
225
+ attribute_code += "=\"#{value.to_s}\"" unless value == true
226
+ end
227
+ end
228
+
229
+ return attribute_code
230
+ end
231
+ end
232
+ end