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.
- data/CHANGES +0 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +22 -0
- data/LICENSE +504 -0
- data/README.md +293 -0
- data/Rakefile +9 -0
- data/TODO +0 -0
- data/bin/templator +137 -0
- data/lib/templator/actions.rb +39 -0
- data/lib/templator/parameter_code_loader.rb +51 -0
- data/lib/templator/parameter_dsl.rb +144 -0
- data/lib/templator/parameters.rb +41 -0
- data/spec/templator/actions_spec.rb +97 -0
- data/spec/templator/parameter_code_loader_spec.rb +29 -0
- data/spec/templator/parameter_dsl_spec.rb +133 -0
- data/spec/templator/parameters_spec.rb +27 -0
- metadata +256 -0
@@ -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
|