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,47 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Logger
4
+ module Logger
5
+ # @Singleton
6
+ class << self
7
+ # Color the input text with the chosen color
8
+ #
9
+ # @param text [String] the string that will be colored
10
+ # @param color_code [String] preset code for certain colors
11
+ #
12
+ def colorize(text, color_code)
13
+ require_windows_libs
14
+ "#{color_code}#{text}\e[0m"
15
+ end
16
+
17
+ # Colors available in the terminal
18
+ #
19
+ def black(text); colorize(text, "\e[30m"); end
20
+ def red(text); colorize(text, "\e[31m"); end
21
+ def green(text); colorize(text, "\e[32m"); end
22
+ def yellow(text); colorize(text, "\e[33m"); end
23
+ def blue(text); colorize(text, "\e[34m"); end
24
+ def magenta(text); colorize(text, "\e[35m"); end
25
+ def cyan(text); colorize(text, "\e[36m"); end
26
+ def white(text); colorize(text, "\e[37m"); end
27
+ def default(text); colorize(text, "\e[38m"); end
28
+
29
+ # Require windows libraries for ANSI Console output
30
+ #
31
+ def require_windows_libs
32
+ begin
33
+ require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /win32/
34
+ rescue LoadError
35
+ raise 'You must run "gem install win32console" to use Opulent\'s
36
+ error reporting on Windows.'
37
+ end
38
+ end
39
+
40
+ # Pretty print Nodes with their important details
41
+ #
42
+ def pretty_print(model, indent = 0)
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,196 @@
1
+ require_relative 'parser/block.rb'
2
+ require_relative 'parser/comment.rb'
3
+ require_relative 'parser/control.rb'
4
+ require_relative 'parser/define.rb'
5
+ require_relative 'parser/eval.rb'
6
+ require_relative 'parser/expression.rb'
7
+ require_relative 'parser/filter.rb'
8
+ require_relative 'parser/node.rb'
9
+ require_relative 'parser/root.rb'
10
+ require_relative 'parser/text.rb'
11
+
12
+ # @Opulent
13
+ module Opulent
14
+ # @Parser
15
+ class Parser
16
+ attr_reader :type, :value, :options, :children, :indent
17
+
18
+ # All node Objects (Array) must follow the next convention in order
19
+ # to make parsing faster
20
+ #
21
+ # [:node_type, :value, :attributes, :children, :indent]
22
+ #
23
+ def initialize
24
+ @type = 0
25
+ @value = 1
26
+ @options = 2
27
+ @children = 3
28
+ @indent = 4
29
+ end
30
+
31
+ # Initialize the parsing process by splitting the code into lines and
32
+ # instantiationg parser variables with their default values
33
+ #
34
+ # @param code [String] Opulent code that needs to be analyzed
35
+ #
36
+ # @return Nodes array
37
+ #
38
+ def parse(code)
39
+ # Split the code into lines and parse them one by one
40
+ @code = code.lines
41
+
42
+ # Node definitions encountered up to the current point
43
+ @definitions = {}
44
+
45
+ # Current line index
46
+ @i = -1
47
+
48
+ # Initialize root node
49
+ @root = [:root, nil, {}, [], -1]
50
+
51
+ # Get all nodes starting from the root element
52
+ root @root
53
+ end
54
+
55
+ # Check and accept or reject a given token as long as we have tokens
56
+ # remaining. Shift the code with the match length plus any extra character
57
+ # count around the capture group
58
+ #
59
+ # @param token [RegEx] Token to be accepted by the parser
60
+ # @param required [Boolean] Expect the given token syntax
61
+ # @param strip [Boolean] Left strip the current code to remove whitespace
62
+ #
63
+ def accept(token, required = false, strip = false)
64
+ # Consume leading whitespace if we want to ignore it
65
+ accept :whitespace if strip
66
+
67
+ # We reached the end of the parsing process and there are no more lines
68
+ # left to parse
69
+ return nil unless @line
70
+
71
+ # Match the token to the current line. If we find it, return the match.
72
+ # If it is required, signal an :expected error
73
+ if (match = @line[@offset..-1].match(Tokens[token]))
74
+ # Advance current offset with match length
75
+ @offset += match[0].size
76
+
77
+ return match[0]
78
+ elsif required
79
+ error :expected, token
80
+ end
81
+ end
82
+
83
+ # Helper method which automatically sets the stripped options to true, so that we
84
+ # do not have to explicitly specify it
85
+ #
86
+ # @param token [RegEx] Token to be accepted by the parser
87
+ # @param required [Boolean] Expect the given token syntax
88
+ #
89
+ def accept_stripped(token, required = false)
90
+ accept(token, required, true)
91
+ end
92
+
93
+ # Check if the lookahead matches the chosen regular expression
94
+ #
95
+ # @param token [RegEx] Token to be checked by the parser
96
+ #
97
+ def lookahead(token)
98
+ return nil unless @line
99
+
100
+ # Check if we match the token to the current line.
101
+ @line[@offset..-1].match Tokens[token]
102
+ end
103
+
104
+ # Check if the lookahead matches the chosen regular expression on the
105
+ # following line which needs to be parsed
106
+ #
107
+ # @param token [RegEx] Token to be checked by the parser
108
+ #
109
+ def lookahead_next_line(token)
110
+ return nil unless @code[@i + 1]
111
+
112
+ # Check if we match the token to the current line.
113
+ @code[@i + 1].match Tokens[token]
114
+ end
115
+
116
+ # Undo a found match by removing the token from the consumed code and
117
+ # adding it back to the code chunk
118
+ #
119
+ # @param match [String] Matched string to be undone
120
+ #
121
+ def undo(match)
122
+ unless match.empty?
123
+ @offset -= match.size
124
+ return nil
125
+ end
126
+ end
127
+
128
+ # Allow expressions to continue on a new line in certain conditions
129
+ #
130
+ def accept_newline
131
+ if @line[@offset..-1].strip.empty?
132
+ @line = @code[(@i += 1)]
133
+ @offset = 0
134
+ end
135
+ end
136
+
137
+ # Give an explicit error report where an unexpected sequence of tokens
138
+ # appears and give indications on how to solve it
139
+ #
140
+ # @param message [Symbol] Error message to display to the user
141
+ #
142
+ def error(error, *data)
143
+ message = case error
144
+ when :unknown_node_type
145
+ "An unknown node type has been encountered at:\n\n#{Logger.red @line}"
146
+ when :expected
147
+ data[0] = "#{Tokens.bracket data[0]}" if [:'(', :'{', :'[', :'<'].include? data[0]
148
+ "Expected to find a :#{data[0]} token at: \n\n#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
149
+ when :root
150
+ "Unknown node type encountered on line #{@i+1} of input at:\n\n" +
151
+ "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
152
+ when :assignments_colon
153
+ "Unexpected end of element attributes reached on line #{@i+1} of input.\n\n" +
154
+ "Expected to find an attribute at:\n\n" +
155
+ "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
156
+ when :assignments_comma
157
+ "Unexpected end of element attributes reached on line #{@i+1} of input.\n\n" +
158
+ "Expected to find an attribute value at:\n\n" +
159
+ "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
160
+ when :expression
161
+ "Unexpected end of expression reached on line #{@i+1} of input.\n\n" +
162
+ "Expected to find another expression term at:\n\n" +
163
+ "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
164
+ when :control_child
165
+ "Unexpected control structure child found on line #{@i+1} of input.\n\n" +
166
+ "Expected to find a parent #{data[0]} structure at:\n\n" +
167
+ "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
168
+ when :case_children
169
+ "Unexpected control structure child found on line #{@i+1} of input.\n\n" +
170
+ "Case structure cannot have any child elements at:\n\n" +
171
+ "#{@code[@i-1][0..@offset-1]}#{Logger.red @code[@i][@offset..-1].rstrip}"
172
+ when :whitespace_expression
173
+ "Unexpected end of expression reached on line #{@i+1} of input.\n\n" +
174
+ "Please use paranthesis for method parameters at:\n\n" +
175
+ "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
176
+ when :definition
177
+ "Unexpected start of definition on line #{@i+1} of input.\n\n" +
178
+ "Found a definition inside another definition or element at:\n\n" +
179
+ "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
180
+ when :self_enclosing
181
+ "Unexpected content found after self enclosing node on line #{@i+1} of input at:\n\n" +
182
+ "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
183
+ when :self_enclosing_children
184
+ "Unexpected child elements found for self enclosing node on line #{data[0]+1} of input at:\n\n" +
185
+ "#{@code[data[0]]}#{Logger.red @code[data[0] + 1]}"
186
+ else
187
+ "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
188
+ end
189
+
190
+ # Reconstruct lines to display where errors occur
191
+ fail "\n\nOpulent " + Logger.red("[Parser Error]") +
192
+ "\n---\n" +
193
+ "#{message}\n\n\n"
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,56 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Match a yield with a explicit or implicit target
6
+ #
7
+ # yield target
8
+ #
9
+ # @param parent [Node] Parent node to which we append the definition
10
+ #
11
+ def block_yield(parent, indent)
12
+ if accept :yield
13
+ # Get definition name
14
+ if(yield_name = accept_stripped(:yield_identifier))
15
+ yield_name = yield_name.strip.to_sym
16
+ else
17
+ yield_name = Settings::DefaultYield
18
+ end
19
+
20
+ # Consume the newline from the end of the element
21
+ error :yield unless accept(:line_feed).strip.empty?
22
+
23
+ # Create a new node
24
+ yield_node = [:yield, yield_name, {}, [], indent]
25
+
26
+ parent[@children] << yield_node
27
+ end
28
+ end
29
+
30
+ # Match a block with a explicit target
31
+ #
32
+ # block target
33
+ #
34
+ # @param parent [Node] Parent node to which we append the definition
35
+ #
36
+ def block(parent, indent)
37
+ if accept :block
38
+ # Get definition name
39
+ if(block_name = accept_stripped(:yield_identifier))
40
+ block_name = block_name.strip.to_sym
41
+ else
42
+ block_name = Settings::DefaultYield
43
+ end
44
+
45
+ # Consume the newline from the end of the element
46
+ error :block unless accept(:line_feed).strip.empty?
47
+
48
+ # Create a new node
49
+ block_node = [:block, block_name, {}, [], indent]
50
+ root block_node, indent
51
+
52
+ parent[@children] << block_node
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Match one line or multiline comments
6
+ #
7
+ def comment(parent, indent)
8
+ if (accept :comment)
9
+ multiline = true if (accept :comment)
10
+
11
+ buffer = accept(:line_feed)
12
+ buffer += accept(:newline) || ""
13
+ buffer += get_indented_lines indent if multiline
14
+
15
+
16
+ # If we have a comment which is visible in the output, we will
17
+ # create a new comment element. Otherwise, we ignore the current
18
+ # gathered text and we simply begin the root parsing again
19
+ if buffer[0] == '!'
20
+ parent[@children] << [:comment, buffer[1..-1].strip, {}, nil, indent]
21
+ end
22
+
23
+ return parent
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,112 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Match an if-else control structure
6
+ #
7
+ def control(parent, indent)
8
+ # Accept eval or multiline eval syntax and return a new node,
9
+ if (structure = accept(:control))
10
+ structure = structure.to_sym
11
+
12
+ # Handle each and the other control structures
13
+ condition = accept(:line_feed).strip
14
+
15
+ # Process each control structure condition
16
+ if structure == :each
17
+ # Check if arguments provided correctly
18
+ error :each_arguments unless condition.match Tokens[:each_pattern]
19
+
20
+ # Split provided arguments for the each structure
21
+ condition = condition.split('in').map(&:strip)
22
+ condition[0] = condition[0].split(',').map(&:strip).map(&:to_sym)
23
+ end
24
+
25
+ # Else and default structures are not allowed to have any condition
26
+ # set and the other control structures require a condition
27
+ if structure == :else
28
+ error :condition_exists unless condition.empty?
29
+ else
30
+ error :condition_missing if condition.empty?
31
+ end
32
+
33
+ # Add the condition and create a new child to the control parent.
34
+ # The control parent keeps condition -> children matches for our
35
+ # document's content
36
+ add_options = Proc.new do |control_parent|
37
+ control_parent.value << condition
38
+ control_parent.children << []
39
+
40
+ root control_parent
41
+ end
42
+
43
+ # If the current control structure is a parent which allows multiple
44
+ # branches, such as an if or case, we create an array of conditions
45
+ # which can be matched and an array of children belonging to each
46
+ # conditional branch
47
+ if [:if, :unless].include? structure
48
+ # Create the control structure and get its child nodes
49
+ control_structure = [structure, [condition], {}, [], indent]
50
+ root control_structure, indent
51
+
52
+ # Turn children into an array because we allow multiple branches
53
+ control_structure[@children] = [control_structure[@children]]
54
+
55
+ # Add it to the parent node
56
+ parent[@children] << control_structure
57
+
58
+ elsif structure == :case
59
+ # Create the control structure and get its child nodes
60
+ control_structure = [structure, [], {condition: condition}, [], indent]
61
+
62
+
63
+ # Add it to the parent node
64
+ parent[@children] << control_structure
65
+
66
+ # If the control structure is a child structure, we need to find the
67
+ # node it belongs to amont the current parent. Search from end to
68
+ # beginning until we find the node parent
69
+ elsif control_child structure
70
+ # During the search, we try to find the matching parent type
71
+ unless control_parent(structure).include? parent[@children][-1][@type]
72
+ error :control_child, control_parent(structure)
73
+ end
74
+
75
+ # Gather child elements for current structure
76
+ control_structure = [structure, [condition], {}, [], indent]
77
+ root control_structure, indent
78
+
79
+ # Add the new condition and children to our parent structure
80
+ parent[@children][-1][@value] << condition
81
+ parent[@children][-1][@children] << control_structure[@children]
82
+
83
+ # When our control structure isn't a complex composite, we create
84
+ # it the same way as a normal node
85
+ else
86
+ control_structure = [structure, condition, {}, [], indent]
87
+ root control_structure, indent
88
+
89
+ parent[@children] << control_structure
90
+ end
91
+ end
92
+ end
93
+
94
+ # Check if the current control structure requires a parent node and
95
+ # return the parent's node type
96
+ #
97
+ def control_child(structure)
98
+ [:else, :elsif, :when].include? structure
99
+ end
100
+
101
+ # Check if the current control structure requires a parent node and
102
+ # return the parent's node type
103
+ #
104
+ def control_parent(structure)
105
+ case structure
106
+ when :else then [:if, :unless, :case]
107
+ when :elsif then [:if]
108
+ when :when then [:case]
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,30 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Check if we match a new node definition to use within our page.
6
+ #
7
+ # Definitions will not be recursive because, by the time we parse
8
+ # the definition children, the definition itself is not in the
9
+ # knowledgebase yet.
10
+ #
11
+ # However, we may use previously defined nodes inside new definitions,
12
+ # due to the fact that they are known at parse time.
13
+ #
14
+ # @param nodes [Array] Parent node to which we append to
15
+ #
16
+ def define(parent, indent)
17
+ if(match = accept :def)
18
+ # Process data
19
+ name = accept(:node, :*).to_sym
20
+
21
+ # Create node
22
+ definition = [:def, name, {parameters: attributes}, [], indent]
23
+ root(definition, indent)
24
+
25
+ # Add to parent
26
+ @definitions[name] = definition
27
+ end
28
+ end
29
+ end
30
+ end