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,70 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @Block
6
+ module Block
7
+ # Match a yield with a explicit or implicit target
8
+ #
9
+ # yield target
10
+ #
11
+ # @param parent [Node] Parent node to which we append the definition
12
+ #
13
+ def block_yield(parent)
14
+ if lookahead :yield_lookahead
15
+ # Get current line's indentation
16
+ indent = accept(:indent, false, false) || ""
17
+
18
+ if accept :yield
19
+ # Get definition name
20
+ yield_name = if(yield_name = accept(:yield_identifier, false, false))
21
+ yield_name.strip.to_sym
22
+ else
23
+ Engine::DEFAULT_YIELD
24
+ end
25
+
26
+ # Create a new node
27
+ node = @create.block_yield yield_name, parent, indent.size
28
+
29
+ # Consume the newline from the end of the element
30
+ error :yield unless accept(:line_feed, false, false).strip.empty?
31
+ accept :newline, false, false
32
+
33
+ return node
34
+ end
35
+ end
36
+ end
37
+
38
+ # Match a block with a explicit target
39
+ #
40
+ # block target
41
+ #
42
+ # @param parent [Node] Parent node to which we append the definition
43
+ #
44
+ def block(parent)
45
+ if lookahead :block_lookahead
46
+ # Get current line's indentation
47
+ indent = accept_unstripped(:indent) || ""
48
+
49
+ if accept :block
50
+ # Get definition name
51
+ block_name = if(block_name = accept_unstripped(:yield_identifier))
52
+ block_name.strip.to_sym
53
+ else
54
+ Engine::DEFAULT_YIELD
55
+ end
56
+
57
+ # Create a new node
58
+ node = @create.block block_name, parent, indent.size
59
+
60
+ # Consume the newline from the end of the element
61
+ error :block unless accept_unstripped(:line_feed).strip.empty?
62
+ accept_unstripped :newline
63
+
64
+ return node
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,32 @@
1
+ # @SugarCube
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @Text
6
+ module Comment
7
+ # Match one line or multiline comments
8
+ #
9
+ def comment(parent)
10
+ if lookahead(:comment_lookahead)
11
+ indent = accept_unstripped(:indent) || ""
12
+ indent = indent.size
13
+
14
+ if (comment_feed = accept_unstripped :comment)
15
+ comment_feed += accept_unstripped(:newline) || ""
16
+ comment_feed += get_indented_lines indent
17
+
18
+ # If we have a comment which is visible in the output, we will
19
+ # create a new comment element. Otherwise, we ignore the current
20
+ # gathered text and we simply begin the root parsing again
21
+ if comment_feed[0] == '!'
22
+ return @create.comment comment_feed[1..-1].strip, parent, indent
23
+ else
24
+ root parent
25
+ return nil
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,83 @@
1
+ # @SugarCube
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @Control
6
+ module Control
7
+ # Match an if-else control structure
8
+ #
9
+ def control(parent)
10
+ if lookahead :control_lookahead
11
+ indent = indent || accept_unstripped(:indent) || ""
12
+
13
+ # Accept eval or multiline eval syntax and return a new node,
14
+ if (structure = accept_unstripped(:control))
15
+ structure = structure.to_sym
16
+
17
+ # Handle each and the other control structures
18
+ condition = accept_unstripped(:line_feed).strip
19
+ accept_unstripped :newline
20
+
21
+ # Process each control structure condition
22
+ if structure == :each
23
+ # Check if arguments provided correctly
24
+ error :each_arguments unless condition.match Tokens[:each_pattern][:regex]
25
+
26
+ # Split provided arguments for the each structure
27
+ condition = condition.split('in').map(&:strip)
28
+ condition[0] = condition[0].split(',').map(&:strip).map(&:to_sym)
29
+ end
30
+
31
+ # Else and default structures are not allowed to have any condition
32
+ # set and the other control structures require a condition
33
+ if structure == :else
34
+ error :condition_exists unless condition.empty?
35
+ else
36
+ error :condition_missing if condition.empty?
37
+ end
38
+
39
+ # Add the condition and create a new child to the control parent.
40
+ # The control parent keeps condition -> children matches for our
41
+ # document's content
42
+ add_options = Proc.new do |control_parent|
43
+ control_parent.value << condition
44
+ control_parent.children << []
45
+
46
+ root control_parent
47
+ end
48
+
49
+ # If the current control structure has a matching parent, we search
50
+ # for that type of element in the siblings with the same indentation
51
+ # otherwise we return a new control structure parent
52
+ if (parent_type = control_parent structure)
53
+ begin
54
+ last = -1
55
+ until parent_type.include? parent.children[last].name
56
+ last -= 1
57
+ end
58
+ add_options[parent.children[last]]
59
+ rescue NoMethodError
60
+ error :control_parent
61
+ end
62
+ else
63
+ control_node = @create.control(structure, condition, parent, indent.size)
64
+ end
65
+
66
+ control_node
67
+ end
68
+ end
69
+ end
70
+
71
+ # Check if the current control structure requires a parent node and
72
+ # return the parent's node type
73
+ #
74
+ def control_parent(structure)
75
+ case structure
76
+ when :else then [:if, :unless, :case]
77
+ when :elsif then [:if]
78
+ when :when then [:case]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,39 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @Define
6
+ module Define
7
+ # Match a definition node with its parameters and body
8
+ #
9
+ # def node_name[ parameters ]
10
+ # body nodes
11
+ #
12
+ # @param parent [Node] Parent node to which we append the definition
13
+ #
14
+ def define(parent)
15
+ if lookahead :def_lookahead
16
+ # Get current line's indentation
17
+ indent = accept_unstripped(:indent) || ""
18
+
19
+ if accept :def
20
+ # Get definition name
21
+ def_name = accept :identifier, :*
22
+
23
+ # Get element attributes
24
+ atts = attributes({}) || {}
25
+
26
+ # Create a new node
27
+ node = @create.definition def_name.to_sym, atts, parent, indent.size
28
+
29
+ # Consume the newline from the end of the element
30
+ error :define unless accept_unstripped(:line_feed).strip.empty?
31
+ accept_unstripped :newline
32
+
33
+ return node
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ # @SugarCube
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @Text
6
+ module Evaluate
7
+ # Match one line or multiline, escaped or unescaped text
8
+ #
9
+ def evaluate(parent)
10
+ indent = indent || accept_unstripped(:indent) || ""
11
+
12
+ # Accept eval or multiline eval syntax and return a new node,
13
+ if (evaluate = accept_unstripped(:eval))
14
+ eval_node = @create.evaluate(evaluate.strip, parent, indent.size)
15
+ accept_unstripped :newline
16
+ elsif (evaluate = accept_unstripped(:eval_multiline))
17
+ # Get all the lines which are more indented than the current one
18
+ eval_node = @create.evaluate(evaluate.strip, parent, indent.size)
19
+ eval_node.value += accept_unstripped(:newline) || ""
20
+ eval_node.value += get_indented_lines(indent.size)
21
+ end
22
+
23
+ if eval_node
24
+ # Return the found eval node
25
+ return eval_node
26
+ else
27
+ # Undo by adding the found intentation back
28
+ return undo indent
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,350 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @Expression
6
+ module Expression
7
+ # Check if the parser matches an expression node
8
+ #
9
+ def expression(allow_assignments = true, wrapped = true)
10
+ buffer = ""
11
+
12
+ # Build a ruby expression out of accepted literals
13
+ while (term = whitespace ||
14
+ modifier ||
15
+ identifier ||
16
+ method_call ||
17
+ paranthesis ||
18
+ array ||
19
+ hash ||
20
+ symbol ||
21
+ percent ||
22
+ primary_term ||
23
+ (allow_assignments ? accept(:exp_assignment) : nil))
24
+ buffer += term
25
+
26
+ # Accept operations which have a right term and raise an error if
27
+ # we have an unfinished expression such as "a +", "b - 1 >" and other
28
+ # expressions following the same pattern
29
+ if wrapped && (op = operation)
30
+ buffer += op
31
+ if (right_term = expression(allow_assignments, wrapped)).nil?
32
+ error :expression
33
+ else
34
+ buffer += right_term.value
35
+ end
36
+ elsif (conditional = ternary_operator allow_assignments, wrapped)
37
+ buffer += conditional
38
+ end
39
+
40
+ # Do not continue if the expression has whitespace method calls in
41
+ # an unwrapped context because this will confuse the parser
42
+ unless buffer.strip.empty?
43
+ break unless wrapped || lookahead(:exp_identifier_lookahead, false)
44
+ end
45
+ end
46
+
47
+ if buffer.strip.empty?
48
+ return undo buffer
49
+ else
50
+ return @create.expression buffer
51
+ end
52
+ end
53
+
54
+ # Check if it's possible to parse a ruby array literal. First, try to see
55
+ # if the next sequence is a hash_open token: "[", and if it is, then a
56
+ # hash_close: "]" token is required next
57
+ #
58
+ # [array_elements]
59
+ #
60
+ def array
61
+ if (buffer = accept :array_open)
62
+ accept_unstripped :newline
63
+ buffer += array_elements
64
+ buffer += accept :array_close, :*
65
+ end
66
+ end
67
+
68
+ # Recursively gather expressions separated by a comma and add them to the
69
+ # expression buffer
70
+ #
71
+ # experssion1, experssion2, experssion3
72
+ #
73
+ # @param buffer [String] Accumulator for the array elements
74
+ #
75
+ def array_elements(buffer = '')
76
+ if (term = expression)
77
+ buffer += term.value
78
+ # If there is an array_terminator ",", recursively gather the next
79
+ # array element into the buffer
80
+ if (terminator = accept :array_terminator) then
81
+ accept_unstripped :newline
82
+ buffer += array_elements terminator
83
+ end
84
+ end
85
+
86
+ # Array ended prematurely with a trailing comma, therefore the current
87
+ # parsing process will stop
88
+ if buffer.strip[-1] == ','
89
+ error :array_elements_terminator
90
+ end
91
+
92
+ buffer
93
+ end
94
+
95
+ # Check if it's possible to parse a ruby hash literal. First, try to see
96
+ # if the next sequence is a hash_open token: "{", and if it is, then a
97
+ # hash_close: "}" token is required next
98
+ #
99
+ # { hash_elements }
100
+ #
101
+ def hash
102
+ if (buffer = accept :hash_open)
103
+ accept_unstripped :newline
104
+ buffer += hash_elements
105
+ buffer += accept :hash_close, :*
106
+ end
107
+ end
108
+
109
+ # Recursively gather expression attributions separated by a comma and add
110
+ # them to the expression buffer
111
+ #
112
+ # key1: experssion1, key2 => experssion2, :key3 => experssion3
113
+ #
114
+ # @param buffer [String] Accumulator for the hash elements
115
+ #
116
+ def hash_elements(buffer = '')
117
+ value = Proc.new do
118
+ # Get the value associated to the current hash key
119
+ if (exp = expression)
120
+ buffer += exp.value
121
+ else
122
+ error :hash_elements
123
+ end
124
+
125
+ # If there is an hash_terminator ",", recursively gather the next
126
+ # array element into the buffer
127
+ if (terminator = accept :hash_terminator) then
128
+ accept_unstripped :newline
129
+ buffer += hash_elements terminator
130
+ end
131
+ end
132
+
133
+ # Accept both shorthand and default ruby hash style. Following DRY
134
+ # principles, a Proc is used to assign the value to the current key
135
+ #
136
+ # key:
137
+ # :key =>
138
+ if (symbol = accept :hash_symbol)
139
+ buffer += symbol
140
+ value[]
141
+ elsif (exp = expression)
142
+ buffer += exp.value
143
+ if(assign = accept :hash_assignment)
144
+ buffer += assign
145
+ value[]
146
+ else
147
+ error :hash_assignment
148
+ end
149
+ end
150
+
151
+ # Array ended prematurely with a trailing comma, therefore the current
152
+ # parsing process will stop
153
+ if buffer.strip[-1] == ','
154
+ error :hash_elements_terminator
155
+ end
156
+
157
+ buffer
158
+ end
159
+
160
+ # Accept a ruby identifier such as a class, module, method or variable
161
+ #
162
+ def identifier
163
+ if (buffer = accept_line_unstripped :exp_identifier)
164
+ if (args = call)
165
+ buffer += args
166
+ end
167
+ return buffer
168
+ end
169
+ end
170
+
171
+ # Check if it's possible to parse a ruby paranthesis expression wrapper.
172
+ #
173
+ def paranthesis
174
+ if (buffer = accept :round_bracket_open)
175
+ accept_unstripped :newline
176
+ buffer += expression.to_s
177
+ buffer += accept :round_bracket_close, :*
178
+ end
179
+ end
180
+
181
+ # Check if it's possible to parse a ruby call literal. First, try to see
182
+ # if the next sequence is a hash_open token: "(", and if it is, then a
183
+ # hash_close: ")" token is required next
184
+ #
185
+ # ( call_elements )
186
+ #
187
+ def call
188
+ if (buffer = accept_unstripped :round_bracket_open)
189
+ accept_unstripped :newline
190
+ buffer += call_elements
191
+ buffer += accept :round_bracket_close, :*
192
+ end
193
+ end
194
+
195
+ # Recursively gather expression attributes separated by a comma and add
196
+ # them to the expression buffer
197
+ #
198
+ # expression1, a: expression2, expression3
199
+ #
200
+ # @param buffer [String] Accumulator for the call elements
201
+ #
202
+ def call_elements(buffer = '')
203
+ # Accept both shorthand and default ruby hash style. Following DRY
204
+ # principles, a Proc is used to assign the value to the current key
205
+ #
206
+ # key: value
207
+ # :key => value
208
+ # value
209
+ if (symbol = accept :hash_symbol)
210
+ buffer += symbol
211
+
212
+ # Get the value associated to the current hash key
213
+ if (exp = expression(true))
214
+ buffer += exp.value
215
+ else
216
+ error :call_elements
217
+ end
218
+
219
+ # If there is an comma ",", recursively gather the next
220
+ # array element into the buffer
221
+ if (terminator = accept :comma) then
222
+ accept_unstripped :newline
223
+ buffer += call_elements terminator
224
+ end
225
+ elsif (exp = expression(true))
226
+ buffer += exp.value
227
+
228
+ if(assign = accept :hash_assignment)
229
+ buffer += assign
230
+
231
+ # Get the value associated to the current hash key
232
+ if (exp = expression(true))
233
+ buffer += exp.value
234
+ else
235
+ error :call_elements
236
+ end
237
+ end
238
+
239
+ # If there is an comma ",", recursively gather the next
240
+ # array element into the buffer
241
+ if (terminator = accept :comma) then
242
+ accept_unstripped :newline
243
+ buffer += call_elements terminator
244
+ end
245
+ end
246
+
247
+ buffer
248
+ end
249
+
250
+ # Accept a ruby symbol defined through a colon and a trailing expression
251
+ #
252
+ # :'symbol'
253
+ # :symbol
254
+ #
255
+ def symbol
256
+ if (colon = accept :colon)
257
+ return undo colon if lookahead(:whitespace_lookahead, false)
258
+
259
+ if (exp = expression).nil?
260
+ error :symbol
261
+ else
262
+ colon + exp.to_s
263
+ end
264
+ end
265
+ end
266
+
267
+ # Accept a ruby module, method or context modifier
268
+ #
269
+ # Module::
270
+ # @, @@, $
271
+ #
272
+ def modifier
273
+ accept(:exp_context) || accept(:exp_module)
274
+ end
275
+
276
+
277
+ # Accept a ruby percentage operator for arrays of strings, symbols and
278
+ # simple escaped strings
279
+ #
280
+ # %w(word1 word2 word3)
281
+ #
282
+ def percent
283
+ if (buffer = accept :exp_percent)
284
+ match_start = buffer[-1]
285
+ match_name = :"percent#{match_start}"
286
+
287
+ unless Tokens[match_name]
288
+ match_end = Tokens.bracket(match_start) || match_start
289
+
290
+ match_inner = "\\#{match_start}"
291
+ match_inner += "\\#{match_end}" unless match_end == match_start
292
+
293
+ pattern = /(((?:[^#{match_inner}\\]|\\.)*?)#{'\\' + match_end})/
294
+
295
+ Tokens[match_name] = Tokens::Token.new pattern
296
+ end
297
+
298
+ buffer += accept match_name
299
+ end
300
+ end
301
+
302
+ # Accept any primary term and return it without the leading whitespace to
303
+ # the expression buffer
304
+ #
305
+ # "string"
306
+ # 123
307
+ # 123.456
308
+ # nil
309
+ # true
310
+ # false
311
+ # /.*/
312
+ #
313
+ def primary_term
314
+ accept(:exp_string) ||
315
+ accept(:exp_fixnum) ||
316
+ accept(:exp_double) ||
317
+ accept(:exp_nil) ||
318
+ accept(:exp_regex) ||
319
+ accept(:exp_boolean)
320
+ end
321
+
322
+ # Accept an operation between two or more expressions
323
+ #
324
+ def operation
325
+ accept_unstripped(:exp_operation)
326
+ end
327
+
328
+ # Accept a ruby method call modifier
329
+ #
330
+ def method_call
331
+ accept_unstripped(:exp_method_call)
332
+ end
333
+
334
+ # Accept ternary operator syntax
335
+ #
336
+ # condition ? expression1 : expression2
337
+ #
338
+ def ternary_operator(allow_assignments, wrapped)
339
+ if (buffer = accept_line_unstripped :exp_ternary)
340
+ buffer += expression(allow_assignments, wrapped).to_s
341
+ if (else_branch = accept_line_unstripped :exp_ternary_else)
342
+ buffer += else_branch
343
+ buffer += expression(allow_assignments, wrapped).to_s
344
+ end
345
+ return buffer
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end