opulent 1.4.0 → 1.4.1

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/benchmark/benchmark.rb +18 -7
  3. data/benchmark/cases/node/node.haml +6 -16
  4. data/benchmark/cases/node/node.op +6 -13
  5. data/benchmark/cases/node/node.slim +6 -18
  6. data/docs/comments.md +5 -0
  7. data/docs/filters.md +31 -0
  8. data/docs/includes.md +0 -0
  9. data/docs/nodes.md +88 -0
  10. data/docs/reference.md +7 -6
  11. data/lib/opulent.rb +2 -1
  12. data/lib/opulent/compiler.rb +19 -18
  13. data/lib/opulent/compiler/buffer.rb +260 -0
  14. data/lib/opulent/compiler/comment.rb +4 -9
  15. data/lib/opulent/compiler/control.rb +65 -67
  16. data/lib/opulent/compiler/define.rb +91 -63
  17. data/lib/opulent/compiler/doctype.rb +1 -5
  18. data/lib/opulent/compiler/eval.rb +1 -1
  19. data/lib/opulent/compiler/node.rb +10 -194
  20. data/lib/opulent/compiler/root.rb +1 -1
  21. data/lib/opulent/compiler/text.rb +3 -40
  22. data/lib/opulent/compiler/yield.rb +15 -0
  23. data/lib/opulent/context.rb +2 -2
  24. data/lib/opulent/engine.rb +56 -57
  25. data/lib/opulent/parser.rb +10 -10
  26. data/lib/opulent/parser/control.rb +2 -3
  27. data/lib/opulent/parser/expression.rb +1 -1
  28. data/lib/opulent/parser/{require.rb → include.rb} +17 -15
  29. data/lib/opulent/parser/node.rb +22 -17
  30. data/lib/opulent/parser/root.rb +3 -4
  31. data/lib/opulent/parser/text.rb +3 -7
  32. data/lib/opulent/parser/yield.rb +23 -0
  33. data/lib/opulent/settings.rb +5 -4
  34. data/lib/opulent/template.rb +12 -30
  35. data/lib/opulent/tokens.rb +5 -9
  36. data/lib/opulent/utils.rb +41 -0
  37. data/lib/opulent/version.rb +1 -1
  38. metadata +9 -5
  39. data/lib/opulent/compiler/block.rb +0 -31
  40. data/lib/opulent/parser/block.rb +0 -56
@@ -11,7 +11,7 @@ module Opulent
11
11
  def root(current, indent, context)
12
12
  if Keywords.include? current[@type]
13
13
  send :"#{current[@type]}_node", current, indent, context
14
- else
14
+ else
15
15
  send current[@type], current, indent, context
16
16
  end
17
17
  end
@@ -9,52 +9,15 @@ module Opulent
9
9
  # @param context [Context] Processing environment data
10
10
  #
11
11
  def plain(node, indent, context)
12
- indentation = " " * indent
13
-
14
- inline = @inline_node.include? @node_stack.last
12
+ value = node[@options][:value]
15
13
 
16
14
  # Evaluate text node if it's marked as such and print nodes in the
17
15
  # current context
18
16
  if node[@value] == :text
19
- if node[@options][:evaluate]
20
- value = context.evaluate "\"#{node[@options][:value]}\""
21
- else
22
- value = node[@options][:value]
23
- end
17
+ buffer_split_by_interpolation value, node[@options][:escaped]
24
18
  else
25
- value = if node[@options][:value] == 'yield'
26
- context.evaluate_yield
27
- else
28
- context.evaluate(node[@options][:value])
29
- end
30
- end
31
-
32
- # Indent all the lines with the given indentation
33
- value = indent_lines value, indentation
34
-
35
- # If the last node was an inline node, we remove the trailing newline
36
- # character and we left strip the value
37
- #pp @node_stack
38
- if @node_stack.last == :text
39
- remove_trailing_newline
40
- value = " " + value.lstrip
41
- elsif inline
42
- remove_trailing_newline
43
- value.lstrip!
19
+ node[@options][:escaped] ? buffer_escape(value) : buffer(value)
44
20
  end
