templator 0.1 → 0.2

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 CHANGED
@@ -0,0 +1,6 @@
1
+ = 0.1.1 / 2012-02-03
2
+ * Fix bug when doing recursive calls to include_file action,
3
+ only the result of the last call was included in the generated file.
4
+
5
+ = 0.1
6
+ * First release
data/Gemfile CHANGED
@@ -8,4 +8,6 @@ end
8
8
 
9
9
  group :test do
10
10
  gem "rspec"
11
+ gem "fakefs"
12
+ gem "pry"
11
13
  end
@@ -1,22 +1,32 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ coderay (1.0.8)
4
5
  diff-lcs (1.1.3)
5
- rspec (2.7.0)
6
- rspec-core (~> 2.7.0)
7
- rspec-expectations (~> 2.7.0)
8
- rspec-mocks (~> 2.7.0)
9
- rspec-core (2.7.1)
10
- rspec-expectations (2.7.0)
11
- diff-lcs (~> 1.1.2)
12
- rspec-mocks (2.7.0)
13
- thor (0.14.6)
14
- yard (0.7.3)
6
+ fakefs (0.4.2)
7
+ method_source (0.8.1)
8
+ pry (0.9.10)
9
+ coderay (~> 1.0.5)
10
+ method_source (~> 0.8)
11
+ slop (~> 3.3.1)
12
+ rspec (2.12.0)
13
+ rspec-core (~> 2.12.0)
14
+ rspec-expectations (~> 2.12.0)
15
+ rspec-mocks (~> 2.12.0)
16
+ rspec-core (2.12.0)
17
+ rspec-expectations (2.12.0)
18
+ diff-lcs (~> 1.1.3)
19
+ rspec-mocks (2.12.0)
20
+ slop (3.3.3)
21
+ thor (0.16.0)
22
+ yard (0.8.3)
15
23
 
16
24
  PLATFORMS
17
25
  ruby
18
26
 
19
27
  DEPENDENCIES
28
+ fakefs
29
+ pry
20
30
  rspec
21
31
  thor
22
32
  yard
data/README.md CHANGED
@@ -99,56 +99,56 @@ and :parameter2 is gotten by invoking the corresponding methods, parameter1 et p
99
99
 
100
100
  The group method allows to define a subset of parameters.
101
101
 
102
- group :my_group {
102
+ group :my_group do
103
103
  export :my_parameter => "my_value"
104
- }
104
+ end
105
105
 
106
106
  Nested group is also possible:
107
107
 
108
- group :top {
109
- group :inner {
108
+ group :top do
109
+ group :inner do
110
110
  ...
111
- }
112
- }
111
+ end
112
+ end
113
113
 
114
114
  Value of parameters defined in other groups must be retrieved with
115
115
  the fully qualified name of the parameter in dot notation.
116
116
 
117
- group :foo_group {
117
+ group :foo_group do
118
118
  export :foo => "foo"
119
- }
119
+ end
120
120
 
121
- group :bar_group {
121
+ group :bar_group do
122
122
  export :bar => "bar"
123
- }
123
+ end
124
124
 
125
- group :foobar_group {
125
+ group :foobar_group do
126
126
  export :foobar => foo_group.foo + bar_group.bar
127
- }
127
+ end
128
128
 
129
129
  A group can de defined multiple times. The resulting group is a merge of all
130
130
  definitions taking into account the order of the parsing:
131
131
 
132
132
  #file1
133
- group :my_group {
133
+ group :my_group do
134
134
  export :parameter1 => 1
135
135
  export :parameter2 => 2
136
- }
136
+ end
137
137
 
138
138
  #file2
139
- group :my_group {
139
+ group :my_group do
140
140
  export :parameter1 => 0.99999
141
141
  export :parameter3 => 3
142
- }
142
+ end
143
143
 
144
144
  Assuming that file1 and file2 are parsed in this order, the resulting group
145
145
  is semantically equivalent to this one:
146
146
 
147
- group :my_group {
147
+ group :my_group do
148
148
  export :parameter1 => 0.99999
149
149
  export :parameter2 => 2
150
150
  export :parameter3 => 3
151
- }
151
+ end
152
152
 
