opulent 0.0.9 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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