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,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