45
- value.rstrip!
46
-
47
- # Escape the value unless explicitly set to false
48
- value = node[@options][:escaped] ? escape(value) : value
49
-
50
- # Create the text tag to be added
51
- text_tag = "#{value}"
52
- text_tag += "\n"
53
-
54
- # Set the current child node as last processed node
55
- @node_stack << :text
56
-
57
- @code += text_tag
58
21
  end
59
22
  end
60
23
  end
@@ -0,0 +1,15 @@
1
+ # @Opulent
2
+ module Opulent
3
+ # @Compiler
4
+ class Compiler
5
+ # Generate the code for a while control structure
6
+ #
7
+ # @param node [Array] Node code generation data
8
+ # @param indent [Fixnum] Size of the indentation to be added
9
+ # @param context [Context] Processing environment data
10
+ #
11
+ def yield_node(node, indent, context)
12
+ buffer_eval "yield if block_given?"
13
+ end
14
+ end
15
+ end
@@ -33,9 +33,9 @@ module Opulent
33
33
  #
34
34
  # @param code [String] Code to be evaluated
35
35
  #
36
- def evaluate(code)
36
+ def evaluate(code, &block)
37
37
  begin
38
- eval code, @binding
38
+ eval code, @binding, &block
39
39
  rescue NameError => variable
40
40
  Compiler.error :binding, variable, code
41
41
  end
@@ -6,92 +6,91 @@ module Opulent
6
6
 
7
7
  # Module method wrapper for creating a new engine instance
8
8
  #
9
- def Opulent.new(settings = {})
10
- return Engine.new settings
9
+ def Opulent.new(input, settings = {})
10
+ return Engine.new input, settings
11
11
  end
12
12
 
13
13
  # @Engine
14
14
  class Engine
15
- attr_reader :nodes, :definitions, :parser, :file, :preamble, :buffer
15
+ attr_reader :nodes, :parser, :def, :file, :template, :buffer
16
16
 
17
17
  # Update render settings
18
18
  #
19
19
  # @param settings [Hash] Opulent settings override
20
- # @param definitions [Hash] Definitions from previously parsed files
20
+ # @param def [Hash] def from previously parsed files
21
21
  # @param overwrite [Boolean] Write changes directly to the parent binding
22
22
  #
23
- def initialize(settings = {})
24
- @definitions = {}
23
+ def initialize(input, settings = {})
24
+ # Set def from other Opulent instances
25
+ @def = settings.delete(:def) || {}
25
26
 
27
+ # Update default settings with user settings
26
28
  Settings.update_settings settings unless settings.empty?
27
- end
28
-
29
- # Avoid code duplication when layouting is set. When we have a layout, look
30
- # in layouts/application by default.
31
- #
32
- # @param file [String] The file that needs to be analyzed
33
- # @param locals [Hash] Render call local variables
34
- # @param block [Proc] Processing environment data
35
- #
36
- def render(input, locals = {}, &block)
37
- # If a layout is set, get the specific layout, otherwise, set the default
38
- # one. If a layout is set to false, the page will be render as it is.
39
- if Settings[:layouts]
40
- layout = locals.has_key?(:layout) ? locals.delete(:layout) : Settings[:default_layout]
41
-
42
- # Process with the built in layout system
43
- process layout, locals, block do
44
- process input, locals, block, &block
45
- end
46
- else
47
- # We pass the same block as content block, in case we're using a
48
- # different yielding system from within a web framework using Tilt
49
- process input, locals, block, &block
50
- end
51
- end
52
29
 
53
- # Analyze the input code and check for matching tokens. In case no match was
54
- # found, throw an exception. In special cases, modify the token hash.
55
- #
56
- # @param file [String] The file that needs to be analyzed
57
- # @param locals [Hash] Render call local variables
58
- # @param block [Proc] Processing environment data
59
- #
60
- def process(input, locals, block, &content)
61
30
  # Read input parameter based on opening mode. If we have a file mode, we
62
31
  # get its path and read the code. We need to reset the mode in case the next