153
153
  * __include_group__
154
154
 
@@ -158,21 +158,21 @@ It is conceptually equivalent to the well known Ruby include method.
158
158
 
159
159
  Consider the following example:
160
160
 
161
- group :mixin {
161
+ group :mixin do
162
162
  export :mixme => "some value"
163
- }
163
+ end
164
164
 
165
- group :my_group {
165
+ group :my_group do
166
166
  include_group :mixin
167
167
  export :another_parameter => "another_value"
168
- }
168
+ end
169
169
 
170
170
  Thus, the resulting group is equivalent to :
171
171
 
172
- group :my_group {
172
+ group :my_group do
173
173
  export :mixme => "some value"
174
174
  export :another_parameter => "another_value"
175
- }
175
+ end
176
176
 
177
177
  Template Actions
178
178
  ----------------
@@ -183,15 +183,15 @@ In addition to the features provided by ERB, the following extra methods can be
183
183
 
184
184
  * __param__
185
185
 
186
- This method allows to retrieve the value of a parameter.
186
+ This method allows to retrieve the value of parameters passed to Templator by the __-p__ swicth.
187
187
 
188
188
  Here is a concrete example:
189
189
 
190
190
  File _parameters.txt_:
191
191
 
192
- group :my_group {
193
- export my_parameter => "my_value"
194
- }
192
+ group :my_group do
193
+ export :my_parameter => "my_value"
194
+ end
195
195
  ...
196
196
 
197
197
  File _template.txt_:
@@ -229,7 +229,7 @@ Here is an example that dynamically generates the name of the template to
229
229
  include according to the value of a parameter:
230
230
 
231
231
  blah blah blah
232
- <%= include_file "#{param :my_parameter}.txt"
232
+ <%= include_file "#{param :my_parameter}.txt" %>
233
233
 
234
234
  The path of the template to include is interpreted relatively from the path
235
235
  of the source template.
@@ -255,23 +255,27 @@ file for three different hosts.
255
255
 
256
256
  File _hosts.txt_:
257
257
 
258
- group :host_a {
258
+ group :common_parameters do
259
+ export :gateway => "192.168.121.254"
260
+ end
261
+
262
+ group :host_a do
259
263
  export :address => "192.168.121.1"
260
264
  export :netmask => "255.255.255.0"
261
- export :gateway => "192.168.121.254"
262
- }
265
+ include_group :common_parameters
266
+ end
263
267
 
264
- group :host_b {
265
- export :address => "192.168.122.1"
268
+ group :host_b do
269
+ export :address => "192.168.121.2"
266
270
  export :netmask => "255.255.255.0"
267
- export :gateway => "192.168.122.254"
268
- }
271
+ include_group :common_parameters
272
+ end
269
273
 
270
- group :host_c {
271
- export :address => "192.168.123.1"
274
+ group :host_c do
275
+ export :address => "192.168.121.3"
272
276
  export :netmask => "255.255.255.0"
273
- export :gateway => "192.168.123.254"
274
- }
277
+ include_group :common_parameters
278
+ end
275
279
 
276
280
  File _interfaces.txt_:
277
281
 
@@ -4,7 +4,9 @@ if RUBY_VERSION < "1.9"
4
4
  require "rubygems"
5
5
  end
6
6
 
7
- $LOAD_PATH << "../lib"
7
+ lib_dir=File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
8
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
9
+
8
10
  require "thor"
9
11
  require "templator/parameters"
10
12
  require "templator/actions"
@@ -36,7 +38,12 @@ class TemplatorCli < Thor
36
38
  @template = template
37
39
 
38
40
  if options.has_key?("parameter-files")
39
- @parameters = Templator::Parameters.load_files *options["parameter-files"]
41
+ begin
42
+ @parameters = Templator::Parameters.load_files *options["parameter-files"]
43
+ rescue Exception => e
44
+ error e
45
+ exit 1
46
+ end
40
47
  end
41
48
 
42
49
  template template, output
@@ -94,43 +101,6 @@ class TemplatorCli < Thor
94
101
  def method_missing(method, *args)
