opulent 1.4.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
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