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