63
32
  # render call is on code, not on a file.
64
33
  @code = case input
65
34
  when Symbol
66
- @file = File.expand_path "#{input}.op"
67
- File.read @file
35
+ @file = File.expand_path get_eval_file input; File.read @file
68
36
  else
69
- @file = File.expand_path __FILE__
70
- input
37
+ @file = File.expand_path __FILE__; input
71
38
  end
72
39
 
73
40
  # Get the nodes tree
74
- @nodes, @definitions = Parser.new(@file, @definitions).parse @code
41
+ @nodes, @def = Parser.new(@file, @def).parse @code
75
42
 
76
- # @TODO
77
- # Implement precompiled template handling
78
- @preamble = @nodes.inspect.inspect
43
+ # Compile our syntax tree using input context
44
+ @template = Compiler.new.compile @nodes
45
+ end
79
46
 
80
- # Create a new context based on our rendering environment
81
- @context = Context.new locals, block, &content
47
+ # Avoid code duplication when layouting is set. When we have a layout, look
48
+ # in layouts/application by default.
49
+ #
50
+ # @param scope [Object] Template evaluation context
51
+ # @param locals [Hash] Render call local variables
52
+ # @param block [Proc] Processing environment data
53
+ #
54
+ def render(scope = Object.new, locals = {}, &block)
55
+ # Get opulent buffer value
56
+ initial_buffer = scope.instance_variable_defined?(:@_opulent_buffer) ? scope.instance_variable_get(:@_opulent_buffer) : []
82
57
 
83
- # Compile our syntax tree using input context
84
- @output = Compiler.new.compile @nodes, @context
58
+ # If a layout is set, get the specific layout, otherwise, set the default
59
+ # one. If a layout is set to false, the page will be render as it is.
60
+ if scope.is_a? binding.class
61
+ scope_object = eval "self", scope
62
+ scope = scope_object.instance_eval{ binding } if block_given?
63
+ else
64
+ scope_object = scope
65
+ scope = scope_object.instance_eval{ binding }
66
+ end
85
67
 
86
- if DEBUG
87
- #puts "Nodes\n---\n"
88
- #pp @nodes
68
+ # Set input local variables in current scope
69
+ locals.each do |key, value|
70
+ scope.local_variable_set key, value
71
+ end
89
72
 
90
- # puts "\n\nCode\n---\n"
91
- # pp @output
73
+ begin
74
+ # Evaluate the template in the given scope (context)
75
+ eval @template, scope
76
+ rescue ::SyntaxError => e
77
+ raise SyntaxError, e.message
78
+ ensure
79
+ # Get rid of the current buffer
80
+ scope_object.instance_variable_set :@_opulent_buffer, initial_buffer
92
81
  end
82
+ end
93
83
 
94
- return @output
84
+ private
85
+ # Add .op extension to input file if it isn't already set.
86
+ #
87
+ # @param input [Symbol] Input file
88
+ #
89
+ def get_eval_file(input)
90
+ input = input.to_s
91
+ input += Settings::FileExtension unless File.extname(input) == Settings::FileExtension
92
+ return input
95
93
  end
94
+
96
95
  end
97
96
  end
@@ -1,4 +1,3 @@
1
- require_relative 'parser/block.rb'
2
1
  require_relative 'parser/comment.rb'
3
2
  require_relative 'parser/control.rb'
4
3
  require_relative 'parser/define.rb'
@@ -7,9 +6,10 @@ require_relative 'parser/eval.rb'
7
6
  require_relative 'parser/expression.rb'
8
7
  require_relative 'parser/filter.rb'
9
8
  require_relative 'parser/node.rb'
10
- require_relative 'parser/require.rb'
9
+ require_relative 'parser/include.rb'
11
10
  require_relative 'parser/root.rb'
12
11
  require_relative 'parser/text.rb'
12
+ require_relative 'parser/yield.rb'
13
13
 
14
14
  # @Opulent
15
15
  module Opulent
@@ -31,8 +31,8 @@ module Opulent
31
31
  @indent = 4
32
32
 