95
102
  @parameters.get(method)
96
103
  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
104
  end
135
105
  end
136
106
 
@@ -28,7 +28,7 @@ module Templator
28
28
  search_path.each do |dir|
29
29
  path = File.join(dir, filename)
30
30
  if File.exist?(path)
31
- content = ERB.new(::File.read(path), nil, '-', '@included_template').result(binding)
31
+ content = ERB.new(::File.read(path), nil, '-', 'included_template').result(binding)
32
32
  throw :file_found
33
33
  end
34
34
  end
@@ -1,6 +1,7 @@
1
1
  module Templator
2
2
 
3
- # Defines and interprets the methods of the Parameter DSL.
3
+ # Parse a the given files with respect to the Parameter DSL.
4
+ #
4
5
  # Supported DSL methods are :
5
6
  # * export(hash) : defines a list of parameters from the given hash
6
7
  # * group(name, block) : defines a group of parameter
@@ -23,32 +24,142 @@ module Templator
23
24
  # include_group "group2.group3"
24
25
  # end
25
26
  #
26
- #
27
27
  # param5 value can retrieved with the following :
28
- # p = ParameterDsl.new.parse(code)
28
+ # p = ParameterFileLoader.new.parse("path/to/parameter_file")
29
29
  # p.group2.param5
30
30
  #
31
- #
32
- class ParameterDsl
31
+ class ParameterFileLoader
33
32
 
34
- # Name of the implicit top level group
35
- TOP_LEVEL_GROUP_NAME = "__top__"
33
+ # Parses the given files
34
+ # @param [String[]] files files to parse
35
+ # @return [Object] a dynamically built object
36
+ # whose methods allow to access values of parameters defined in given code.
37
+ def parse(*files)
38
+ files.each do |file|
39
+ begin
40
+ load file
41
+ rescue ::Exception => e
42
+ raise ParseError.new(e, file)
43
+ end
44
+ end
45
+ DslContext.top_level_group
46
+ end
36
47
 
37
- def initialize
38
- #initialize the top level group
39
- @group_stack = []
40
- @group_stack.push Group.new(TOP_LEVEL_GROUP_NAME)
48
+ end
49
+
50
+ # Error wrapper of all errors occuring during the parsing of a parameter file.
51
+ # This wrapper provides convenient methods to retrieve the origin of the error.
52
+ class ParseError < Exception
53
+
54
+ attr_reader :file, :line
55
+
56
+ def initialize(original_exception, file)
57
+ @original_exception = original_exception
58
+ @file = file
59
+
60
+ process original_exception
41
61
  end
