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.
- data/bin/cadenza +9 -1
- data/lib/cadenza.rb +15 -0
- data/lib/cadenza/base_renderer.rb +0 -8
- data/lib/cadenza/block_hierarchy.rb +42 -0
- data/lib/cadenza/cli.rb +2 -2
- data/lib/cadenza/context.rb +44 -313
- data/lib/cadenza/context/blocks.rb +60 -0
- data/lib/cadenza/context/filters.rb +60 -0
- data/lib/cadenza/context/functional_variables.rb +58 -0
- data/lib/cadenza/context/loaders.rb +116 -0
- data/lib/cadenza/context/stack.rb +85 -0
- data/lib/cadenza/context_object.rb +92 -2
- data/lib/cadenza/filters/standard_filters.rb +82 -23
- data/lib/cadenza/lexer.rb +0 -3
- data/lib/cadenza/nodes/filter_node.rb +1 -2
- data/lib/cadenza/nodes/filtered_value_node.rb +48 -0
- data/lib/cadenza/nodes/variable_node.rb +17 -4
- data/lib/cadenza/racc_parser.rb +329 -339
- data/lib/cadenza/source_renderer.rb +225 -0
- data/lib/cadenza/text_renderer.rb +25 -10
- data/lib/cadenza/version.rb +2 -2
- metadata +10 -3
- data/lib/cadenza/nodes/inject_node.rb +0 -57
@@ -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
|
-
|
7
|
-
#
|
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
|