cadenza 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+
2
+ module Cadenza
3
+ BlockNotDefinedError = Class.new(Cadenza::Error)
4
+
5
+ class Context
6
+ module Blocks
7
+
8
+ # @!attribute [r] blocks
9
+ # @return [Hash] the block names mapped to their implementing procs
10
+ def blocks
11
+ @blocks ||= {}
12
+ end
13
+
14
+ # looks up the block by name
15
+ #
16
+ # @raise [BlockNotDefinedError] if the block can not be found
17
+ # @param [Symbol] name the name of the block to look up
18
+ # @return [Proc] the block implementation
19
+ def lookup_block(name)
20
+ blocks.fetch(name.to_sym) { raise BlockNotDefinedError.new("undefined block '#{name}'") }
21
+ end
22
+
23
+ # defines a generic block proc with the given name
24
+ #
25
+ # @param [Symbol] name the name for the template to use for this block
26
+ # @yield [Context, Array, *args] the block will receive the context object,
27
+ # a list of Node objects (it's children), and
28
+ # a variable number of aarguments passed to
29
+ # the block.
30
+ # @return nil
31
+ def define_block(name, &block)
32
+ blocks[name.to_sym] = block
33
+ nil
34
+ end
35
+
36
+ # creates an alias of the given block name under a different name
37
+ #
38
+ # @raise [BlockNotDefinedError] if the original block name isn't defined
39
+ # @param [Symbol] original_name the original name of the block
40
+ # @param [Symbol] alias_name the new name of the block
41
+ # @return nil
42
+ def alias_block(original_name, alias_name)
43
+ define_block alias_name, &lookup_block(original_name)
44
+ end
45
+
46
+ # calls the defined generic block proc with the given name and children
47
+ # nodes.
48
+ #
49
+ # @raise [BlockNotDefinedError] if the named block does not exist
50
+ # @param [Symbol] name the name of the block to evaluate
51
+ # @param [Array] nodes the child nodes of the block
52
+ # @param [Array, []] params a list of parameters to pass to the block
53
+ # when calling it.
54
+ # @return [String] the result of evaluating the block
55
+ def evaluate_block(name, nodes, parameters)
56
+ lookup_block(name).call(self, nodes, parameters)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,60 @@
1
+
2
+ module Cadenza
3
+ FilterNotDefinedError = Class.new(Cadenza::Error)
4
+
5
+ class Context
6
+ module Filters
7
+
8
+ # @!attribute [r] filters
9
+ # @return [Hash] the filter names mapped to their implementing procs
10
+ def filters
11
+ @filters ||= {}
12
+ end
13
+
14
+ # looks up the filter by name
15
+ #
16
+ # @raise [FilterNotDefinedError] if the filter can not be found
17
+ # @param [Symbol] name the name of the filter to look up
18
+ # @return [Proc] the filter implementation
19
+ def lookup_filter(name)
20
+ filters.fetch(name.to_sym) { raise FilterNotDefinedError.new("undefined filter '#{name}'") }
21
+ end
22
+
23
+ # defines a filter proc with the given name
24
+ #
25
+ # @param [Symbol] name the name for the template to use for this filter
26
+ # @yield [String, *args] the block will receive the input string and a
27
+ # variable number of arguments passed to the filter.
28
+ # @return nil
29
+ def define_filter(name, &block)
30
+ filters[name.to_sym] = block
31
+ nil
32
+ end
33
+
34
+ # creates an alias of the given filter name under a different name
35
+ #
36
+ # @raise [FilterNotDefinedError] if the original filter name isn't defined
37
+ # @param [Symbol] original_name the original name of the filter
38
+ # @param [Symbol] alias_name the new name of the filter
39
+ # @return nil
40
+ def alias_filter(original_name, alias_name)
41
+ define_filter alias_name, &lookup_filter(original_name)
42
+ end
43
+
44
+ # calls the defined filter proc with the given parameters and returns the
45
+ # result.
46
+ #
47
+ # @raise [FilterNotDefinedError] if the named filter doesn't exist
48
+ # @param [Symbol] name the name of the filter to evaluate
49
+ # @param [Object] input the input value which will be filtered
50
+ # @param [Array] params a list of parameters to pass to the filter
51
+ # block when calling it.
52
+ # @return [String] the result of evaluating the filter
53
+ def evaluate_filter(name, input, params=[])
54
+ lookup_filter(name).call(input, params)
55
+ end
56
+
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,58 @@
1
+
2
+ module Cadenza
3
+ FunctionalVariableNotDefinedError = Class.new(Cadenza::Error)
4
+
5
+ class Context
6
+ module FunctionalVariables
7
+
8
+ # @!attribute [r] functional_variables
9
+ # @return [Hash] the functional variable names mapped to their implementing procs
10
+ def functional_variables
11
+ @functional_variables ||= {}
12
+ end
13
+
14
+ # looks up the functional variable by name
15
+ #
16
+ # @raise [FunctionalVariableNotDefinedError] if the functional variable can not be found
17
+ # @param [Symbol] name the name of the functional variable to look up
18
+ # @return [Proc] the functional variable implementation
19
+ def lookup_functional_variable(name)
20
+ functional_variables.fetch(name.to_sym) { raise FunctionalVariableNotDefinedError.new("undefined functional variable '#{name}'") }
21
+ end
22
+
23
+ # defines a functional variable proc with the given name
24
+ #
25
+ # @param [Symbol] name the name for the template to use for this variable
26
+ # @yield [Context, *args] the block will receive the context object and a
27
+ # variable number of arguments passed to the variable.
28
+ # @return nil
29
+ def define_functional_variable(name, &block)
30
+ functional_variables[name.to_sym] = block
31
+ nil
32
+ end
33
+
34
+ # creates an alias of the given functional variable name under a different name
35
+ #
36
+ # @raise [FunctionalVariableNotDefinedError] if the original functional variable name isn't defined
37
+ # @param [Symbol] original_name the original name of the functional variable
38
+ # @param [Symbol] alias_name the new name of the functional variable
39
+ # @return nil
40
+ def alias_functional_variable(original_name, alias_name)
41
+ define_functional_variable alias_name, &lookup_functional_variable(original_name)
42
+ end
43
+
44
+ # calls the defined functional variable proc with the given parameters and
45
+ # returns the result.
46
+ #
47
+ # @raise [FunctionalVariableNotDefinedError] if the named variable doesn't exist
48
+ # @param [Symbol] name the name of the functional variable to evaluate
49
+ # @param [Array] params a list of parameters to pass to the variable
50
+ # block when calling it
51
+ # @return [Object] the result of evaluating the functional variable
52
+ def evaluate_functional_variable(name, params=[])
53
+ lookup_functional_variable(name).call([self] + params)
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,116 @@
1
+
2
+ module Cadenza
3
+ TemplateNotFoundError = Class.new(Cadenza::Error)
4
+
5
+ class Context
6
+ module Loaders
7
+
8
+ # @!attribute [rw] whiny_template_loading
9
+ # @return [Boolean] true if a {TemplateNotFoundError} should still be
10
+ # raised if not calling the bang form of {#load_source}
11
+ # or {#load_template}
12
+ def whiny_template_loading
13
+ @whiny_template_loading ||= false
14
+ end
15
+
16
+ def whiny_template_loading=(rhs)
17
+ @whiny_template_loading = rhs
18
+ end
19
+
20
+ # @!attribute [r] loaders
21
+ # @return [Array] the list of loaders
22
+ def loaders
23
+ @loaders ||= []
24
+ end
25
+
26
+ # constructs a {FilesystemLoader} with the string given as its path and
27
+ # adds the loader to the end of the loader list.
28
+ #
29
+ # @param [String] path to use for loader
30
+ # @return [Loader] the loader that was created
31
+ def add_load_path(path)
32
+ loader = FilesystemLoader.new(path)
33
+ add_loader(loader)
34
+ loader
35
+ end
36
+
37
+ # adds the given loader to the end of the loader list.
38
+ #
39
+ # @param [Loader] loader the loader to add
40
+ # @return nil
41
+ def add_loader(loader)
42
+ loaders.push loader
43
+ nil
44
+ end
45
+
46
+ # removes all loaders from the context
47
+ # @return nil
48
+ def clear_loaders
49
+ loaders.reject! { true }
50
+ nil
51
+ end
52
+
53
+ # loads and returns the given template but does not parse it
54
+ #
55
+ # @raise [TemplateNotFoundError] if {#whiny_template_loading} is enabled and
56
+ # the template could not be loaded.
57
+ # @param [String] template_name the name of the template to load
58
+ # @return [String] the template text or nil if the template could not be loaded
59
+ def load_source(template_name)
60
+ source = nil
61
+
62
+ loaders.each do |loader|
63
+ source = loader.load_source(template_name)
64
+ break if source
65
+ end
66
+
67
+ if source.nil? and whiny_template_loading
68
+ raise TemplateNotFoundError.new(template_name)
69
+ else
70
+ return source
71
+ end
72
+ end
73
+
74
+ # loads and returns the given template but does not parse it
75
+ #
76
+ # @raise [TemplateNotFoundError] if the template could not be loaded
77
+ # @param [String] template_name the name of the template to load
78
+ # @return [String] the template text
79
+ def load_source!(template_name)
80
+ load_source(template_name) || raise(TemplateNotFoundError.new(template_name))
81
+ end
82
+
83
+ # loads, parses and returns the given template
84
+ #
85
+ # @raise [TemplateNotFoundError] if {#whiny_template_loading} is enabled and
86
+ # the template could not be loaded.
87
+ # @param [String] template_name the name of the template to load
88
+ # @return [DocumentNode] the root of the parsed document or nil if the
89
+ # template could not be loaded.
90
+ def load_template(template_name)
91
+ template = nil
92
+
93
+ loaders.each do |loader|
94
+ template = loader.load_template(template_name)
95
+ break if template
96
+ end
97
+
98
+ if template.nil? and whiny_template_loading
99
+ raise TemplateNotFoundError.new(template_name)
100
+ else
101
+ return template
102
+ end
103
+ end
104
+
105
+ # loads, parses and returns the given template
106
+ #
107
+ # @raise [TemplateNotFoundError] if the template could not be loaded
108
+ # @param [String] template_name the name of the template ot load
109
+ # @return [DocumentNode] the root of the parsed document
110
+ def load_template!(template_name)
111
+ load_template(template_name) || raise(TemplateNotFoundError.new(template_name))
112
+ end
113
+
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,85 @@
1
+
2
+ module Cadenza
3
+ class Context
4
+ module Stack
5
+
6
+ # @!attribute [r] stack
7
+ # @return [Array] the variable stack
8
+ def stack
9
+ @stack ||= []
10
+ end
11
+
12
+ # retrieves the value of the given identifier by inspecting the variable
13
+ # stack from top to bottom. Identifiers with dots in them are separated
14
+ # on that dot character and looked up as a path. If no value could be
15
+ # found then nil is returned.
16
+ #
17
+ # @return [Object] the object matching the identifier or nil if not found
18
+ def lookup(identifier)
19
+ sym_identifier = identifier.to_sym
20
+
21
+ # if a functional variable is defined matching the identifier name then return that
22
+ return functional_variables[sym_identifier] if functional_variables.has_key?(sym_identifier)
23
+
24
+ stack.reverse_each do |scope|
25
+ value = lookup_identifier(scope, identifier)
26
+
27
+ return value unless value.nil?
28
+ end
29
+
30
+ nil
31
+ end
32
+
33
+ # assigns the given value to the given identifier at the highest current
34
+ # scope of the stack.
35
+ #
36
+ # @param [String] identifier the name of the variable to store
37
+ # @param [Object] value the value to assign to the given name
38
+ def assign(identifier, value)
39
+ stack.last[identifier.to_sym] = value
40
+ end
41
+
42
+ # creates a new scope on the variable stack and assigns the given hash
43
+ # to it.
44
+ #
45
+ # @param [Hash] scope the mapping of names to values for the new scope
46
+ # @return nil
47
+ def push(scope)
48
+ # TODO: symbolizing strings is slow so consider symbolizing here to improve
49
+ # the speed of the lookup method (its more important than push)
50
+
51
+ # TODO: since you can assign with the #assign method then make the scope
52
+ # variable optional (assigns an empty hash)
53
+ stack.push(scope)
54
+
55
+ nil
56
+ end
57
+
58
+ # removes the highest scope from the variable stack
59
+ # @return [Hash] the removed scope
60
+ def pop
61
+ stack.pop
62
+ end
63
+
64
+ private
65
+ def lookup_identifier(scope, identifier)
66
+ if identifier.index('.')
67
+ lookup_path(scope, identifier.split("."))
68
+ else
69
+ self.class.lookup_on_object(identifier, scope)
70
+ end
71
+ end
72
+
73
+ def lookup_path(scope, path)
74
+ loop do
75
+ component = path.shift
76
+
77
+ scope = self.class.lookup_on_object(component, scope)
78
+
79
+ return scope if path.empty?
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -1,10 +1,100 @@
1
1
 