33
33
  # Set current compiled file as the first in the file stack together with
34
- # its base indentation. The stack is used to allow require directives to
35
- # be used with the last parent path found
34
+ # its base indentation. The stack is used to allow include directives to
35
+ # be used with the last parent path found
36
36
  @file = [[file, -1]]
37
37
 
38
38
  # Initialize definitions for the parser
@@ -206,12 +206,12 @@ module Opulent
206
206
  when :self_enclosing_children
207
207
  "Unexpected child elements found for self enclosing node on line #{data[0]+1} of input at:\n\n" +
208
208
  "#{@code[data[0]]}#{Logger.red @code[data[0] + 1]}"
209
- when :require
210
- "The required file #{data[0]} does not exist or an incorrect path has been specified."
211
- when :require_dir
212
- "The required file path #{data[0]} is a directory."
213
- when :require_end
214
- "Unexpected content found after require on line #{@i+1} of input at:\n\n" +
209
+ when :include
210
+ "The included file #{data[0]} does not exist or an incorrect path has been specified."
211
+ when :include_dir
212
+ "The included file path #{data[0]} is a directory."
213
+ when :include_end
214
+ "Missing argument for include on line #{@i+1} of input at:\n\n" +
215
215
  "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
216
216
  else
217
217
  "#{@line[0..@offset-1]}#{Logger.red @line[@offset..-1].rstrip}"
@@ -17,9 +17,8 @@ module Opulent
17
17
  # Check if arguments provided correctly
18
18
  error :each_arguments unless condition.match Tokens[:each_pattern]
19
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)
20
+ condition = [$1.split(' '), $2.split(/,(.+)$/).map(&:strip).map(&:to_sym)]
21
+ condition[0].unshift '{}' if condition[0].length == 1 # Array loop as default
23
22
  end
24
23
 
25
24
  # Else and default structures are not allowed to have any condition
@@ -44,7 +44,7 @@ module Opulent
44
44
  if buffer.strip.empty?
45
45
  return undo buffer
46
46
  else
47
- return [:expression, buffer.strip, {evaluate: true}]
47
+ return [:expression, buffer.strip, {}]
48
48
  end
49
49
  end
50
50
 
@@ -13,42 +13,44 @@ module Opulent
13
13
  #
14
14
  # @param nodes [Array] Parent node to which we append to
15
15
  #
16
- def require_file(parent, indent)
17
- if(match = accept :require)
16
+ def include_file(parent, indent)
17
+ if(match = accept :include)
18
18
 
19
19
  # Process data
20
- name = accept :exp_string, :*
20
+ name = accept :line_feed || ""
21
+ name.strip!
21
22
 
22
- # Check if there is any string after the require input
23
- unless (feed = accept(:line_feed) || "").strip.empty?
24
- undo feed; error :require_end
23
+
24
+ # Check if there is any string after the include input
25
+ if name.empty?
26
+ error :include_end
25
27
  end
26
28
 
27
29
  # Get the complete file path based on the current file being compiled
28
- require_path = File.expand_path name[1..-2], File.dirname(@file[-1][0])
30
+ include_path = File.expand_path name, File.dirname(@file[-1][0])
29
31
 
30
32
  # Try to see if it has any existing extension, otherwise add .op
31
- require_path += '.op' unless Settings::Extensions.include? File.extname require_path
33
+ include_path += Settings::FileExtension if File.extname(name).empty?
32
34
 
33
35
  # Throw an error if the file doesn't exist
34
- error :require, name unless Dir[require_path].any?
36
+ error :include, name unless Dir[include_path].any?
35
37
 
36
- # Require entire directory tree
37
- Dir[require_path].each do |file|
38
+ # include entire directory tree
39
+ Dir[include_path].each do |file|
38
40
  # Skip current file when including from same directory
39
41
  next if file == @file[-1][0]
40
42
 
41
- @file << [require_path, indent]
43
+ @file << [include_path, indent]
42
44
 
43
45
  # Throw an error if the file doesn't exist
44
- error :require_dir, file if File.directory? file
46
+ error :include_dir, file if File.directory? file
45
47
 
