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,41 @@
1
+ # @SugarCube
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @Text
6
+ module Filter
7
+ # Check if we match an compile time filter
8
+ #
9
+ # :filter
10
+ #
11
+ # @param parent [Node] Parent node to which we append the element
12
+ #
13
+ def filter_element(parent)
14
+ if lookahead :filter_lookahead
15
+ # Get current line's indentation
16
+ indent = accept_unstripped(:indent) || ""
17
+
18
+ if (filter_name = accept :filter)
19
+ # Get element attributes
20
+ atts = attributes({}) || {}
21
+
22
+ # Create a new node and set its extension
23
+ filter_node = @create.filter filter_name.to_sym, atts, parent, indent.size
24
+
25
+ # Accept inline text or multiline text feed as first child
26
+ if(text_node = text filter_node, indent, true)
27
+ #filter_node.atts = accept_unstripped(:line_feed)
28
+ error :fiter unless accept_line(:line_feed).strip.empty?
29
+ end
30
+ accept_unstripped(:newline)
31
+
32
+ # Get everything under the filter and set it as the node value
33
+ filter_node.value += get_indented_lines(indent.size)
34
+
35
+ return filter_node
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,232 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @node
6
+ module Node
7
+ # Check if we match an node node with its attributes and possibly
8
+ # inline text
9
+ #
10
+ # node [ attributes ] Inline text
11
+ #
12
+ # @param parent [Node] Parent node to which we append the node
13
+ #
14
+ def node(parent, indent = nil)
15
+ if (name = lookahead :identifier_lookahead)
16
+ return nil if Tokens.keyword name
17
+
18
+ # Get current line's indentation
19
+ unless indent
20
+ indent = accept_unstripped(:indent) || ""
21
+ indent = indent.size
22
+ end
23
+
24
+ # Get a theme if set, or set append definitions to the default
25
+ # element namespace
26
+ if (theme_name = accept_unstripped(:theme_node))
27
+ theme_name = theme_name.to_sym
28
+ else
29
+ theme_name = Engine::DEFAULT_THEME
30
+ end
31
+
32
+ # Accept either explicit node_name or implicit :div node_name
33
+ # with shorthand attributes
34
+ if (node_name = accept :identifier)
35
+ shorthand = shorthand_attributes
36
+ elsif (shorthand = shorthand_attributes)
37
+ node_name = :div
38
+ end
39
+
40
+ # Get leading and trailing whitespace
41
+ if accept_line_unstripped :leading_whitespace
42
+ leading_whitespace = true
43
+ if accept_line_unstripped :leading_trailing_whitespace
44
+ trailing_whitespace = true
45
+ end
46
+ elsif accept_line_unstripped :trailing_whitespace
47
+ trailing_whitespace = true
48
+ end
49
+
50
+ # Get wrapped node attributes
51
+ atts = attributes(shorthand) || {}
52
+
53
+ # Inherit attributes from definition
54
+ extension = extend_attributes
55
+
56
+ # Get unwrapped node attributes
57
+ atts = attributes_assignments atts, false
58
+
59
+ # Create a new node and set its extension
60
+ _node = @create.node node_name.to_sym, atts, parent, indent, []
61
+ _node.extension = extension
62
+ _node.theme = theme_name
63
+ _node.whitespace = [leading_whitespace, trailing_whitespace]
64
+
65
+ if(accept_line :inline_child)
66
+ if (child_node = node _node, indent + Engine[:indent])
67
+ _node.push child_node
68
+ else
69
+ error :inline_child
70
+ end
71
+ end
72
+
73
+ if(close = accept_line :self_enclosing)
74
+ _node.self_enclosing = true
75
+ unless close.strip.empty?
76
+ undo close
77
+ error :self_enclosing
78
+ end
79
+ end
80
+
81
+ # Accept inline text or multiline text feed as first child
82
+ if(text_node = text _node, " " * indent, false)
83
+ text_node.indent += Engine[:indent]
84
+ _node.push text_node unless text_node.nil?
85
+ end
86
+
87
+ return _node
88
+ end
89
+ end
90
+
91
+ def shorthand_attributes(atts = {})
92
+ find_shorthand = Proc.new do
93
+ Engine[:shorthand].find do |key, value|
94
+ if accept_unstripped :"shorthand@#{key}"
95
+ key
96
+ else
97
+ false
98
+ end
99
+ end
100
+ end
101
+
102
+ while (attribute = find_shorthand[])
103
+ key = attribute[0]
104
+
105
+ # Get the attribute value and process it
106
+ if (value = accept_unstripped(:identifier))
107
+ value = @create.expression "\"#{value}\""
108
+ elsif (value = (accept_unstripped(:exp_string) || paranthesis))
109
+ value = @create.expression "#{value}"
110
+ else
111
+ error :shorthand
112
+ end
113
+
114
+ # IDs are unique, the rest of the attributes turn into arrays in
115
+ # order to allow multiple values or identifiers
116
+ if key == :id
117
+ atts[key] = value
118
+ else
119
+ if atts[key].is_a? Array
120
+ atts[key] << value
121
+ elsif atts[key]
122
+ atts[key] = [atts[key], value]
123
+ else
124
+ atts[key] = [value]
125
+ end
126
+ end
127
+ end
128
+
129
+ return atts
130
+ end
131
+
132
+ # Extend node attributes with hash from
133
+ #
134
+ # [hash]
135
+ #
136
+ def extend_attributes
137
+ if (accept_unstripped :extend_attributes)
138
+ bracket = accept_unstripped :brackets, :*
139
+ extension = expression
140
+ accept bracket.to_sym, :*
141
+ return extension
142
+ end
143
+ end
144
+
145
+ # Check if we match node attributes
146
+ #
147
+ # [ assignments ]
148
+ #
149
+ # @param as_parameters [Boolean] Accept or reject identifier nodes
150
+ #
151
+ def attributes(list)
152
+ if (bracket = accept_unstripped :brackets)
153
+ attributes_assignments list
154
+ accept bracket.to_sym, :*
155
+ end
156
+
157
+ return list
158
+ end
159
+
160
+ # Check if we match an expression node or
161
+ # a node node
162
+ #
163
+ # [ assignments ]
164
+ #
165
+ # @param parent [Hash] Parent to which we append nodes
166
+ # @param as_parameters [Boolean] Accept or reject identifier nodes
167
+ #
168
+ def attributes_assignments(parent, wrapped = true)
169
+ unless wrapped
170
+ return parent if lookahead(:assignment_lookahead).nil?
171
+ end
172
+
173
+ if (argument = accept :identifier)
174
+ argument = argument.to_sym
175
+
176
+ if accept :assignment
177
+ # Check if we have an attribute escape or not
178
+ escaped = if accept_unstripped :assignment_unescaped
179
+ false
180
+ else
181
+ true
182
+ end
183
+
184
+ # Get the argument value if we have an assignment
185
+ if (value = expression(false, wrapped))
186
+ value.escaped = escaped
187
+
188
+ # Check if our argument already exists in the attributes list, and
189
+ # if it does, check if it's an array. If it isn't, turn it into an
190
+ # array literal, otherwise push the value into it. However, id
191
+ # attributes do not get turned into arrays as they are supposed
192
+ # to be unique
193
+ if parent[argument] && argument != :id
194
+ # Check if argument is already an array, otherwise create an
195
+ # array in which we will add values
196
+ if parent[argument].is_a? Array
197
+ parent[argument] << value
198
+ else
199
+ new_parent = []
200
+ new_parent.push parent[argument]
201
+
202
+ parent[argument] = new_parent
203
+ parent[argument] << value
204
+ end
205
+ else
206
+ parent[argument] = value
207
+ end
208
+ else
209
+ error :assignments_colon
210
+ end
211
+ else
212
+ parent[argument] = @create.expression "true" unless parent[argument]
213
+ end
214
+
215
+ # If our attributes are wrapped, we allow method calls without
216
+ # paranthesis, ruby style, therefore we need a terminator to signify
217
+ # the expression end. If they are not wrapped (inline), we require
218
+ # paranthesis and allow inline calls
219
+ if wrapped && accept_line(:assignment_terminator)
220
+ attributes_assignments parent, wrapped
221
+ elsif lookahead(:assignment_lookahead)
222
+ attributes_assignments parent, wrapped
223
+ end
224
+
225
+ return parent
226
+ elsif !parent.empty?
227
+ error :assignments_comma
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,96 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @Root
6
+ module Root
7
+ # Check if we match any root element. This is the starting point of the
8
+ # parser and it checks for any elements which can be considered as root
9
+ #
10
+ # @param parent [Node] Parent element to append to
11
+ # @param min_indent [Integer] Minimum indent to which the root responds
12
+ #
13
+ def root(parent)
14
+ # Starting line of the root method
15
+ starting_line = @current_line
16
+
17
+ # Skip any whitespace at the beginning of the document
18
+ accept_unstripped :newline
19
+
20
+ if(element = define(parent))
21
+ # Add definition elements to the root's knowledgebase and add elements
22
+ # and evaluate elements to their parent elements
23
+ if parent.is_a? Nodes::Root
24
+ @root.themes[Engine::DEFAULT_THEME][element.name] = element
25
+ elsif parent.is_a? Nodes::Theme
26
+ @root.themes[parent.name][element.name] = element
27
+ else
28
+ error :definition
29
+ end
30
+ elsif(element = theme(parent))
31
+ # Theme nodes cannot be nested, so we accept only a root parent and
32
+ # we add the node in the themes hash and we allow reopening the theme
33
+ # and adding more definitions to it, ruby style
34
+ if parent.is_a? Nodes::Root
35
+ @root.themes[element.name] ||= {}
36
+ else
37
+ error :theme
38
+ end
39
+ elsif (element = node(parent) ||
40
+ text(parent) ||
41
+ comment(parent) ||
42
+ control(parent) ||
43
+ evaluate(parent) ||
44
+ filter_element(parent) ||
45
+ html_text(parent))
46
+ parent.push element
47
+ elsif (element = block_yield(parent))
48
+ # Handle yield node by searching for ther definition parent node
49
+ yield_parent = element.parent
50
+ begin
51
+ until [Nodes::Define, Nodes::Root].include? yield_parent.class
52
+ yield_parent = yield_parent.parent
53
+ end
54
+ rescue NoMethodError
55
+ error :yield_parent
56
+ end
57
+
58
+ # Add the yield element to the parent and keep a pointer to the parent
59
+ # for each yield element to avoid recursive searching as much as
60
+ # possible
61
+ parent.push element
62
+ yield_parent.yields << parent
63
+ elsif (element = block(parent))
64
+ # Blocks will be set as separate entities for the parent element in
65
+ # order to replace them fast in the definition yields
66
+ parent.blocks[element.name] ||= []
67
+ parent.blocks[element.name] << element
68
+ end
69
+
70
+ next_indent = lookahead(:indent_lookahead, false).length
71
+ if element
72
+ if next_indent > element.indent
73
+ # If we have an indentation which is greater than the current one,
74
+ # set the current element as a root element and add children to it
75
+ root element
76
+ elsif next_indent < element.indent
77
+ # If we have an indentation which is smaller than the current one,
78
+ # go through the current element's ancestors until we find one with
79
+ # the same indentation as the next element
80
+ next_parent = element.parent
81
+ while next_parent.indent > next_indent
82
+ next_parent = next_parent.parent
83
+ end
84
+ next_parent = next_parent.parent
85
+
86
+ root next_parent
87
+ else
88
+ # If the indentation level stays the same, the parent element will
89
+ # remain the current one
90
+ root parent
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,114 @@
1
+ # @SugarCube
2
+ module Opulent
3
+ # @Parser
4
+ module Parser
5
+ # @Text
6
+ module Text
7
+ # Match one line or multiline, escaped or unescaped text
8
+ #
9
+ def text(parent, indent = nil, only_multiline = true)
10
+ indent = indent || accept_unstripped(:indent) || ""
11
+
12
+ # Try to see if we can match a multiline operator. If we can accept only
13
+ # multiline, which is the case for filters, undo the operation.
14
+ if accept_line :multiline
15
+ multiline = true
16
+ elsif only_multiline
17
+ return undo indent unless lookahead :print_lookahead
18
+ end
19
+
20
+ indent = indent.size
21
+
22
+ # Unescaped Print Eval
23
+ if (text_feed = accept_unstripped :unescaped_print)
24
+ text_node = @create.print(text_feed.strip, false, parent, indent)
25
+ # Escaped Print Eval
26
+ elsif (text_feed = accept_unstripped :escaped_print)
27
+ text_node = @create.print(text_feed.strip, true, parent, indent)
28
+ # Unescaped Text
29
+ elsif (text_feed = accept_unstripped :unescaped_text)
30
+ text_node = @create.text(text_feed.strip, false, parent, indent)
31
+ # Escaped Text
32
+ elsif (text_feed = accept_unstripped :escaped_text)
33
+ text_node = @create.text(text_feed.strip, true, parent, indent)
34
+ else
35
+ # Undo by adding the found intentation back
36
+ undo indent
37
+ return nil
38
+ end
39
+
40
+
41
+ if text_node
42
+ text_node
43
+ if multiline
44
+ text_node.value += accept_unstripped(:newline) || ""
45
+ text_node.value += get_indented_lines(indent)
46
+
47
+ text_node
48
+ else
49
+ accept_unstripped :newline
50
+
51
+ text_node.value.strip!
52
+ text_node.value = text_node.value[1..-1] if text_node.value[0] == '\\'
53
+ text_node.value.size > 0 ? text_node : nil
54
+ end
55
+ else
56
+ return nil
57
+ end
58
+ end
59
+
60
+ # Match one line or multiline, escaped or unescaped text
61
+ #
62
+ def html_text(parent)
63
+ indent = accept_unstripped(:indent) || ""
64
+ indent_size = indent.size
65
+
66
+ if (text_feed = accept_unstripped :html_text)
67
+ text_node = @create.text(text_feed.strip, false, parent, indent_size)
68
+ accept_unstripped :newline
69
+ pp text_feed
70
+ return text_node
71
+ else
72
+ return undo indent
73
+ end
74
+ end
75
+
76
+ # Match a whitespace by preventing code trimming
77
+ #
78
+ def whitespace(required = false)
79
+ accept_unstripped :whitespace, required
80
+ end
81
+
82
+ # Gather all the lines which have higher indentation than the one given as
83
+ # parameter and put them into the buffer
84
+ #
85
+ # @param indentation [Fixnum] parent node strating indentation
86
+ #
87
+ def get_indented_lines(indent)
88
+ buffer = ''
89
+
90
+ # Get the next indentation after the parent line
91
+ # and set it as primary indent
92
+ first_indent = lookahead(:indent_lookahead, false).size
93
+ next_indent = first_indent
94
+
95
+ # While the indentation is smaller, add the line feed to our buffer
96
+ while next_indent > indent
97
+ # Get leading whitespace trimmed with first_indent's size
98
+ next_line_indent = accept_unstripped(:indent)[first_indent..-1] || ""
99
+ next_line_indent = next_line_indent.size
100
+
101
+ # Add next line feed, prepend the indent and append the newline
102
+ buffer += " " * next_line_indent if next_line_indent > 0
103
+ buffer += accept_unstripped(:line_feed) || ""
104
+ buffer += accept_unstripped(:newline) || ""
105
+
106
+ # Get next indentation and repeat
107
+ next_indent = lookahead(:indent_lookahead, false).size
108
+ end
109
+
110
+ buffer
111
+ end
112
+ end
113
+ end
114
+ end