42
62
 
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
63
+
64
+ def message_to_s
65
+ @message.sub(/\A.*:\d+:\s*/, "")
66
+ end
67
+
68
+ def origin_to_s
69
+ "in file #{file}" + (@line ? " line #{@line}" : "")
70
+ end
71
+
72
+
73
+ def to_s
74
+ "ParseError #{origin_to_s}: #{message_to_s}"
75
+ end
76
+
77
+ private
78
+
79
+ def process(exception)
80
+ @message = exception.message
81
+
82
+ case exception
83
+ when LoadError, ::SyntaxError
84
+ trace = nil
85
+ else
86
+ trace = exception.backtrace.drop_while {|line| line.match(/#{__FILE__}/)}.first
87
+ end
88
+
89
+ @line = find_line_number_in_trace trace if trace
90
+ end
91
+
92
+ def find_line_number_in_trace(trace)
93
+ line = nil
94
+ matcher = trace.match(/:(\d+):/)
95
+ line = matcher[1].to_i if matcher
96
+ end
97
+
98
+ end
99
+
100
+ # Base class to define a group.
101
+ # A Group instance is created whenever a group method is parsed from the DSL code.
102
+ # Methods are dynamically created inside the singleton of the instance to access nested parameters and groups.
103
+ class Group
104
+ attr_reader :name
105
+ def initialize(name)
106
+ @name = name
107
+ end
108
+ end
109
+
110
+ # Context used by the DSL methods to retrieve and update
111
+ # the current group.
112
+ class DslContext
113
+
114
+ # Retrieve the current group from the context
115
+ # @return [Group] the current group
116
+ def self.current_group
117
+ group_stack.last
118
+ end
119
+
120
+ # Retrieve the top level group from the context
121
+ # @return [Group] the top level group
122
+ def self.top_level_group
123
+ group_stack.first
124
+ end
125
+
126
+ # Enter a new group.
127
+ # This method shall be called by the DSL methods whenever
128
+ # a new group is entered.
129
+ # @param [Group] group group entered.
130
+ def self.enter_group(group)
131
+ group_stack.push(group)
132
+ end
133
+
134
+ # Leave a group.
135
+ # This method shall be calles by the DSL methods whenever
136
+ # a group is left.
137
+ def self.leave_group
138
+ group_stack.pop
50
139
  end
51
140
 
141
+ private
142
+
143
+ # Retrieve the stack of groups.
144
+ # The stack is automatically created on the first call
145
+ # and the top level group is inserted as the first element
146
+ # of the stack.
147
+ # @return [Array<Group>] the stack of groups.
148
+ def self.group_stack
149
+ if (@group_stack.nil?)
150
+ @group_stack = []
151
+ @group_stack.push Group.new(DslMethods::TOP_LEVEL_GROUP_NAME)
152
+ end
153
+ @group_stack
154
+ end
155
+ end
156
+
157
+ # Module that defines the Parameter DSL methods.
158
+ module DslMethods
159
+
160
+ # Name of the implicit top level group
161
+ TOP_LEVEL_GROUP_NAME = "__top__"
162
+
52
163
  # Defines parameters providing a name and a value for each parameter.
53
164
  # @param [Hash]params hash of parameter name and value
54
165
  def export(params)
@@ -80,21 +191,21 @@ module Templator
80
191
 
81
192
  private
82
193
 
83
- # Gets the current group from the group stack
194
+ # Gets the current group from the DslContext
84
195
  def current_group
85
- @group_stack.last
196
+ DslContext.current_group
86
197
  end
87
198
 
88
199
  # 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
200
+ # * create a new Group instance (or retrieve it if it already exists in the current context)
201
+ # * define a method in the current group to access the new group
202
+ # * notify the context about the entry in a new group
203
+ # @param [#to_s] name name of the new group
93
204
  #
94
205
  def enter_group(name)
95
206
  group = group_in_current_context(name) || Group.new(name)
96
207
  define_method_in_current_group(name) {group}
97
- @group_stack.push(group)
208
+ DslContext.enter_group(group)
98
209
  end
99
210
 
100
211
  # Defines a method inside the current group
@@ -102,43 +213,32 @@ module Templator
102
213
  # @param [Block] method_block block of the méthode to define
103
214
  def define_method_in_current_group(method_name, &method_block)
104
215
  (class << current_group; self; end).send(:define_method, method_name, method_block)
105
- end
216
+ end
106
217
 
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
218
+ # Verify if a group belongs to the current group
219
+ # @param [#to_s] name name of the group to control
220
+ # @return the group with the given name if it belongs to the current group, nil otherwise
221
+ def group_in_current_context(name)
222
+ current_group.respond_to?(name) ? current_group.send(name) : nil
223
+ end
113
224
 
114
- # Manages the exit from a group
115
- def leave_group
116
- @group_stack.pop
117
- end
225
+ # Manages the exit from a group
226
+ def leave_group
227
+ DslContext.leave_group
228
+ end
118
229
 
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
230
+ # Manages the access to a parameter outside of the current group
231
+ def method_missing(name, *args)
232
+ DslContext.top_level_group.send(name, *args)
233
+ end
123
234
 
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
235
+ # Get a group from its fully qualified name
236
+ def get_group(fully_qualified_name)
237
+ fully_qualified_name.to_s.split('.').inject(DslContext.top_level_group) {|result, name| result.send(name)}
238
+ end
128
239
 
129
- # Return the top level goup
130
- def top_level_group
131
- @group_stack.first
132
240
  end
133
-
134
241
  end
135
242
 
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
243
+ #inject the DSL methos in the main object
244
+ extend Templator::DslMethods