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,21 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Match one line or multiline, escaped or unescaped text
6
+ #
7
+ def evaluate(parent, indent)
8
+ # Accept eval or multiline eval syntax and return a new node,
9
+ if (evaluate = accept(:eval))
10
+ eval_node = [:evaluate, evaluate[1..-1].strip, {}, nil, indent]
11
+ elsif (evaluate = accept(:eval_multiline))
12
+ # Get all the lines which are more indented than the current one
13
+ eval_node = [:evaluate, evaluate[1..-1].strip, {}, nil, indent]
14
+ eval_node[@value] += accept(:newline) || ""
15
+ eval_node[@value] += get_indented_lines(indent)
16
+ end
17
+
18
+ parent[@children] << eval_node if eval_node
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,344 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Check if the parser matches an expression node
6
+ #
7
+ def expression(allow_assignments = true, wrapped = true, whitespace = true)
8
+ buffer = ""
9
+
10
+ # Build a ruby expression out of accepted literals
11
+ while (term = (whitespace ? accept(:whitespace) : nil) ||
12
+ modifier ||
13
+ identifier ||
14
+ method_call ||
15
+ paranthesis ||
16
+ array ||
17
+ hash ||
18
+ symbol ||
19
+ percent ||
20
+ primary_term)
21
+ buffer += term
22
+
23
+ # Accept operations which have a right term and raise an error if
24
+ # we have an unfinished expression such as "a +", "b - 1 >" and other
25
+ # expressions following the same pattern
26
+ if wrapped && (op = operation || (allow_assignments ? accept_stripped(:exp_assignment) : nil))
27
+ buffer += op
28
+ if (right_term = expression(allow_assignments, wrapped)).nil?
29
+ error :expression
30
+ else
31
+ buffer += right_term[@value]
32
+ end
33
+ elsif (conditional = ternary_operator allow_assignments, wrapped)
34
+ buffer += conditional
35
+ end
36
+
37
+ # Do not continue if the expression has whitespace method calls in
38
+ # an unwrapped context because this will confuse the parser
39
+ unless buffer.strip.empty?
40
+ break unless wrapped || lookahead(:exp_identifier_lookahead).nil?
41
+ end
42
+ end
43
+
44
+ if buffer.strip.empty?
45
+ return undo buffer
46
+ else
47
+ return [:expression, buffer.strip, {evaluate: true}]
48
+ end
49
+ end
50
+
51
+ # Check if it's possible to parse a ruby array literal. First, try to see
52
+ # if the next sequence is a hash_open token: "[", and if it is, then a
53
+ # hash_close: "]" token is required next
54
+ #
55
+ # [array_elements]
56
+ #
57
+ def array
58
+ if (buffer = accept :square_bracket)
59
+ accept_newline
60
+ buffer += array_elements
61
+ accept_newline
62
+ buffer += accept :'[', :*
63
+ end
64
+ end
65
+
66
+ # Recursively gather expressions separated by a comma and add them to the
67
+ # expression buffer
68
+ #
69
+ # experssion1, experssion2, experssion3
70
+ #
71
+ # @param buffer [String] Accumulator for the array elements
72
+ #
73
+ def array_elements(buffer = '')
74
+ if (term = expression)
75
+ buffer += term[@value]
76
+ # If there is an array_terminator ",", recursively gather the next
77
+ # array element into the buffer
78
+ if (terminator = accept_stripped :comma) then
79
+ accept_newline
80
+ buffer += array_elements terminator
81
+ end
82
+ end
83
+
84
+ # Array ended prematurely with a trailing comma, therefore the current
85
+ # parsing process will stop
86
+ if buffer.strip[-1] == ','
87
+ error :array_elements_terminator
88
+ end
89
+
90
+ return buffer
91
+ end
92
+
93
+ # Check if it's possible to parse a ruby hash literal. First, try to see
94
+ # if the next sequence is a hash_open token: "{", and if it is, then a
95
+ # hash_close: "}" token is required next
96
+ #
97
+ # { hash_elements }
98
+ #
99
+ def hash
100
+ if (buffer = accept :curly_bracket)
101
+ accept_newline
102
+ buffer += hash_elements
103
+ accept_newline
104
+ buffer += accept :'{', :*
105
+ end
106
+ end
107
+
108
+ # Recursively gather expression attributions separated by a comma and add
109
+ # them to the expression buffer
110
+ #
111
+ # key1: experssion1, key2 => experssion2, :key3 => experssion3
112
+ #
113
+ # @param buffer [String] Accumulator for the hash elements
114
+ #
115
+ def hash_elements(buffer = '')
116
+ value = Proc.new do
117
+ # Get the value associated to the current hash key
118
+ if (exp = expression)
119
+ buffer += exp[@value]
120
+ else
121
+ error :hash_elements
122
+ end
123
+
124
+ # If there is an hash_terminator ",", recursively gather the next
125
+ # array element into the buffer
126
+ if (terminator = accept_stripped :comma) then
127
+ accept_newline
128
+ buffer += hash_elements terminator
129
+ end
130
+ end
131
+
132
+ # Accept both shorthand and default ruby hash style. Following DRY
133
+ # principles, a Proc is used to assign the value to the current key
134
+ #
135
+ # key:
136
+ # :key =>
137
+ if (symbol = accept_stripped :hash_symbol)
138
+ buffer += symbol
139
+ value[]
140
+ elsif (exp = expression false)
141
+ buffer += exp[@value]
142
+ if(assign = accept_stripped :hash_assignment)
143
+ buffer += assign
144
+ value[]
145
+ else
146
+ error :hash_assignment
147
+ end
148
+ end
149
+
150
+ # Array ended prematurely with a trailing comma, therefore the current
151
+ # parsing process will stop
152
+ if buffer.strip[-1] == ','
153
+ error :hash_elements_terminator
154
+ end
155
+
156
+ return buffer
157
+ end
158
+
159
+ # Accept a ruby identifier such as a class, module, method or variable
160
+ #
161
+ def identifier
162
+ if (buffer = accept :exp_identifier)
163
+ if (args = call)
164
+ buffer += args
165
+ end
166
+ return buffer
167
+ end
168
+ end
169
+
170
+ # Check if it's possible to parse a ruby paranthesis expression wrapper.
171
+ #
172
+ def paranthesis
173
+ if (buffer = accept :round_bracket)
174
+ buffer += expression[@value]
175
+ buffer += accept_stripped :'(', :*
176
+ end
177
+ end
178
+
179
+ # Check if it's possible to parse a ruby call literal. First, try to see
180
+ # if the next sequence is a hash_open token: "(", and if it is, then a
181
+ # hash_close: ")" token is required next
182
+ #
183
+ # ( call_elements )
184
+ #
185
+ def call
186
+ if (buffer = accept :round_bracket)
187
+ buffer += call_elements
188
+ buffer += accept_stripped :'(', :*
189
+ end
190
+ end
191
+
192
+ # Recursively gather expression attributes separated by a comma and add
193
+ # them to the expression buffer
194
+ #
195
+ # expression1, a: expression2, expression3
196
+ #
197
+ # @param buffer [String] Accumulator for the call elements
198
+ #
199
+ def call_elements(buffer = '')
200
+ # Accept both shorthand and default ruby hash style. Following DRY
201
+ # principles, a Proc is used to assign the value to the current key
202
+ #
203
+ # key: value
204
+ # :key => value
205
+ # value
206
+ if (symbol = accept_stripped :hash_symbol)
207
+ buffer += symbol
208
+
209
+ # Get the value associated to the current hash key
210
+ if (exp = expression(true))
211
+ buffer += exp[@value]
212
+ else
213
+ error :call_elements
214
+ end
215
+
216
+ # If there is an comma ",", recursively gather the next
217
+ # array element into the buffer
218
+ if (terminator = accept_stripped :comma) then
219
+ buffer += call_elements terminator
220
+ end
221
+ elsif (exp = expression(true))
222
+ buffer += exp[@value]
223
+
224
+ if(assign = accept_stripped :hash_assignment)
225
+ buffer += assign
226
+
227
+ # Get the value associated to the current hash key
228
+ if (exp = expression(true))
229
+ buffer += exp[@value]
230
+ else
231
+ error :call_elements
232
+ end
233
+ end
234
+
235
+ # If there is an comma ",", recursively gather the next
236
+ # array element into the buffer
237
+ if (terminator = accept_stripped :comma) then
238
+ buffer += call_elements terminator
239
+ end
240
+ end
241
+
242
+ buffer
243
+ end
244
+
245
+ # Accept a ruby symbol defined through a colon and a trailing expression
246
+ #
247
+ # :'symbol'
248
+ # :symbol
249
+ #
250
+ def symbol
251
+ if (colon = accept :colon)
252
+ return undo colon if lookahead(:whitespace)
253
+
254
+ if (exp = expression).nil?
255
+ error :symbol
256
+ else
257
+ colon + exp[@value]
258
+ end
259
+ end
260
+ end
261
+
262
+ # Accept a ruby module, method or context modifier
263
+ #
264
+ # Module::
265
+ # @, @@, $
266
+ #
267
+ def modifier
268
+ accept(:exp_context) || accept(:exp_module)
269
+ end
270
+
271
+
272
+ # Accept a ruby percentage operator for arrays of strings, symbols and
273
+ # simple escaped strings
274
+ #
275
+ # %w(word1 word2 word3)
276
+ #
277
+ def percent
278
+ if (buffer = accept_stripped :exp_percent)
279
+ match_start = buffer[-1]
280
+ match_name = :"percent#{match_start}"
281
+
282
+ unless Tokens[match_name]
283
+ match_end = Tokens.bracket(match_start) || match_start
284
+
285
+ match_inner = "\\#{match_start}"
286
+ match_inner += "\\#{match_end}" unless match_end == match_start
287
+
288
+ pattern = /(((?:[^#{match_inner}\\]|\\.)*?)#{'\\' + match_end})/
289
+
290
+ Tokens[match_name] = pattern
291
+ end
292
+
293
+ buffer += accept match_name
294
+ end
295
+ end
296
+
297
+ # Accept any primary term and return it without the leading whitespace to
298
+ # the expression buffer
299
+ #
300
+ # "string"
301
+ # 123
302
+ # 123.456
303
+ # nil
304
+ # true
305
+ # false
306
+ # /.*/
307
+ #
308
+ def primary_term
309
+ accept_stripped(:exp_string) ||
310
+ accept_stripped(:exp_fixnum) ||
311
+ accept_stripped(:exp_double) ||
312
+ accept_stripped(:exp_nil) ||
313
+ accept_stripped(:exp_regex) ||
314
+ accept_stripped(:exp_boolean)
315
+ end
316
+
317
+ # Accept an operation between two or more expressions
318
+ #
319
+ def operation
320
+ accept(:exp_operation)
321
+ end
322
+
323
+ # Accept a ruby method call modifier
324
+ #
325
+ def method_call
326
+ accept(:exp_method_call)
327
+ end
328
+
329
+ # Accept ternary operator syntax
330
+ #
331
+ # condition ? expression1 : expression2
332
+ #
333
+ def ternary_operator(allow_assignments, wrapped)
334
+ if (buffer = accept :exp_ternary)
335
+ buffer += expression(allow_assignments, wrapped)[@value]
336
+ if (else_branch = accept :exp_ternary_else)
337
+ buffer += else_branch
338
+ buffer += expression(allow_assignments, wrapped)[@value]
339
+ end
340
+ return buffer
341
+ end
342
+ end
343
+ end
344
+ end
@@ -0,0 +1,25 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Check if we match an compile time filter
6
+ #
7
+ # :filter
8
+ #
9
+ # @param parent [Node] Parent node to which we append the element
10
+ #
11
+ def filter(parent, indent)
12
+ if (filter_name = accept :filter)
13
+ # Get element attributes
14
+ atts = attributes(shorthand_attributes) || {}
15
+
16
+ # Accept inline text or multiline text feed as first child
17
+ error :fiter unless accept(:line_feed).strip.empty?
18
+
19
+ # Get everything under the filter and set it as the node value
20
+ # and create a new node and set its extension
21
+ parent[@children] << [:filter, filter_name[1..-1].to_sym, atts, get_indented_lines(indent), indent]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,246 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Check if we match an node node with its attributes and possibly
6
+ # inline text
7
+ #
8
+ # node [ attributes ] Inline text
9
+ #
10
+ # @param parent [Node] Parent node to which we append the node
11
+ #
12
+ def node(parent, indent = nil)
13
+ if (name = lookahead(:node_lookahead) || lookahead(:shorthand_lookahead))
14
+ return nil if Keywords.include? name[0].to_sym
15
+
16
+ # Accept either explicit node_name or implicit :div node_name
17
+ # with shorthand attributes
18
+ if (node_name = accept :node)
19
+ node_name = node_name.to_sym
20
+ shorthand = shorthand_attributes
21
+ elsif (shorthand = shorthand_attributes)
22
+ node_name = :div
23
+ end
24
+
25
+ # Node creation options
26
+ options = {}
27
+
28
+ # Get leading and trailing whitespace
29
+ if accept_stripped :leading_whitespace
30
+ options[:leading_whitespace] = true
31
+ if accept :leading_trailing_whitespace
32
+ options[:trailing_whitespace] = true
33
+ end
34
+ elsif accept_stripped :trailing_whitespace
35
+ options[:trailing_whitespace] = true
36
+ end
37
+
38
+ # Get wrapped node attributes
39
+ atts = attributes(shorthand) || {}
40
+
41
+ # Inherit attributes from definition
42
+ options[:extension] = extension = extend_attributes
43
+
44
+ # Get unwrapped node attributes
45
+ options[:attributes] = attributes_assignments atts, false
46
+
47
+ # Create node
48
+ current_node = [:node, node_name, options, [], indent]
49
+
50
+ # Check if the node is explicitly self enclosing
51
+ if(close = accept_stripped :self_enclosing) || Settings::SelfEnclosing.include?(node_name)
52
+ current_node[@options][:self_enclosing] = true
53
+
54
+ unless close.nil? || close.strip.empty?
55
+ undo close; error :self_enclosing
56
+ end
57
+
58
+ # For self enclosing tag error reporting purposes
59
+ line = @i
60
+ end
61
+
62
+ # Check whether we have explicit inline elements and add them
63
+ # with increased base indentation
64
+ if (accept :inline_child)
65
+ # Inline node element
66
+ unless (child_node = node current_node, indent)
67
+ error :inline_child
68
+ end
69
+ else
70
+ # Inline text element
71
+ text_node = text current_node, indent, false
72
+ end
73
+
74
+ # Add the current node to the root
75
+ root(current_node, indent)
76
+
77
+ if current_node[@options][:self_enclosing] && current_node[@children].any?
78
+ error :self_enclosing_children, line
79
+ end
80
+
81
+ if @definitions.keys.include? node_name
82
+ model = @definitions[node_name].clone
83
+ model[@options][:call] = current_node
84
+
85
+ parent[@children] << model
86
+ else
87
+ parent[@children] << current_node
88
+ end
89
+ end
90
+ end
91
+
92
+ # Helper method to create an array of values when an attribute is set
93
+ # multiple times. This happens unless the key is id, which is unique
94
+ #
95
+ # @param atts [Hash] Current node attributes hash
96
+ # @param key [Symbol] Attribute name
97
+ # @param value [String] Attribute value
98
+ #
99
+ def add_attribute(atts, key, value)
100
+ # Check whether the attribute value needs to be evaluated or not
101
+ value[@options][:evaluate] = if value[@value] =~ Settings::EvaluationCheck
102
+ value[@value] =~ Settings::InterpolationCheck ? true : false
103
+ else
104
+ true
105
+ end
106
+
107
+ # Check for unique key and arrays of attributes
108
+ if key == :class
109
+ # If the key is already associated to an array, add the value to the
110
+ # array, otherwise, create a new array or set it
111
+ if atts[key]
112
+ atts[key] << value
113
+ else
114
+ atts[key] = [value]
115
+ end
116
+ else
117
+ atts[key] = value
118
+ end
119
+ end
120
+
121
+ # Accept node shorthand attributes. Each shorthand attribute is directly
122
+ # mapped to an attribute key
123
+ #
124
+ # @param atts [Hash] Node attributes
125
+ #
126
+ def shorthand_attributes(atts = {})
127
+ while (key = accept :shorthand)
128
+ key = Settings::Shorthand[key.to_sym]
129
+
130
+ # Check whether the value is escaped or unescaped
131
+ escaped = accept(:unescaped_value) ? false : true
132
+
133
+ # Get the attribute value and process it
134
+ if (value = accept(:node))
135
+ value = [:expression, value.inspect, {escaped: escaped}]
136
+ elsif (value = accept(:exp_string))
137
+ value = [:expression, value, {escaped: escaped}]
138
+ elsif (value = paranthesis)
139
+ value = [:expression, value, {escaped: escaped}]
140
+ else
141
+ error :shorthand
142
+ end
143
+
144
+ # IDs are unique, the rest of the attributes turn into arrays in
145
+ # order to allow multiple values or identifiers
146
+ add_attribute(atts, key, value)
147
+ end
148
+
149
+ return atts
150
+ end
151
+
152
+ def attributes(atts = {})
153
+ wrapped_attributes atts
154
+ attributes_assignments atts, false
155
+
156
+ return atts
157
+ end
158
+
159
+
160
+ # Check if we match node attributes
161
+ #
162
+ # [ assignments ]
163
+ #
164
+ # @param as_parameters [Boolean] Accept or reject identifier nodes
165
+ #
166
+ def wrapped_attributes(list)
167
+ if (bracket = accept :brackets)
168
+ attributes_assignments list
169
+ accept bracket.to_sym, :*
170
+ end
171
+
172
+ return list
173
+ end
174
+
175
+ # Check if we match an expression node or
176
+ # a node node
177
+ #
178
+ # [ assignments ]
179
+ #
180
+ # @param parent [Hash] Parent to which we append nodes
181
+ # @param as_parameters [Boolean] Accept or reject identifier nodes
182
+ #
183
+ def attributes_assignments(parent, wrapped = true)
184
+ unless wrapped
185
+ return parent if lookahead(:assignment_lookahead).nil?
186
+ end
187
+
188
+ if (argument = accept_stripped :node)
189
+ argument = argument.to_sym
190
+
191
+ if accept :assignment
192
+ # Check if we have an attribute escape or not
193
+ escaped = if accept :assignment_unescaped
194
+ false
195
+ else
196
+ true
197
+ end
198
+
199
+ # Get the argument value if we have an assignment
200
+ if (value = expression(false, wrapped))
201
+ value[@options][:escaped] = escaped
202
+
203
+ # IDs are unique, the rest of the attributes turn into arrays in
204
+ # order to allow multiple values or identifiers
205
+ add_attribute(parent, argument, value)
206
+ else
207
+ error :assignments_colon
208
+ end
209
+ else
210
+ parent[argument] = [:expression, "nil", {evaluate: true, escaped: false}] unless parent[argument]
211
+ end
212
+
213
+ # If our attributes are wrapped, we allow method calls without
214
+ # paranthesis, ruby style, therefore we need a terminator to signify
215
+ # the expression end. If they are not wrapped (inline), we require
216
+ # paranthesis and allow inline calls
217
+ if wrapped && accept_stripped(:assignment_terminator)
218
+ attributes_assignments parent, wrapped
219
+ elsif !wrapped && lookahead(:assignment_lookahead)
220
+ attributes_assignments parent, wrapped
221
+ end
222
+
223
+ return parent
224
+ elsif !parent.empty?
225
+ error :assignments_comma
226
+ end
227
+ end
228
+
229
+ # Extend node attributes with hash from
230
+ #
231
+ # +value
232
+ # +{hash: "value"}
233
+ # +(paranthesis)
234
+ #
235
+ def extend_attributes
236
+ if (accept :extend_attributes)
237
+ unescaped = accept :unescaped_value
238
+
239
+ extension = expression(false, false, false)
240
+ extension[@options][:escaped] = false if unescaped
241
+
242
+ return extension
243
+ end
244
+ end
245
+ end
246
+ end