2
2
  module Cadenza
3
+ # ContextObject is a bare bones class which should become the superclass
4
+ # of any object you wish to push onto the {Context::Stack#stack} of the
5
+ # context you render your template with.
6
+ #
7
+ # Normal ruby objects (with the exception of Hash and Array) will not allow
8
+ # any value to be looked up upon it when pushed onto the {Context::Stack#stack}
9
+ # unless they are subclasses of {ContextObject}. See {Context.lookup_on_object}
10
+ # for specific details on how this is done.
3
11
  class ContextObject < Object # intentionally declared, since we cannot
4
12
  # subclass BasicObject in 1.8.7
5
13
 
6
- # this class is intentionally left blank as a placeholder, see
7
- # context_object_spec.rb under spec/ for details.
14
+ private
15
+ # Callback method which is called before the given method name is called
16
+ # by {#invoke_context_method}
17
+ #
18
+ # This method can be overridden by subclasses for whatever purpose they
19
+ # wish, ex. statistics gathering
20
+ #
21
+ # @note this method is a *private* method, but has been documented for
22
+ # your understanding.
23
+ # @!visibility public
24
+ # @param [String|Symbol] method the name of the method that was called
25
+ def before_method(method)
26
+ # no implementation, used by subclasses
27
+ end
28
+
29
+ # Callback method which is called after the given method name is called
30
+ # by {#invoke_context_method}
31
+ #
32
+ # This method can be overridden by subclasses for whatever purpose they
33
+ # wish, ex. statistics gathering
34
+ #
35
+ # @note this method is a *private* method, but has been documented for
36
+ # your understanding.
37
+ # @!visibility public
38
+ # @param [String|Symbol] method the name of the method that was called
39
+ def after_method(method)
40
+ # no implementation, used by subclasses
41
+ end
42
+
43
+ # Method called by {#invoke_context_method} when the given method could
44
+ # not be found in the class's public method list.
45
+ #
46
+ # The return value of this method is returned by {#invoke_context_method}
47
+ #
48
+ # @note this method is a *private* method, but has been documented for
49
+ # your understanding.
50
+ # @!visibility public
51
+ # @param [String|Symbol] method the name of the method that was called
52
+ def missing_context_method(method)
53
+ # no implementation, used by subclasses
54
+ end
55
+
56
+ # Looks up and calls the given method on this object and returns it's
57
+ # value.
58
+ #
59
+ # Only methods in the public visibility scope (see {#context_methods}) can
60
+ # be called by this method.
61
+ #
62
+ # If the method can not be found on this object then {#missing_context_method}
63
+ # will be called instead. You can use this to provide a "method_missing"
64
+ # for your context object.
65
+ #
66
+ # Around the call to the method on this object the {#before_method} and
67
+ # {#after_method} methods will be called with the name of the method. You
68
+ # may wish to use this to perform live benchmarking or other statistics
69
+ # gathering.
70
+ #
71
+ # @note this method is a *private* method, but has been documented for
72
+ # your understanding.
73
+ # @!visibility public
74
+ # @param [Symbol|String] method the name of the method to call
75
+ # @return [Object] the value returned from the method call
76
+ def invoke_context_method(method)
77
+ send(:before_method, method)
78
+
79
+ if context_methods.include?(method.to_s)
80
+ result = send(method)
81
+ else
82
+ result = send(:missing_context_method, method)
83
+ end
84
+
85
+ send(:after_method, method)
86
+
87
+ result
88
+ end
89
+
90
+ # @note this method is a *private* method, but has been documented for
91
+ # your understanding.
92
+ # @!visibility public
93
+ # @return [Array] a list of methods which are in the public visibility
94
+ # scope for this class
95
+ def context_methods
96
+ (self.class.public_instance_methods - ContextObject.public_instance_methods).map(&:to_s)
97
+ end
8
98
 
9
99
  end
10
100
  end