46
48
  # Throw an error if the file doesn't exist
47
- error :require, file unless File.file? file
49
+ error :include, file unless File.file? file
48
50
 
49
51
  # Indent all lines and prepare them for the parser
50
52
  lines = indent_lines File.read(file), " " * indent
51
-
53
+ lines << " "
52
54
  # Indent all the output lines with the current indentation
53
55
  @code.insert @i + 1, *lines.lines
54
56
  end
@@ -74,9 +74,9 @@ module Opulent
74
74
  # Add the current node to the root
75
75
  root(current_node, indent)
76
76
 
77
- if current_node[@options][:self_enclosing] && current_node[@children].any?
78
- error :self_enclosing_children, line
79
- end
77
+ # if current_node[@options][:self_enclosing] && current_node[@children].any?
78
+ # error :self_enclosing_children, line
79
+ # end
80
80
 
81
81
  # Create a clone of the definition model. Cloning the options is also
82
82
  # necessary because it's a shallow copy
@@ -99,16 +99,28 @@ module Opulent
99
99
  model[@options] = {}.merge model[@options]
100
100
  model[@options][:call] = call_context
101
101
 
102
- # Recursively map each child node with its definition
103
- model[@children].map! do |child|
104
- if @definitions.keys.include? child[@value]
105
- process_definition child[@value], child
102
+ # Recursively map each child nodes to their definitions
103
+ # for the initial call node children and for the model
104
+ # children
105
+ process_definition_child model[@options][:call]
106
+ process_definition_child model
107
+
108
+ return model
109
+ end
110
+
111
+ def process_definition_child(node)
112
+ node[@children].map! do |child|
113
+ if child[@type] == :node
114
+ if @definitions.keys.include? child[@value]
115
+ process_definition child[@value], child
116
+ else
117
+ process_definition_child child if child[@children]
118
+ child
119
+ end
106
120
  else
107
121
  child
108
122
  end
109
123
  end
110
-
111
- return model
112
124
  end
113
125
 
114
126
  # Helper method to create an array of values when an attribute is set
@@ -119,13 +131,6 @@ module Opulent
119
131
  # @param value [String] Attribute value
120
132
  #
121
133
  def add_attribute(atts, key, value)
122
- # Check whether the attribute value needs to be evaluated or not
123
- value[@options][:evaluate] = if value[@value] =~ Settings::EvaluationCheck
124
- value[@value] =~ Settings::InterpolationCheck ? true : false
125
- else
126
- true
127
- end
128
-
129
134
  # Check for unique key and arrays of attributes
130
135
  if key == :class
131
136
  # If the key is already associated to an array, add the value to the
@@ -229,7 +234,7 @@ module Opulent
229
234
  error :assignments_colon
230
235
  end
231
236
  else
232
- parent[argument] = [:expression, "nil", {evaluate: true, escaped: false}] unless parent[argument]
237
+ parent[argument] = [:expression, "nil", {escaped: false}] unless parent[argument]
233
238
  end
234
239
 
235
240
  # If our attributes are wrapped, we allow method calls without
@@ -21,12 +21,12 @@ module Opulent
21
21
  indent = accept(:indent).size
22
22
 
23
23
  # Stop using the current parent as root if it does not match the
24
- # minimum indentation requirements
24
+ # minimum indentation includements
25
25
  unless min_indent < indent
26
26
  @i -= 1; break
27
27
  end
28
28
 
29
- # If last require path had a greater indentation, pop the last file path
29
+ # If last include path had a greater indentation, pop the last file path
30
30
  @file.pop if @file[-1][1] > indent
31
31
 
32
32
  # Try the main Opulent node types and process each one of them using
@@ -39,8 +39,7 @@ module Opulent
39
39
  evaluate(parent, indent) ||
40
40
  filter(parent, indent) ||
41
41
  block_yield(parent, indent) ||
42
- block(parent, indent) ||
43
- require_file(parent, indent)||
42
+ include_file(parent, indent)||
44
43
  html_text(parent, indent) ||
45
44
  doctype(parent, indent)
46
45