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,48 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Analyze the input code and check for matching tokens.
6
+ # In case no match was found, throw an exception.
7
+ # In special cases, modify the token hash.
8
+ #
9
+ # @param nodes [Array] Parent node to which we append to
10
+ #
11
+ def root(parent = @root, min_indent = -1)
12
+ while(@line = @code[(@i += 1)])
13
+ # Skip to next iteration if we have a blank line
14
+ if @line =~ /\A\s*\Z/ then next end
15
+
16
+ # Reset the line offset
17
+ @offset = 0
18
+
19
+ # Parse the current line by trying to match each node type towards it
20
+ # Add current indentation to the indent stack
21
+ indent = accept(:indent).size
22
+
23
+ # Stop using the current parent as root if it does not match the
24
+ # minimum indentation requirements
25
+ unless min_indent < indent
26
+ @i -= 1; break
27
+ end
28
+
29
+ # Try the main Opulent node types and process each one of them using
30
+ # their matching evaluation procedure
31
+ current_node = node(parent, indent) ||
32
+ text(parent, indent) ||
33
+ comment(parent, indent) ||
34
+ define(parent, indent) ||
35
+ control(parent, indent) ||
36
+ evaluate(parent, indent) ||
37
+ filter(parent, indent) ||
38
+ block_yield(parent, indent) ||
39
+ block(parent, indent)
40
+
41
+ # Throw an error if we couldn't find a valid node
42
+ error :unknown_node_type unless current_node
43
+ end
44
+
45
+ return parent
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,127 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Parser
4
+ class Parser
5
+ # Match one line or multiline, escaped or unescaped text
6
+ #
7
+ # @param parent [Array] Parent node element
8
+ # @param indent [Fixnum] Size of the current indentation
9
+ # @param multiline_or_print [Boolean] Allow only multiline text or print
10
+ #
11
+ def text(parent, indent = nil, multiline_or_print = true)
12
+ # Try to see if we can match a multiline operator. If we can accept_stripped only
13
+ # multiline, which is the case for filters, undo the operation.
14
+ if accept :multiline
15
+ multiline = true
16
+ elsif multiline_or_print
17
+ return nil unless lookahead :print_lookahead
18
+ end
19
+
20
+ # Get text node type
21
+ type = if accept(:print) then :print else :text end
22
+
23
+ # Check if the text or print node is escaped or unescaped
24
+ escaped = accept(:unescaped_value) ? false : true
25
+
26
+ # Get text value
27
+ value = accept :line_feed
28
+ value = value[1..-1] if value[0] == '\\'
29
+
30
+ # Create the text node using input data
31
+ text_node = [:plain, type, {value: value.strip, escaped: escaped, evaluate: false}, nil, indent]
32
+
33
+ # If we have a multiline node, get all the text which has higher
34
+ # indentation than our indentation node.
35
+ if multiline
36
+ text_node[@options][:value] += accept(:newline) || ""
37
+ text_node[@options][:value] += get_indented_lines(indent)
38
+ text_node[@options][:value].strip!
39
+ elsif value.empty?
40
+ # If our value is empty and we're not going to add any more lines to
41
+ # our buffer, skip the node
42
+ return nil
43
+ end
44
+
45
+ if text_node[@options][:value] =~ Settings::InterpolationCheck
46
+ text_node[@options][:evaluate] = true
47
+ end
48
+
49
+ # Increase indentation if this is an inline text node
50
+ text_node[@indent] += Settings[:indent] unless multiline_or_print
51
+
52
+ # Add text node to the parent element
53
+ parent[@children] << text_node
54
+ end
55
+
56
+ # Match one line or multiline, escaped or unescaped text
57
+ #
58
+ def html_text(parent, indent)
59
+ if (text_feed = accept_stripped :html_text)
60
+ text_node = [:plain, :text, {value: text_feed.strip, escaped: false}, nil, indent]
61
+
62
+ parent[@children] << text_node
63
+ end
64
+ end
65
+
66
+ # Match a whitespace by preventing code trimming
67
+ #
68
+ def whitespace(required = false)
69
+ accept :whitespace, required
70
+ end
71
+
72
+ # Gather all the lines which have higher indentation than the one given as
73
+ # parameter and put them into the buffer
74
+ #
75
+ # @param indentation [Fixnum] parent node strating indentation
76
+ #
77
+ def get_indented_lines(indent)
78
+ buffer = ''
79
+
80
+ # Gather multiple blank lines between lines of text
81
+ blank_lines = Proc.new do
82
+ while lookahead_next_line :line_whitespace
83
+ @line = @code[(@i += 1)]
84
+ @offset = 0
85
+
86
+ buffer += accept :line_whitespace
87
+ end
88
+ end
89
+
90
+ # Get blank lines until we match something
91
+ blank_lines[]
92
+
93
+ # Get the next indentation after the parent line
94
+ # and set it as primary indent
95
+ next_indent = first_indent = (lookahead_next_line(:indent).to_s || "").size
96
+
97
+ # While the indentation is smaller, add the line feed to our buffer
98
+ while next_indent > indent
99
+ # Advance current line and reset offset
100
+ @line = @code[(@i += 1)]
101
+ @offset = 0
102
+
103
+ # Get leading whitespace trimmed with first_indent's size
104
+ next_line_indent = accept(:indent)[first_indent..-1] || ""
105
+ next_line_indent = next_line_indent.size
106
+
107
+ # Add next line feed, prepend the indent and append the newline
108
+ buffer += " " * next_line_indent if next_line_indent > 0
109
+ buffer += accept_stripped(:line_feed) || ""
110
+ buffer += accept_stripped(:newline) || ""
111
+
112
+ # Get blank lines until we match something
113
+ blank_lines[]
114
+
115
+ # Check the indentation on the following line. When we reach EOF,
116
+ # set the indentation to 0 and cause the loop to stop
117
+ if (next_indent = lookahead_next_line :indent)
118
+ next_indent = next_indent[0].size
119
+ else
120
+ next_indent = 0
121
+ end
122
+ end
123
+
124
+ return buffer
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,79 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Settings
4
+ module Settings
5
+ # Default yield target which is used for child block replacements
6
+ DefaultYield = :children
7
+
8
+ # Default yield target which is used for child block replacements
9
+ DefaultEachKey = :key
10
+
11
+ # Default yield target which is used for child block replacements
12
+ DefaultEachValue = :value
13
+
14
+ # List of self enclosing node elements
15
+ SelfEnclosing = %i(img link input meta br hr area base col command embed keygen param source track wbr)
16
+
17
+ # List of inline node parents which can be either inline or have complex
18
+ # structures inside of them, such as anchor tags
19
+ MultiNode = %i(a)
20
+
21
+ # List of inline node names
22
+ InlineNode = %i(text a span strong em br i b small label sub sup abbr var code kbd)
23
+
24
+ # Check whether text should or shouldn't be evaluated
25
+ InterpolationCheck = /(?<!\\)\#\{.*\}/
26
+
27
+ # Check if the attribute value is a bare string
28
+ EvaluationCheck = /\A(("((?:[^"\\]|\\.)*?)")|('(?:[^'\\]|\\.)*?')|true|false|nil)\Z/
29
+
30
+ # Shorthand attribute associations
31
+ Shorthand = {
32
+ :'.' => :class,
33
+ :'#' => :id,
34
+ :'&' => :name
35
+ }
36
+
37
+ # @Singleton
38
+ class << self
39
+ # Opulent runtime options
40
+ Defaults = {
41
+ pretty: true,
42
+ indent: 2,
43
+ dependency_manager: true
44
+ }
45
+
46
+ # Set defaults as initial options
47
+ @@options = Defaults
48
+
49
+ # Get an option at runtime
50
+ #
51
+ # @param name [Symbol] Identifier for the option
52
+ #
53
+ def [](name)
54
+ @@options[name]
55
+ end
56
+
57
+ # Set a new option at runtime
58
+ #
59
+ # @param name [Symbol] Identifier for the option
60
+ # @param value Option value to be set
61
+ #
62
+ def []=(name, value)
63
+ @@options[name] = value
64
+ end
65
+
66
+ # Update the engine options with the required option changes
67
+ #
68
+ # @param opts [Hash] Option extension hash
69
+ #
70
+ def update_settings(opts)
71
+ @@options = Defaults
72
+
73
+ opts.each do |key, value|
74
+ @@options[key] = value
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,67 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @OpulentTemplate
4
+ class OpulentTemplate < ::Tilt::Template
5
+ self.default_mime_type = 'text/html'
6
+
7
+ # Do whatever preparation is necessary to setup the underlying template
8
+ # engine. Called immediately after template data is loaded. Instance
9
+ # variables set in this method are available when #evaluate is called.
10
+ #
11
+ # Subclasses must provide an implementation of this method.
12
+ #
13
+ def prepare
14
+ # Set the file which is being evaluated
15
+ @options[:file] = eval_file
16
+
17
+ # Enable caching for the current rendered file
18
+ @options[:cache] = true
19
+
20
+ # Set up the rendering engine
21
+ @engine = ::Opulent.new @options
22
+ #@engine.update_options @options
23
+ end
24
+
25
+ # Execute the compiled template and return the result string. Template
26
+ # evaluation is guaranteed to be performed in the scope object with the
27
+ # locals specified and with support for yielding to the block.
28
+ #
29
+ # This method is only used by source generating templates. Subclasses that
30
+ # override render() may not support all features.
31
+ #
32
+ def evaluate(scope, locals, &block)
33
+ # if @engine.respond_to?(:precompiled_method_return_value, true)
34
+ # super
35
+ # else
36
+ # @engine.render(data, locals, &block)
37
+ # end
38
+ if @engine.preamble
39
+ super
40
+ else
41
+ locals[:scope] = scope
42
+ @engine.render(data, locals, &block)
43
+ end
44
+ end
45
+
46
+ # A string containing the (Ruby) source code for the template. The
47
+ # default Template#evaluate implementation requires either this
48
+ # method or the #precompiled method be overridden. When defined,
49
+ # the base Template guarantees correct file/line handling, locals
50
+ # support, custom scopes, proper encoding, and support for template
51
+ # compilation.
52
+ #
53
+ def precompiled_template(locals)
54
+ # This here should be evaluated in order to return the precompiled code
55
+ # as text to the user.
56
+ # For example:
57
+ # _buff = [] # This should be in preamble
58
+ # _buff << "<html>",
59
+ # _buff << compile('a * b')
60
+ # _buff << "</html>"
61
+ @engine.preamble
62
+ end
63
+ end
64
+
65
+ # Register Opulent to Tilt
66
+ ::Tilt.register OpulentTemplate, 'op', 'opl', 'opulent'
67
+ end
@@ -0,0 +1,166 @@
1
+ module Opulent
2
+ # Opulent Keywords
3
+ Keywords = %i(def block yield if else elsif unless case when each while until)
4
+
5
+ # @Tokens
6
+ class Tokens
7
+ # All tokens available within Opulent
8
+ #
9
+ @@tokens = {
10
+ # Indentation
11
+ indent: /\A */,
12
+
13
+ # Node
14
+ node: /\A\w+(\-\w+)*/,
15
+ node_lookahead: /\A(\w+(\-\w+)*)/,
16
+
17
+ # Shorthand attributes
18
+ shorthand: /\A[\.\#\&]/,
19
+ shorthand_lookahead: /\A[\.\#\&][a-zA-Z\_\(\"]/,
20
+
21
+ # Leading and trailing whitespace
22
+ leading_whitespace: /\A(\<\-)/,
23
+ leading_trailing_whitespace: /\A(\>)/,
24
+ trailing_whitespace: /\A(\-\>)/,
25
+
26
+ # Self enclosing node
27
+ self_enclosing: /\A\/(.*)/,
28
+
29
+ # Definition
30
+ def: /\Adef +/,
31
+
32
+ # Node Attributes
33
+ attributes_bracket: /\A\(\[\{/,
34
+ extend_attributes: /\A(\+)/,
35
+
36
+ # Attribute assignments
37
+ assignment: /\A *(\:|\=)/,
38
+ assignment_unescaped: /\A\~/,
39
+ assignment_terminator: /\A(\,|\;)\s*/,
40
+ assignment_lookahead: /\A *([a-zA-Z]([\-\_]?[a-zA-Z0-9]+)* *[\:\=] *)/,
41
+
42
+ # Node inline child
43
+ inline_child: /\A *\> */,
44
+
45
+ # Comments
46
+ comment: /\A\//,
47
+
48
+ # Intepreted filters
49
+ filter: /\A\:[a-zA-Z]([\-\_]?[a-zA-Z0-9]+)*/,
50
+
51
+ # Print nodes
52
+ print: /\A\=/,
53
+ print_lookahead: /\A *=/,
54
+
55
+ # Unescaped value
56
+ unescaped_value: /\A\~/,
57
+
58
+ # Multiline Text
59
+ multiline: /\A(\|)/,
60
+
61
+ # HTML Text
62
+ html_text: /\A(\<.+\>.*)/,
63
+
64
+ # Yield
65
+ yield: /\A(yield)/,
66
+ yield_identifier: /\A[a-zA-Z]([\_]?[a-zA-Z0-9]+)*/,
67
+
68
+ # Yield
69
+ block: /\A(block)/,
70
+
71
+ # Conditional Structures
72
+ control: /\A(if|elsif|else|unless|case|when|each|while|until)/,
73
+ each_pattern: /\A(\w+( *, *\w+)? +)?in +.+/,
74
+
75
+ # Text
76
+ text: /\A\|/,
77
+
78
+ # Brackets
79
+ round_bracket: /\A(\()/,
80
+ square_bracket: /\A(\[)/,
81
+ curly_bracket: /\A(\{)/,
82
+ angular_bracket: /\A(\<)/,
83
+
84
+ # Receive matching brackets for allowing multiple bracket types for
85
+ # element attributes
86
+ :brackets => /\A([\(\[\{])/,
87
+ :'(' => /\A(\))/,
88
+ :'[' => /\A(\])/,
89
+ :'{' => /\A(\})/,
90
+ :'<' => /\A(\>)/,
91
+
92
+ # Terminators
93
+ comma: /\A(\s*\,\s*)/,
94
+ colon: /\A(\s*\:\s*)/,
95
+ semicolon: /\A(\s*\;\s*)/,
96
+
97
+ # Expression
98
+ exp_context: /\A(\$(.|\-.)?|\@|\@\@)/,
99
+ exp_method_call: /\A(\.[a-zA-Z\_][a-zA-Z0-9\_]*[\!\?]?)/,
100
+ exp_module: /\A(\:\:)/,
101
+ exp_identifier: /\A([a-zA-Z\_][a-zA-Z0-9\_]*[\!\?]?)/,
102
+ exp_assignment: /\A(\=)/,
103
+ exp_operation: /\A( *(\+|\-|\*\*|\*|\/|\<\<|\>\>|\.\.|\%|\<\=\>|\<\=|\^|\<|\>\=|\>|\=\~|\!\~|\=\=\=|\=\=|\!|not|\&\&|\&|and|\|\||\||or) *)/,
104
+ exp_regex: /\A(\/((?:[^\/\\]|\\.)*?)\/)/,
105
+ exp_string: /\A(("((?:[^"\\]|\\.)*?)")|('(?:[^'\\]|\\.)*?'))/,
106
+ exp_percent: /\A(\%[wWqQrxsiI]?.)/,
107
+ exp_double: /\A([0-9]+\.[0-9]+([eE][-+]?[0-9]+)?)/,
108
+ exp_fixnum: /\A([0-9]+)/,
109
+ exp_nil: /\A(nil)/,
110
+ exp_boolean: /\A(true|false)/,
111
+ exp_ternary: /\A( *\? *)/,
112
+ exp_ternary_else: /\A( *\: *)/,
113
+
114
+ exp_identifier_lookahead: /\A[a-zA-Z\_][a-zA-Z0-9\_]*[\!\?]?/,
115
+
116
+ # Hash
117
+ hash_terminator: /\A(\s*(\,)\s*)/,
118
+ hash_assignment: /\A(\s*(\=\>)\s*)/,
119
+ hash_symbol: /\A([a-zA-Z\_][a-zA-Z0-9\_]*\:(?!\:))/,
120
+
121
+ # Whitespace
122
+ whitespace: /\A +/,
123
+
124
+ # Evaluation
125
+ eval: /\A\-(.*)/,
126
+ eval_multiline: /\A\+(.*)/,
127
+
128
+ # Whitespace
129
+ newline: /\A(\n+)/,
130
+
131
+ # Feed
132
+ line_feed: /\A(.*)/,
133
+ line_whitespace: /\A\s*\Z/
134
+ }
135
+
136
+ # Return the matching closing bracket
137
+ #
138
+ # @param bracket [String] Opening bracket for the capture group
139
+ #
140
+ def self.bracket(bracket)
141
+ case bracket
142
+ when '(' then return ')'
143
+ when '[' then return ']'
144
+ when '{' then return '}'
145
+ when '<' then return '>'
146
+ end
147
+ end
148
+
149
+ # Return the requested token to the parser
150
+ #
151
+ # @param name [Symbol] Token requested by the parser accept method
152
+ #
153
+ def self.[](name)
154
+ @@tokens[name]
155
+ end
156
+
157
+ # Set a new token at runtime
158
+ #
159
+ # @param name [Symboidentifierl] Identifier for the token
160
+ # @param token [Token] Token data to be set
161
+ #
162
+ def self.[]=(name, token)
163
+ @@tokens[name] = token
164
+ end
165
+ end
166
+ end