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