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