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,293 @@
1
+ Templator
2
+ =========
3
+
4
+ Description
5
+ -----------
6
+ Templator is a command line tool allowing to generate text documents from templates written
7
+ in the [ERB](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/erb/rdoc/ERB.html) template language.
8
+
9
+ It also provides a Domain Specific Language, the _Parameter DSL_, to define a set of parameters
10
+ that can be referenced from template files in order to generate the target document
11
+ with expected values.
12
+
13
+ Templator is developped in Ruby. It requires Ruby 1.8.7 and higher, or any version of the 1.9 branch.
14
+
15
+ Installation
16
+ ------------
17
+
18
+ To quickly install Templator, use the following command:
19
+
20
+ gem install templator
21
+
22
+ Usage
23
+ -----
24
+
25
+ The following command allows to display the online help:
26
+
27
+ $ templator help
28
+
29
+ Two tasks are available from the command line:
30
+
31
+ * __gen__
32
+
33
+ This task is responsible for the transformation of a given template to a target document,
34
+ taking into account any provided parameter files.
35
+
36
+ Here is the most simple command line invokation.
37
+
38
+ $ templator gen path/to/template path/to/target
39
+
40
+ Files that define parameters can be passed to Templator with the __-p__ switch:
41
+
42
+ $ templator gen path/to/template path/to/target -p path/to/paramaters1 path/to/parameters2
43
+
44
+ When parameter files are passed, Templator firstly parses these files with respect to the Parameter DSL (see below).
45
+ Files are parsed in the same order that they are provided by the __-p__ switch.
46
+ All parameters exported from these files are then visible by the template.
47
+
48
+ The __-c__ switch allows to define a default context from which Templator will try to
49
+ resolve parameter names that are not fully qualified in the template. More details are provided
50
+ at the end of this document.
51
+
52
+
53
+ * __get_param__
54
+
55
+ This task allows to get the value of a parameter from the provided parameter files.
56
+
57
+ $ templator get_param 'my_parameter' -p path/to/parameters
58
+
59
+ Parameter DSL
60
+ -------------
61
+
62
+ The set of parameters is expressed in a Ruby DSL that provides following methods:
63
+
64
+ * __export__
65
+
66
+ This method allows to define new parameters and make them visible from a template during
67
+ the generation process. Following example shows how to define the parameter 'my_parameter' with
68
+ the value 'my_value'
69
+
70
+ export "my_parameter" => "my_value"
71
+
72
+ It is also possible to define several parameters in a single export line:
73
+
74
+ export "my_parameter" => "my_value", "my_other_parameter" => "my_other_value"
75
+
76
+
77
+ It is worth noting that parameter names can be a Ruby Symbol:
78
+
79
+ export :my_parameter => "my_value"
80
+
81
+ More over, the parameter value can be any Ruby valid expression, for example:
82
+
83
+ export :integer => 3
84
+ export :now => Time.now
85
+ export :upper_parameter => "my_value".upcase
86
+
87
+ Last but not least, you can use the value of previously defined parameters to build a
88
+ more complex parameter:
89
+
90
+ export :parameter1 => 1
91
+ export :parameter2 => 2
92
+ export :sum => parameter1 + parameter2
93
+
94
+ Each time the Parameter DSL parser encounters an exported parameter, it defines
95
+ a method with the same name. In the previous example, the value of :parameter1
96
+ and :parameter2 is gotten by invoking the corresponding methods, parameter1 et parameter2.
97
+
98
+ * __group__
99
+
100
+ The group method allows to define a subset of parameters.
101
+
102
+ group :my_group {
103
+ export :my_parameter => "my_value"
104
+ }
105
+
106
+ Nested group is also possible:
107
+
108
+ group :top {
109
+ group :inner {
110
+ ...
111
+ }
112
+ }
113
+
114
+ Value of parameters defined in other groups must be retrieved with
115
+ the fully qualified name of the parameter in dot notation.
116
+
117
+ group :foo_group {
118
+ export :foo => "foo"
119
+ }
120
+
121
+ group :bar_group {
122
+ export :bar => "bar"
123
+ }
124
+
125
+ group :foobar_group {
126
+ export :foobar => foo_group.foo + bar_group.bar
127
+ }
128
+
129
+ A group can de defined multiple times. The resulting group is a merge of all
130
+ definitions taking into account the order of the parsing:
131
+
132
+ #file1
133
+ group :my_group {
134
+ export :parameter1 => 1
135
+ export :parameter2 => 2
136
+ }
137
+
138
+ #file2
139
+ group :my_group {
140
+ export :parameter1 => 0.99999
141
+ export :parameter3 => 3
142
+ }
143
+
144
+ Assuming that file1 and file2 are parsed in this order, the resulting group
145
+ is semantically equivalent to this one:
146
+
147
+ group :my_group {
148
+ export :parameter1 => 0.99999
149
+ export :parameter2 => 2
150
+ export :parameter3 => 3
151
+ }
152
+
153
+ * __include_group__
154
+
155
+ The include_group method is an interesting way to share some common parameters between different groups.
156
+ It allows to mix the parameters of a group in another one.
157
+ It is conceptually equivalent to the well known Ruby include method.
158
+
159
+ Consider the following example:
160
+
161
+ group :mixin {
162
+ export :mixme => "some value"
163
+ }
164
+
165
+ group :my_group {
166
+ include_group :mixin
167
+ export :another_parameter => "another_value"
168
+ }
169
+
170
+ Thus, the resulting group is equivalent to :
171
+
172
+ group :my_group {
173
+ export :mixme => "some value"
174
+ export :another_parameter => "another_value"
175
+ }
176
+
177
+ Template Actions
178
+ ----------------
179
+
180
+ As said before, the template language used by Templator is ERB.
181
+
182
+ In addition to the features provided by ERB, the following extra methods can be invoked from a template:
183
+
184
+ * __param__
185
+
186
+ This method allows to retrieve the value of a parameter.
187
+
188
+ Here is a concrete example:
189
+
190
+ File _parameters.txt_:
191
+
192
+ group :my_group {
193
+ export my_parameter => "my_value"
194
+ }
195
+ ...
196
+
197
+ File _template.txt_:
198
+
199
+ The value of the parameter "my_parameter" defined in the group "my_group" is <%= param "my_group.my_parameter" %>
200
+
201
+ Command line invokation from the shell:
202
+
203
+ $ templator gen template.txt output -p parameters.txt
204
+
205
+ The resulting _output_ file should have the following content:
206
+
207
+ The value of the parameter "my_parameter" defined in the group "my_group" is my_value
208
+
209
+ * __param_exists?__
210
+
211
+ This method tests if a parameter is defined.
212
+ Consider the following template example:
213
+
214
+ <% if param_exists? "my_group.my_parameter" %>
215
+ The parameter "my_parameter" is well defined in group "my_group".
216
+ <% else %>
217
+ There is no parameter "my_parameter" defined in group "my_group".
218
+ <% end %>
219
+
220
+ * __include_file__
221
+
222
+
223
+ This method parses the content of the given file as an ERB template,
224
+ and appends the resulting text into the output stream of the source template.
225
+
226
+ This is a convenient method to spread a template on multiple files.
227
+
228
+ Here is an example that dynamically generates the name of the template to
229
+ include according to the value of a parameter:
230
+
231
+ blah blah blah
232
+ <%= include_file "#{param :my_parameter}.txt"
233
+
234
+ The path of the template to include is interpreted relatively from the path
235
+ of the source template.
236
+
237
+ Contextual resolution of parameter names
238
+
239
+ Context
240
+ -------
241
+
242
+ A context is defined with the __-c__ switch. It is eventually
243
+ used by the __param__ and __param_exists?__ methods to resolve
244
+ the provided parameter names.
245
+ Whenever the resolution of a parameter name fails,
246
+ if a context is defined, it is prepended to the parameter name
247
+ and a new resolution is tried with the resulting name. In this case,
248
+ you must ensure that the context matches a valid fully qualified group name.
249
+
250
+ Context is a convenient way to generate different documents from the same template,
251
+ assuming that a group of parameters is defined for each expected documents.
252
+
253
+ Here is a concrete example where the objective is to generate a Debian /etc/network/interfaces
254
+ file for three different hosts.
255
+
256
+ File _hosts.txt_:
257
+
258
+ group :host_a {
259
+ export :address => "192.168.121.1"
260
+ export :netmask => "255.255.255.0"
261
+ export :gateway => "192.168.121.254"
262
+ }
263
+
264
+ group :host_b {
265
+ export :address => "192.168.122.1"
266
+ export :netmask => "255.255.255.0"
267
+ export :gateway => "192.168.122.254"
268
+ }
269
+
270
+ group :host_c {
271
+ export :address => "192.168.123.1"
272
+ export :netmask => "255.255.255.0"
273
+ export :gateway => "192.168.123.254"
274
+ }
275
+
276
+ File _interfaces.txt_:
277
+
278
+ iface eth0 inet static
279
+ address <%= param :address %>
280
+ netmask <%= param :netmask %>
281
+ gateway <%= param :gateway %>
282
+
283
+ Command line execution from shell:
284
+
285
+ for host in host_a host_b host_c
286
+ do
287
+ templator gen interfaces.txt interfaces.$host -p hosts.txt -c $host
288
+ done
289
+
290
+ Copyright
291
+ ---------
292
+
293
+ Copyright © 2011 Christophe Arguel. See LICENSE for details.
@@ -0,0 +1,9 @@
1
+ require 'rake/gempackagetask'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+
7
+ spec = Gem::Specification.load('templator.gemspec')
8
+ Rake::GemPackageTask.new(spec).define
9
+
data/TODO ADDED
File without changes
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if RUBY_VERSION < "1.9"
4
+ require "rubygems"
5
+ end
6
+
7
+ $LOAD_PATH << "../lib"
8
+ require "thor"
9
+ require "templator/parameters"
10
+ require "templator/actions"
11
+
12
+
13
+ class TemplatorCli < Thor
14
+ include Thor::Actions
15
+ include Templator::Actions
16
+
17
+
18
+ # Root directory to prepend to relative paths
19
+ source_root(Dir.pwd)
20
+
21
+ #
22
+ # THOR TASK gen
23
+ # Generate a file from a template
24
+ #
25
+ desc "gen TEMPLATE OUTPUT", "Generate a file from a template"
26
+ method_option "parameter-files",
27
+ :aliases => "-p",
28
+ :type => :array,
29
+ :desc => "list of files and directories, that defines parameters"
30
+ method_option "context",
31
+ :aliases => '-c',
32
+ :type => :string,
33
+ :desc => "context name prepended to parameter name from template (action <%=param name%>)"
34
+ def gen(template, output)
35
+
36
+ @template = template
37
+
38
+ if options.has_key?("parameter-files")
39
+ @parameters = Templator::Parameters.load_files *options["parameter-files"]
40
+ end
41
+
42
+ template template, output
43
+ end
44
+
45
+
46
+ #
47
+ # THOR TASK get_param
48
+ # Get the value of a parameter from provided parameter files.
49
+ #
50
+ desc "get_param PARAMETER_NAME", "Get a parameter value"
51
+ method_option "parameter-files",
52
+ :aliases => "-p",
53
+ :required => true,
54
+ :type => :array,
55
+ :desc => "list of files and directories, that defines parameters"
56
+ method_option "context",
57
+ :aliases => '-c',
58
+ :type => :string,
59
+ :desc => "context name prepended to parameter name from template (action <%=param name%>)"
60
+ def get_param(parameter_name)
61
+
62
+ if options.has_key?("parameter-files")
63
+ @parameters = Templator::Parameters.load_files *options["parameter-files"]
64
+ end
65
+
66
+ begin
67
+ puts param(parameter_name)
68
+ rescue
69
+ STDERR.puts "%{parameter_name} is not defined"
70
+ exit 1
71
+ end
72
+
73
+ exit 0
74
+ end
75
+
76
+
77
+ #
78
+ # Internal methods
79
+ #
80
+ no_tasks do
81
+
82
+ def parameters
83
+ @parameters
84
+ end
85
+
86
+ def context
87
+ @options["context"]
88
+ end
89
+
90
+ def search_path
91
+ [File.expand_path(File.dirname(@template))]
92
+ end
93
+
94
+ def method_missing(method, *args)
95
+ @parameters.get(method)
96
+ end
97
+
98
+ # Check global consistency of provided options
99
+ def sanity_check
100
+
101
+ #mutually exclusive options
102
+ mandatory_and_mutually_exclusive "template-directory", "template-file"
103
+ optional_and_mutually_exclusive "output-directory", "output-file"
104
+
105
+ #dependent option
106
+ dependent "template-file", "output-file"
107
+ dependent "template-directory", "output-directory"
108
+ end
109
+
110
+ # Check that options hash has one and only one of two given keys
111
+ def mandatory_and_mutually_exclusive(key1, key2)
112
+ raise Thor::Error.new("One of --#{key1} or --#{key2} must be provided. Try again.") unless (options.has_key?(key1) ^ options.has_key?(key2))
113
+ end
114
+
115
+ # Check that options hash has zero or one of two given keys
116
+ def optional_and_mutually_exclusive(key1, key2)
117
+ raise Thor::Error.new("Only one of --#{key1} or --#{key2} must be provided. Try again.") unless (options.has_key(key1) || options.has_key?(key2)) || ! (options.has_key?(key1) && options.has_key?(key2))
118
+ end
119
+
120
+ # Check that options hash has child_key if it has parent_key.
121
+ def dependent(parent_key, child_key)
122
+ raise Thor::Error.new("--#{child_key} must be provided when using --#{parent_key}. Try again.") if (options.has_key?(parent_key) && ! options.has_key?(child_key))
123
+ end
124
+
125
+ # Build a list of template files from provided options
126
+ def templates
127
+ options.has_key?("template-file") ? [options["template-file"]] : options["template-directory"]
128
+ end
129
+
130
+ # Build the output path based on the given option and the current template file.
131
+ def output_path(template_file)
132
+ options.has_key?("output-directory") ? File.join(options["output-directory"], template_file) : options["output-file"]
133
+ end
134
+ end
135
+ end
136
+
137
+ TemplatorCli.start
@@ -0,0 +1,39 @@
1
+ require 'tempfile'
2
+ require 'erb'
3
+
4
+ module Templator
5
+
6
+ module Actions
7
+
8
+ def param(name)
9
+ begin
10
+ parameters.get(name)
11
+ rescue NoMethodError => e
12
+ parameters.get("#{context}.#{name}")
13
+ end
14
+ end
15
+
16
+ def param_exists?(name)
17
+ begin
18
+ param(name)
19
+ true
20
+ rescue
21
+ false
22
+ end
23
+ end
24
+
25
+ def include_file(filename)
26
+ content=""
27
+ catch(:file_found) do
28
+ search_path.each do |dir|
29
+ path = File.join(dir, filename)
30
+ if File.exist?(path)
31
+ content = ERB.new(::File.read(path), nil, '-', '@included_template').result(binding)
32
+ throw :file_found
33
+ end
34
+ end
35
+ end
36
+ return content
37
+ end
38
+ end
39
+ end