cadenza 0.7.2 → 0.8.0

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.
@@ -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