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,69 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Nodes
4
+ module Nodes
5
+ # @NodeFactory
6
+ class Helper
7
+ def root
8
+ Root.new
9
+ end
10
+
11
+ def node(name = '', atts = {}, parent = nil, indent = 0, children = [])
12
+ Node.new name, atts, parent, indent, children
13
+ end
14
+
15
+ def block_yield(name = '', parent = nil, indent = 0, children = [])
16
+ Yield.new name, {}, parent, indent, children
17
+ end
18
+
19
+ def block(name = '', parent = nil, indent = 0, children = [])
20
+ Block.new name, {}, parent, indent, children
21
+ end
22
+
23
+ def theme(name = '', parent = nil, indent = 0, children = [])
24
+ Theme.new name, parent, indent, children
25
+ end
26
+
27
+ def filter(name = '', atts = {}, parent = nil, indent = 0, value = '')
28
+ Filter.new name, atts, parent, indent, value
29
+ end
30
+
31
+ def evaluate(value, parent = nil, indent = 0, children = [])
32
+ Evaluate.new value, parent, indent, children
33
+ end
34
+
35
+ def control(name, value = '', parent = nil, indent = 0, children = [[]])
36
+ case name
37
+ when :if, :unless
38
+ CnditionalControl.new name, value, parent, indent, children
39
+ when :case
40
+ CaseControl.new name, value, parent, indent, children.first
41
+ when :while, :until
42
+ LoopControl.new name, value, parent, indent, children.first
43
+ when :each
44
+ EachControl.new name, value, parent, indent, children.first
45
+ end
46
+ end
47
+
48
+ def expression(value = [])
49
+ Expression.new value
50
+ end
51
+
52
+ def definition(name, params = [], parent = nil, indent = 0)
53
+ Define.new name, params, parent, indent
54
+ end
55
+
56
+ def text(value = nil, escaped = true, parent = nil, indent = 0)
57
+ Text.new value, escaped, parent, indent
58
+ end
59
+
60
+ def print(value = nil, escaped = true, parent = nil, indent = 0)
61
+ Print.new value, escaped, parent, indent
62
+ end
63
+
64
+ def comment(value = nil, parent = nil, indent = 0)
65
+ Comment.new value, parent, indent
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,101 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Nodes
4
+ module Nodes
5
+ # @Node
6
+ #
7
+ # Node class used to describe a HTML Element used for building a
8
+ # page model during the parsing process
9
+ #
10
+ class Node
11
+ # Allow direct access to node variables
12
+ attr_accessor :name, :attributes, :children, :whitespace, :parent, :indent, :extension, :theme, :blocks, :yields, :self_enclosing
13
+
14
+ # Initialize node instance variables
15
+ #
16
+ # @param name [String] name of the html node
17
+ # @param indentation [Fixnum] node indentation for restructuring
18
+ # @param attributes [Hash] stores key="value" attributes
19
+ # @param children [Array] collection of the node's child elements
20
+ #
21
+ def initialize(name = '', attributes = {}, parent = nil, indent = 0, children = [])
22
+ @name = name
23
+ @parent = parent
24
+ @indent = indent
25
+ @attributes = attributes
26
+ @children = children
27
+ @extension = nil
28
+ @theme = Engine::DEFAULT_THEME
29
+ @self_enclosing = Engine::SELF_ENCLOSING.include? name
30
+ @whitespace = [nil, nil]
31
+ @blocks = {
32
+ Engine::DEFAULT_YIELD => @children
33
+ }
34
+ @yields = []
35
+ end
36
+
37
+ # Add a new node to the nodes array
38
+ #
39
+ def push(node)
40
+ @children << node
41
+ self
42
+ end
43
+
44
+ # Return extended attributes if an extension was set using the [extend]
45
+ # identifier of the node
46
+ #
47
+ def extend_attributes(attributes, extension)
48
+ return attributes if extension.nil?
49
+
50
+ extension.each do |key, value|
51
+ case attributes[key]
52
+ when Array
53
+ attributes[key] = (attributes[key] << value).flatten
54
+ when Hash
55
+ attributes[key] = value.merge attributes[key]
56
+ when nil
57
+ attributes[key] = value
58
+ end
59
+ end
60
+
61
+ attributes
62
+ end
63
+
64
+ # Node evaluation method which goes through all the child nodes and evaluates
65
+ # them using their own eval method
66
+ #
67
+ def evaluate(context)
68
+ # Set attributes for current context
69
+ attributes = Runtime.attributes @attributes, @extension, context
70
+
71
+ # Evaluate all provided blocks
72
+ blocks = Hash[@blocks.map{ |key, value|
73
+ children = value.map do |child|
74
+ child.evaluate context
75
+ end.flatten.compact
76
+
77
+ [key, children]
78
+ }]
79
+
80
+ # Map self to a different node and set evaluated children nodes and
81
+ # evaluated attributes and add a pointer to the children block
82
+ mapped_node = self.dup
83
+ mapped_node.attributes = attributes
84
+ mapped_node.blocks = blocks
85
+ mapped_node.children = mapped_node.blocks[Engine::DEFAULT_YIELD]
86
+
87
+ # Replace node with its definition if it has one
88
+ if Runtime[@theme]
89
+ if Runtime[@theme][@name] && @name != context.name
90
+ return Runtime.define mapped_node, attributes, context
91
+ else
92
+ return mapped_node
93
+ end
94
+ else
95
+ # Theme does not exist
96
+ Runtime.error :theme, @theme
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,62 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Nodes
4
+ module Nodes
5
+ # @Root
6
+ #
7
+ # HTML Root from which the construction of the final page starts. The root
8
+ # stores all the node definitions that will be replaced in the components.
9
+ #
10
+ class Root
11
+ # Allow direct access to node variables
12
+ attr_accessor :themes, :children, :indent, :blocks, :yields
13
+
14
+ # Initialize node instance variables
15
+ #
16
+ # @param definitions [Hash] node definitions to be replaced in children
17
+ # @param children [Array] collection of the node's child elements
18
+ #
19
+ def initialize
20
+ @themes = {
21
+ Engine::DEFAULT_THEME => {}
22
+ }
23
+ @children = []
24
+ @blocks = {}
25
+ @yields = {}
26
+ @indent = -1
27
+ end
28
+
29
+ # Add a new node to the nodes array
30
+ #
31
+ # @param node [Node] Node to be added to the parent
32
+ #
33
+ def push(node)
34
+ @children << node
35
+ self
36
+ end
37
+
38
+ # Shorthand theme access for root definitions
39
+ #
40
+ # @param key [Symbol] definition or theme name
41
+ #
42
+ def [](key)
43
+ if @themes.has_key? key
44
+ @themes[key]
45
+ else
46
+ @themes[Engine::DEFAULT_THEME][key]
47
+ end
48
+ end
49
+
50
+ # Evaluate all child nodes using the given context and the
51
+ # node definitions from the root knowledgebase
52
+ #
53
+ # @param context [Context] Call environment binding object
54
+ #
55
+ def evaluate(context)
56
+ @children.map do |node|
57
+ node.evaluate(context)
58
+ end.flatten.compact
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Nodes
4
+ module Nodes
5
+ # @Text
6
+ #
7
+ # The text class will output raw or escaped HTML text
8
+ #
9
+ class Text
10
+ # Allow direct access to literal value and type
11
+ attr_accessor :value, :escaped, :parent, :indent, :name
12
+
13
+ # Initialize literal instance variables
14
+ #
15
+ # @param value stores the literal's explicit value
16
+ #
17
+ def initialize(value = nil, escaped = true, parent = nil, indent = 0)
18
+ @value = value
19
+ @escaped = escaped
20
+ @parent = parent
21
+ @indent = indent
22
+ @name = :text
23
+ end
24
+
25
+ # Value evaluation method which returns the processed value of the
26
+ # literal
27
+ #
28
+ def evaluate(context)
29
+ value = context.evaluate "\"#{@value}\""
30
+
31
+ evaluated_text = self.dup
32
+ evaluated_text.value = @escaped ? Runtime.escape(value) : value
33
+ return evaluated_text
34
+ end
35
+ end
36
+
37
+ # @Print
38
+ #
39
+ # The print class will evaluate the ruby code and return a new text
40
+ # node containing the escaped or unescaped eval sequence
41
+ #
42
+ class Print < Text
43
+ # Value evaluation method which returns the processed value of the literal
44
+ #
45
+ def evaluate(context)
46
+ value = context.evaluate @value
47
+
48
+ evaluated_text = self.dup
49
+ evaluated_text.value = @escaped ? Runtime.escape(value) : value
50
+ return evaluated_text
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Nodes
4
+ module Nodes
5
+ # @Theme
6
+ #
7
+ # Node class used to describe a HTML Element used for building a
8
+ # page model during the parsing process
9
+ #
10
+ class Theme
11
+ # Allow direct access to node variables
12
+ attr_accessor :name, :indent, :parent, :children
13
+
14
+ # Initialize node instance variables
15
+ #
16
+ # @param name [String] name of the html node
17
+ # @param indentation [Fixnum] node indentation for restructuring
18
+ # @param attributes [Hash] stores key="value" attributes
19
+ # @param children [Array] collection of the node's child elements
20
+ #
21
+ def initialize(name = '', parent = nil, indent = 0, children = [])
22
+ @name = name
23
+ @parent = parent
24
+ @indent = indent
25
+ @children = children
26
+ end
27
+
28
+ # Add a new node to the nodes array
29
+ #
30
+ def push(node)
31
+ @children << node
32
+ self
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,252 @@
1
+ require_relative 'parser/root'
2
+ require_relative 'parser/node'
3
+ require_relative 'parser/eval'
4
+ require_relative 'parser/expression'
5
+ require_relative 'parser/define'
6
+ require_relative 'parser/text'
7
+ require_relative 'parser/filter'
8
+ require_relative 'parser/control'
9
+ require_relative 'parser/theme'
10
+ require_relative 'parser/block'
11
+ require_relative 'parser/comment'
12
+
13
+ # @Opulent
14
+ module Opulent
15
+ # @Lexer
16
+ module Parser
17
+ # @Singleton
18
+ class << self
19
+ # Include Opulent tokens regular expressions to be checked using the
20
+ # accept (or expect) method together with lookahead expressions
21
+ include Root
22
+ include Node
23
+ include Expression
24
+ include Evaluate
25
+ include Define
26
+ include Text
27
+ include Filter
28
+ include Control
29
+ include Theme
30
+ include Block
31
+ include Comment
32
+
33
+ # Analyze the input code and check for matching tokens.
34
+ # In case no match was found, throw an exception.
35
+ # In special cases, modify the token hash.
36
+ #
37
+ # @param code [String] Opulent code that needs to be analyzed
38
+ # @return Nodes array
39
+ #
40
+ def parse(code)
41
+ # Code to be parsed
42
+ @code = code
43
+
44
+ # Keeps track of consumed code
45
+ @consumed = ""
46
+
47
+ # Current line being parsed, used for error reporting
48
+ @current_line = 1
49
+
50
+ # Node creation wrapper for comprehensible node creation
51
+ @create = Nodes::Helper.new
52
+
53
+ # Create root node, our wrapper for all the HTML elements we use
54
+ # which knows how to evaluate each of them to output the final code
55
+ # The root also stores custom node definitions collection from which we
56
+ # replace the nodes which use them
57
+ @root = @create.root
58
+
59
+ # Loop until we have no tokens left to parse or we find an error
60
+ root @root
61
+
62
+ # We still have code left to parse
63
+ error :root unless @code.strip.empty?
64
+
65
+ return @root
66
+ end
67
+
68
+ # Accept and consume or reject a given token as long as we have tokens
69
+ # remaining. Shift the code with the match length plus any extra character
70
+ # count around the capture group
71
+ #
72
+ # @param identifier [RegEx] Token syntax to be accepted by the parser
73
+ # @param required [Boolean] Expect the given token syntax
74
+ # @param strip [Boolean] Left strip the current code to remove whitespace
75
+ #
76
+ def accept(identifier, required = false, strip = true)
77
+ # Get token from tokens knowledgebase
78
+ token = Tokens[identifier]
79
+
80
+ # If the token's capture group is smaller than the whole match,
81
+ # advance the code chunk with more spaces
82
+ extra = token[:extra] || 0
83
+
84
+ # Remove leading whitespace between expressions
85
+ if @code && strip
86
+ if (stripped = @code[/\A +/]) then @consumed += stripped end
87
+ @code.lstrip!
88
+ end
89
+
90
+ # Check to see if
91
+ if @code =~ token[:regex]
92
+ @current_line += 1 if identifier == :newline
93
+
94
+ shift = $1.size + extra
95
+
96
+ @consumed += @code[0..shift - 1] if shift > 0
97
+ @code = @code[shift..-1]
98
+
99
+ return $1
100
+ elsif required
101
+ error :expect, identifier
102
+ else
103
+ return nil
104
+ end
105
+ end
106
+
107
+ # Accept and consume or reject a given token as long as we have tokens
108
+ # remaining on the current line only. Shift the code with the match length
109
+ # plus any extra character count around the capture group
110
+ #
111
+ # @param identifier [RegEx] Token syntax to be accepted by the parser
112
+ # @param required [Boolean] Expect the given token syntax
113
+ # @param strip [Boolean] Left strip the current code to remove whitespace
114
+ #
115
+ def accept_line(identifier, required = false, strip = true)
116
+ # Get token from tokens knowledgebase
117
+ token = Tokens[identifier]
118
+
119
+ # If the token's capture group is smaller than the whole match,
120
+ # advance the code chunk with more spaces
121
+ extra = token[:extra] || 0
122
+
123
+ # Remove leading whitespace between expressions
124
+ if @code.lines.first && strip
125
+ if (stripped = @code.lines.first[/\A +/]) then @consumed += stripped end
126
+ @code.gsub! /\A +/, ''
127
+ end
128
+
129
+ # Check to see if
130
+ if @code.lines.first =~ token[:regex]
131
+ @current_line += 1 if identifier == :newline
132
+
133
+ shift = $1.size + extra
134
+
135
+ @consumed += @code[0..shift - 1] if shift > 0
136
+ @code = @code[shift..-1]
137
+
138
+ return $1
139
+ elsif required
140
+ error :expect, identifier
141
+ else
142
+ return nil
143
+ end
144
+ end
145
+
146
+ # Wrapper method for accepting unstripped tokens such as whitespace or a
147
+ # certain required sequence directly
148
+ #
149
+ # @param identifier [RegEx] Token syntax to be accepted by the parser
150
+ # @param required [Boolean] Expect the given token syntax
151
+ #
152
+ def accept_unstripped(identifier, required = false)
153
+ accept(identifier, required, false)
154
+ end
155
+
156
+ # Wrapper method for accepting unstripped tokens such as whitespace or a
157
+ # certain required sequence directly on the current line only
158
+ #
159
+ # @param identifier [RegEx] Token syntax to be accepted by the parser
160
+ # @param required [Boolean] Expect the given token syntax
161
+ #
162
+ def accept_line_unstripped(identifier, required = false)
163
+ accept_line(identifier, required, false)
164
+ end
165
+
166
+ # Look ahead in the current code to see if we can match an input token.
167
+ # No modifications will be done to the code.
168
+ #
169
+ # @param token [RegEx] Token syntax to be accepted by the parser
170
+ # @param strip [Boolean] Left strip the current code to remove whitespace
171
+ #
172
+ def lookahead(token, strip = true)
173
+ # Get token from tokens knowledgebase
174
+ token = Tokens[token]
175
+
176
+ # We don't want to modify anything in the code directly, so we use a
177
+ # local code variable
178
+ code = @code
179
+
180
+ # Remove leading whitespace between expressions
181
+ code = code.lstrip if code && strip
182
+
183
+ # Check to see if
184
+ if code =~ token[:regex]
185
+ return $~[:capture]
186
+ else
187
+ return nil
188
+ end
189
+ end
190
+
191
+ # Undo a found match by removing the token from the consumed code and
192
+ # adding it back to the code chunk
193
+ #
194
+ # @param match [String] Matched string to be undone
195
+ #
196
+ def undo(match)
197
+ unless match.empty?
198
+ @consumed = @consumed[0..-match.length]
199
+ @code = match + @code
200
+ return nil
201
+ end
202
+ end
203
+
204
+ # Give an explicit error report where an unexpected sequence of tokens
205
+ # appears and give indications on how to solve it
206
+ #
207
+ # @param context [Symbol] Context name in which the error happens
208
+ # @param data [Array] Additional error information
209
+ #
210
+ def error(context, *data)
211
+ consumed = @consumed.lines.last.strip if @consumed.lines.last
212
+ code = @code.empty? ? ' END' : @code.lines.first.strip
213
+
214
+ message = case context
215
+ when :root
216
+ "Unknown node type encountered on line #{@current_line} of input at:\n\n" +
217
+ "#{consumed}" + Logger.red(code)
218
+ when :expect
219
+ "Expected to find token :#{data[0]} on line #{@current_line} of input at:\n\n" +
220
+ "#{consumed}" + Logger.red(code)
221
+ when :assignments_colon
222
+ "Unexpected end of element attributes reached on line #{@current_line} of input.\n\n" +
223
+ "Expected to find an attribute at:\n\n" +
224
+ "#{consumed}" + Logger.red(code)
225
+ when :assignments_comma
226
+ "Unexpected end of element attributes reached on line #{@current_line} of input.\n\n" +
227
+ "Expected to find an attribute value at:\n\n" +
228
+ "#{consumed}" + Logger.red(code)
229
+ when :expression
230
+ "Unexpected end of expression reached on line #{@current_line} of input.\n\n" +
231
+ "Expected to find another expression term at:\n\n" +
232
+ "#{consumed}" + Logger.red(code)
233
+ when :whitespace_expression
234
+ "Unexpected end of expression reached on line #{@current_line} of input.\n\n" +
235
+ "Please use paranthesis for method parameters at:\n\n" +
236
+ "#{consumed}" + Logger.red(code)
237
+ when :definition
238
+ "Unexpected start of definition on line #{@current_line - 1} of input.\n\n" +
239
+ "Found a definition inside another definition or element at:\n\n" +
240
+ (@consumed.lines[-2] || "") + Logger.red(consumed + "\n " + code)
241
+ else
242
+ "#{consumed}" + Logger.red(code)
243
+ end
244
+
245
+ # Reconstruct lines to display where errors occur
246
+ fail "\n\nOpulent " + Logger.red("[Parser Error]") + "\n---\n" +
247
+ "A parsing error has been encountered in the \"#{context}\" context.\n" +
248
+ "#{message}\n\n\n"
249
+ end
250
+ end
251
+ end
252
+ end