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