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