templator 0.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.
@@ -0,0 +1,51 @@
1
+ module Templator
2
+
3
+ class ParameterCodeLoader
4
+
5
+ def self.load_code_from(*paths)
6
+ files = get_candidate_files paths
7
+ code = concatenate_content_of files
8
+ end
9
+
10
+ private
11
+
12
+ # Identifies all candidate files from the given array of paths.
13
+ # A path may match a file or a directory.
14
+ # The method considers as candidates :
15
+ # * all paths that match a regular file
16
+ # * all files at the root of a directory when the path match a directory
17
+ # @param [Array<String>] paths list of paths
18
+ # @return [Array<String>] array of candidate files.
19
+ def self.get_candidate_files(paths)
20
+ candidates = []
21
+ paths.each do |path|
22
+ if File.directory?(path)
23
+ candidates.concat(get_files_from_directory(path))
24
+ else
25
+ candidates << path
26
+ end
27
+ end
28
+ return candidates
29
+ end
30
+
31
+ # Lists files at the root of the given directory path
32
+ # @param [String] directory path of the directory to process
33
+ # @return [Array<String>] array of files included at the top level of the directory
34
+ def self.get_files_from_directory(directory)
35
+ Dir["#{directory}/*"].sort.select do |file|
36
+ File.file? file
37
+ end
38
+ end
39
+
40
+ # Concatenates the content of the given files.
41
+ # @param [Array<String>] files array of files to process
42
+ # @return [String] the content concatenated of all given files
43
+ def self.concatenate_content_of(files)
44
+ files.inject("") do |content, file|
45
+ content += File.read(file)
46
+ content += "\n" unless content[-1, 1] == "\n"
47
+ content
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,144 @@
1
+ module Templator
2
+
3
+ # Defines and interprets the methods of the Parameter DSL.
4
+ # Supported DSL methods are :
5
+ # * export(hash) : defines a list of parameters from the given hash
6
+ # * group(name, block) : defines a group of parameter
7
+ # * include_group(name) : include parameters and sub groups the given group into the current group
8
+ #
9
+ # =Example
10
+ # With the following code :
11
+ # export :param1 => 'value1'
12
+ # export :param2 => 'value2', :param3 => 'value3'
13
+ # group "group1" do
14
+ # export :param4 => value4
15
+ # end
16
+ # group "group2" do
17
+ # export :param5 => group1.param4
18
+ # group "group3" do
19
+ # export :param6 => "value6"
20
+ # end
21
+ # end
22
+ # group "group4" do
23
+ # include_group "group2.group3"
24
+ # end
25
+ #
26
+ #
27
+ # param5 value can retrieved with the following :
28
+ # p = ParameterDsl.new.parse(code)
29
+ # p.group2.param5
30
+ #
31
+ #
32
+ class ParameterDsl
33
+
34
+ # Name of the implicit top level group
35
+ TOP_LEVEL_GROUP_NAME = "__top__"
36
+
37
+ def initialize
38
+ #initialize the top level group
39
+ @group_stack = []
40
+ @group_stack.push Group.new(TOP_LEVEL_GROUP_NAME)
41
+ end
42
+
43
+ # Parses the given code
44
+ # @param [String] code code to parse
45
+ # @return [Object] a dynamically built object
46
+ # whose methods allow to access values of parameters defined in given code.
47
+ def parse(code)
48
+ instance_eval code
49
+ @group_stack.first
50
+ end
51
+
52
+ # Defines parameters providing a name and a value for each parameter.
53
+ # @param [Hash]params hash of parameter name and value
54
+ def export(params)
55
+ params.each do |name, value|
56
+ define_method_in_current_group(name) {value}
57
+ end
58
+ end
59
+
60
+ # Defines a group of parameters.
61
+ # @param [#to_s] name name of the group
62
+ # @yield group block definition
63
+ def group(name)
64
+ enter_group name.to_s
65
+ yield
66
+ leave_group
67
+ end
68
+
69
+ # Includes all parameters and sub groups of a given group into the current group.
70
+ # @param [Group,#to_s] group group to include
71
+ def include_group(group)
72
+ source_group = group
73
+ if ! group.kind_of? Group
74
+ source_group = get_group group.to_s
75
+ end
76
+ source_group.singleton_methods.each do |method|
77
+ define_method_in_current_group(method) {source_group.send(method)}
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ # Gets the current group from the group stack
84
+ def current_group
85
+ @group_stack.last
86
+ end
87
+
88
+ # Manages the entry in a new group:
89
+ # * create a new Group instance
90
+ # * define a method in the current group to access this new group
91
+ # * push the new group instance on top of the group stack
92
+ # @param [#to_s] name of the new group
93
+ #
94
+ def enter_group(name)
95
+ group = group_in_current_context(name) || Group.new(name)
96
+ define_method_in_current_group(name) {group}
97
+ @group_stack.push(group)
98
+ end
99
+
100
+ # Defines a method inside the current group
101
+ # @param [#to_s] method_name name of the method to define.
102
+ # @param [Block] method_block block of the méthode to define
103
+ def define_method_in_current_group(method_name, &method_block)
104
+ (class << current_group; self; end).send(:define_method, method_name, method_block)
105
+ end
106
+
107
+ # Verify if a group belongs to the current group
108
+ # @param [#to_s] name name of the group to control
109
+ # @return the group with the given name if it belongs to the current group, nil otherwise
110
+ def group_in_current_context(name)
111
+ current_group.respond_to?(name) ? current_group.send(name) : nil
112
+ end
113
+
114
+ # Manages the exit from a group
115
+ def leave_group
116
+ @group_stack.pop
117
+ end
118
+
119
+ # Manages the access to a parameter outside of the current group
120
+ def method_missing(name, *args)
121
+ @group_stack.first.send(name, *args)
122
+ end
123
+
124
+ # Get a group from its fully qualified name
125
+ def get_group(fully_qualified_name)
126
+ fully_qualified_name.to_s.split('.').inject(top_level_group) {|result, name| result.send(name)}
127
+ end
128
+
129
+ # Return the top level goup
130
+ def top_level_group
131
+ @group_stack.first
132
+ end
133
+
134
+ end
135
+
136
+ # Base class to define a group.
137
+ # A Group instance is created whenever a group method is parsed from the DSL code.
138
+ # Methods are dynamically created inside the singleton of the instance to access nested parameters and groups.
139
+ class Group
140
+ def initialize(name)
141
+ @name = name
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'templator/parameter_code_loader'
3
+ require 'templator/parameter_dsl'
4
+
5
+ module Templator
6
+
7
+ class Parameters
8
+
9
+ # Loads parameter files from given paths.
10
+ #
11
+ # @param [Array<String>] paths list of paths that match parameter files.
12
+ # Each element can match an individual file or a directory, in this case
13
+ # all files included at the root of this directory are assumed to be
14
+ # parameter files.
15
+ #
16
+ # @return [Parameters] a Parameters instance.
17
+ # This instance is suitable to provide a later access
18
+ # to parameters defined in loaded files.
19
+ def self.load_files(*paths)
20
+
21
+ code = ParameterCodeLoader.load_code_from(*paths)
22
+
23
+ parameters = Parameters.new
24
+ parameters.load(code)
25
+ return parameters
26
+ end
27
+
28
+ # Retrieves the value of a variable
29
+ # defined in the parameter files previously loaded
30
+ # @param [#to_s] var the fully qualified name of the variable (in dot notation)
31
+ def get(var)
32
+ var.to_s.split('.').inject(@parameters) {|result, element| result.send(element)}
33
+ end
34
+
35
+ # Loads code in a fresh context
36
+ # @param [String ] code code to load
37
+ def load(code)
38
+ @parameters = Templator::ParameterDsl.new.parse(code)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,97 @@
1
+ require 'templator/actions'
2
+
3
+ module Templator
4
+
5
+ describe Actions do
6
+
7
+ # test class
8
+ class Includer
9
+ include Actions
10
+
11
+ def initialize
12
+ @context = nil
13
+ end
14
+
15
+ def context
16
+ return @context
17
+ end
18
+
19
+ def context=(context)
20
+ @context = context
21
+ end
22
+
23
+ end
24
+
25
+ before do
26
+ @includer = Includer.new
27
+ @varname = "foo"
28
+ @varvalue = "bar"
29
+ end
30
+
31
+ describe "#param" do
32
+
33
+ context ", when no context is provided," do
34
+ it "should retrieve from the #parameters instance the value of the provided parameter name" do
35
+
36
+ parameters = mock(:parameters)
37
+
38
+ @includer.should_receive(:parameters).once.and_return(parameters)
39
+ parameters.should_receive(:get).with(@varname).once.and_return(@varvalue)
40
+
41
+ @includer.param(@varname).should == @varvalue
42
+
43
+ end
44
+ end
45
+
46
+ context ", when a context is defined in the includer" do
47
+
48
+ before do
49
+ @parameters = mock(:parameters)
50
+
51
+ @context_name = "context"
52
+ @includer.context = @context_name
53
+ end
54
+
55
+ context "and the provided parameter is defined outside of the context," do
56
+
57
+ it "should retrieve the value of the provided parameter name from #parameters" do
58
+
59
+ @includer.should_receive(:parameters).once.and_return(@parameters)
60
+ @parameters.should_receive(:get).with(@varname).once.and_return(@varvalue)
61
+
62
+ @includer.param(@varname).should == @varvalue
63
+
64
+ end
65
+ end
66
+
67
+ context "and the provided variable is defined inside of the context," do
68
+ it "should retrieve the value of the provided parameter name from #parameters" do
69
+
70
+ @includer.should_receive(:parameters).at_least(:once).and_return(@parameters)
71
+ @parameters.should_receive(:get).with(any_args).twice.and_return do |varname|
72
+ case varname
73
+ when @varname
74
+ raise NoMethodError.new
75
+ when "#{@context_name}.#{@varname}"
76
+ @varvalue
77
+ end
78
+ end
79
+
80
+ @includer.param(@varname).should == @varvalue
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "#include_file" do
87
+
88
+ it "should include a file relative to the current template file" do
89
+
90
+ test_file = File.expand_path('file_to_include', File.join(File.dirname(__FILE__),'../data'))
91
+
92
+ @includer.should_receive(:search_path).once.and_return([File.dirname(test_file)])
93
+ @includer.include_file(File.basename(test_file)).should == File.new(test_file).read
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,29 @@
1
+ require 'templator/parameter_code_loader'
2
+ require 'spec_helper'
3
+
4
+
5
+ module Templator
6
+
7
+ describe "ParameterCodeLoader" do
8
+
9
+ describe "#parameter_loader" do
10
+
11
+ it "should load the code from the given file" do
12
+
13
+ path = File.join(parameter_dir_path, 'parameter1')
14
+
15
+ code = ParameterCodeLoader.load_code_from(path)
16
+
17
+ code.should == parameter_file_content(path)
18
+
19
+
20
+ end
21
+
22
+ it "should load the code from all the files inside the given directory" do
23
+ code = ParameterCodeLoader.load_code_from(parameter_dir_path)
24
+ code.should == parameter_file_content(*Dir["#{parameter_dir_path}/*"])
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,133 @@
1
+ require 'templator/parameter_dsl'
2
+
3
+ module Templator
4
+
5
+ describe ParameterDsl do
6
+
7
+ describe "#parse" do
8
+
9
+ context "when the DSL defines a parameter" do
10
+
11
+ it "should return an object that allows to access the parameter value in a natural way" do
12
+
13
+ code =<<-CODE
14
+ export :var1 => "value1"
15
+ CODE
16
+
17
+ pdsl = ParameterDsl.new
18
+ group = pdsl.parse(code)
19
+
20
+ group.var1.should == "value1"
21
+ end
22
+ end
23
+
24
+ context "when the DSL defines a group and a parameter inside this group" do
25
+
26
+ it "should return an object that allows to access the parameter value in a natural way" do
27
+ code = <<-CODE
28
+ group "group1" do
29
+ export :var2 => "value2"
30
+ end
31
+ CODE
32
+
33
+ pdsl = ParameterDsl.new
34
+ group = pdsl.parse(code)
35
+
36
+ group.group1.var2.should == "value2"
37
+ end
38
+ end
39
+
40
+ context "when the DSL defines a parameter for which the value is a parameter from another group" do
41
+
42
+ it "should return an object that allows to access the parameter value in a natural way" do
43
+ code = <<-CODE
44
+ group "group3" do
45
+ export :var3 => "value3"
46
+ end
47
+
48
+ group "group4" do
49
+ export :var4 => group3.var3
50
+ end
51
+ CODE
52
+
53
+
54
+ pdsl = ParameterDsl.new
55
+ group = pdsl.parse(code)
56
+
57
+ group.group4.var4.should == group.group3.var3
58
+ end
59
+ end
60
+
61
+ context "when the DSL ask to mix a group into the current group" do
62
+
63
+ let (:code) {
64
+ <<-CODE
65
+ group "source" do
66
+ group "subgroup" do
67
+ export :var_in_subgroup => "value_in_subgroup"
68
+ end
69
+ export :var_in_group => "value_in_group"
70
+ end
71
+ CODE
72
+ }
73
+
74
+ context "and the group to include is given as a String" do
75
+
76
+ it "should include in the current group all parameters and sub groups defined in the source group" do
77
+
78
+ my_code = code + <<-CODE
79
+ group :target do
80
+ include_group "source"
81
+ end
82
+ CODE
83
+
84
+ pdsl = ParameterDsl.new
85
+ group = pdsl.parse(my_code)
86
+
87
+ group.target.subgroup.var_in_subgroup.should == "value_in_subgroup"
88
+ group.target.var_in_group.should == "value_in_group"
89
+
90
+ end
91
+ end
92
+
93
+ context "and the group to include is given as a Group" do
94
+
95
+ it "should include in the current group all parameters and sub groups defined in the source group" do
96
+ my_code = code + <<-CODE
97
+ group :target do
98
+ include_group source
99
+ end
100
+ CODE
101
+
102
+ pdsl = ParameterDsl.new
103
+ group = pdsl.parse(my_code)
104
+
105
+ group.target.subgroup.var_in_subgroup.should == "value_in_subgroup"
106
+ group.target.var_in_group.should == "value_in_group"
107
+ end
108
+ end
109
+ end
110
+
111
+ context "when a group is defined twice" do
112
+
113
+ it "should merge the definition of each group into a single one" do
114
+ my_code = <<-CODE
115
+ group :group1 do
116
+ export :var1 => "value1"
117
+ end
118
+
119
+ group :group1 do
120
+ export :var2 => "value2"
121
+ end
122
+ CODE
123
+
124
+ pdsl = ParameterDsl.new
125
+ group = pdsl.parse(my_code)
126
+
127
+ group.group1.var1.should == "value1"
128
+ group.group1.var2.should == "value2"
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end