jeckyl 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Bugs.rdoc +6 -0
- data/Gemfile +6 -0
- data/History.txt +74 -0
- data/Intro.txt +5 -0
- data/LICENCE.rdoc +159 -0
- data/bin/jeckyl +239 -0
- data/lib/jeckyl/errors.rb +35 -0
- data/lib/jeckyl/version.rb +13 -0
- data/lib/jeckyl.rb +619 -0
- data/spec/check_config_spec.rb +57 -0
- data/spec/jeckyl_spec.rb +202 -0
- data/spec/spec_helper.rb +11 -0
- data/test/conf.d/a_few_params +2 -0
- data/test/conf.d/bad_filename +2 -0
- data/test/conf.d/bclass.rb +2 -0
- data/test/conf.d/defaults.rb +0 -0
- data/test/conf.d/jeckyl +46 -0
- data/test/conf.d/jeckyl_check +46 -0
- data/test/conf.d/merger.rb +31 -0
- data/test/conf.d/no_such_file +2 -0
- data/test/conf.d/not_a_bool +1 -0
- data/test/conf.d/not_a_flag +1 -0
- data/test/conf.d/not_a_float +1 -0
- data/test/conf.d/not_a_hash +1 -0
- data/test/conf.d/not_a_member +1 -0
- data/test/conf.d/not_a_pattern +1 -0
- data/test/conf.d/not_a_set +1 -0
- data/test/conf.d/not_a_string +1 -0
- data/test/conf.d/not_an_array +1 -0
- data/test/conf.d/not_an_email +1 -0
- data/test/conf.d/not_an_integer_array +1 -0
- data/test/conf.d/out_of_range +1 -0
- data/test/conf.d/sloppy_params +5 -0
- data/test/conf.d/syntax_error +3 -0
- data/test/conf.d/unknown_param +2 -0
- data/test/conf.d/unwritable_dir +1 -0
- data/test/conf.d/wrong_type +2 -0
- data/test/reports/not_ok.txt +1 -0
- data/test/reports/ok.txt +1 -0
- data/test/test_class.rb +32 -0
- data/test/test_configurator.rb +105 -0
- data/test/test_configurator_errors.rb +105 -0
- data/test/test_subclass.rb +11 -0
- metadata +144 -0
data/lib/jeckyl.rb
ADDED
@@ -0,0 +1,619 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Robert Sharp
|
3
|
+
# Copyright:: Copyright (c) 2011 Robert Sharp
|
4
|
+
# License:: Open Software Licence v3.0
|
5
|
+
#
|
6
|
+
# This software is licensed for use under the Open Software Licence v. 3.0
|
7
|
+
# The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
8
|
+
# and in the file copyright.txt. Under the terms of this licence, all derivative works
|
9
|
+
# must themselves be licensed under the Open Software Licence v. 3.0
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# == JECKYL
|
13
|
+
#
|
14
|
+
require 'jeckyl/version'
|
15
|
+
require 'jeckyl/errors'
|
16
|
+
|
17
|
+
#
|
18
|
+
# The Jeckyl configurator module, which is just a wrapper. See {file:README.md Readme} for details.
|
19
|
+
#
|
20
|
+
module Jeckyl
|
21
|
+
|
22
|
+
#default location for all config files
|
23
|
+
ConfigRoot = '/etc/jermine'
|
24
|
+
|
25
|
+
# This is the main Jeckyl class from which to create specific application
|
26
|
+
# classes. For example, to create a new set of parameters, define a class as
|
27
|
+
#
|
28
|
+
# class MyConfig < Jeckyl::Config
|
29
|
+
#
|
30
|
+
# More details are available in the {file:README.md Readme} file
|
31
|
+
class Config < Hash
|
32
|
+
|
33
|
+
# create a configuration hash by evaluating the parameters defined in the given config file.
|
34
|
+
#
|
35
|
+
# @param [String] config_file string path to a ruby file,
|
36
|
+
# @param [Hash] opts contains the following options.
|
37
|
+
# @option opts [Boolean] :flag_errors_on_defaults will raise exceptions from checks during default
|
38
|
+
# evaluation - although why is not clear, so best not to use it.
|
39
|
+
# @option opts [Boolean] :local limits generated defaults to the direct class being evaluated
|
40
|
+
# and should only be set internally on this call
|
41
|
+
# @option opts [Boolean] :relax, if set to true will not check for parameter methods but instead
|
42
|
+
# add unknown methods to the hash unchecked.
|
43
|
+
#
|
44
|
+
# If no config file is given then the hash of options returned will only have
|
45
|
+
# the defaults defined for the given class.
|
46
|
+
#
|
47
|
+
#
|
48
|
+
def initialize(config_file=nil, opts={})
|
49
|
+
# do whatever a hash has to do
|
50
|
+
super()
|
51
|
+
|
52
|
+
flag_errors_on_defaults = opts[:flag_errors_on_defaults] || false
|
53
|
+
local = opts[:local] || false
|
54
|
+
@_relax = opts[:relax] || false
|
55
|
+
|
56
|
+
# somewhere to save the most recently set symbol
|
57
|
+
@_last_symbol = nil
|
58
|
+
# hash for comments accessed with the same symbol
|
59
|
+
@_comments = {}
|
60
|
+
# hash for input defaults
|
61
|
+
@_defaults={}
|
62
|
+
# save order in which methods are defined for generating config files
|
63
|
+
@_order = Array.new
|
64
|
+
|
65
|
+
# get the defaults defined in the config parser
|
66
|
+
get_defaults(:local=> local, :flag_errors => flag_errors_on_defaults)
|
67
|
+
|
68
|
+
return self if config_file.nil?
|
69
|
+
|
70
|
+
# remember where the config file itself is
|
71
|
+
self[:config_files] = [config_file]
|
72
|
+
|
73
|
+
# and finally get the values from the config file itself
|
74
|
+
self.instance_eval(File.read(config_file), config_file)
|
75
|
+
|
76
|
+
rescue SyntaxError => err
|
77
|
+
raise ConfigSyntaxError, err.message
|
78
|
+
rescue Errno::ENOENT
|
79
|
+
# duff file path so tell the caller
|
80
|
+
raise ConfigFileMissing, "#{config_file}"
|
81
|
+
end
|
82
|
+
|
83
|
+
# gives access to a hash containing an entry for each parameter and the comments
|
84
|
+
# defined by the class definitions - used internally by class methods
|
85
|
+
def comments
|
86
|
+
@_comments
|
87
|
+
end
|
88
|
+
|
89
|
+
# This contains an array of the parameter names - used internally by class methods
|
90
|
+
def order
|
91
|
+
@_order
|
92
|
+
end
|
93
|
+
|
94
|
+
# this contains a hash of the defaults for each parameter - used internally by class methods
|
95
|
+
def defaults
|
96
|
+
@_defaults
|
97
|
+
end
|
98
|
+
|
99
|
+
# a class method to check a given config file one item at a time
|
100
|
+
#
|
101
|
+
# This evaluates the given config file and reports if there are any errors to the
|
102
|
+
# report_file, which defaults to Stdout. Can only do the checking one error at a time.
|
103
|
+
#
|
104
|
+
# To use this method, it is necessary to write a script that calls it for the particular
|
105
|
+
# subclass.
|
106
|
+
#
|
107
|
+
# @param [String] config_file is the file to check
|
108
|
+
# @param [String] report_file is a file to write the report to, or stdout
|
109
|
+
# @return [Boolean] indicates if the check was OK or not
|
110
|
+
#
|
111
|
+
def self.check_config(config_file, report_file=nil)
|
112
|
+
|
113
|
+
# create myself to generate defaults, but nothing else
|
114
|
+
me = self.new
|
115
|
+
|
116
|
+
success = true
|
117
|
+
message = "No errors found in: #{config_file}"
|
118
|
+
|
119
|
+
begin
|
120
|
+
# evaluate the config file
|
121
|
+
me.instance_eval(File.read(config_file), config_file)
|
122
|
+
|
123
|
+
rescue Errno::ENOENT
|
124
|
+
message = "No such config file: #{config_file}"
|
125
|
+
success = false
|
126
|
+
rescue JeckylError => err
|
127
|
+
message = err.message
|
128
|
+
success = false
|
129
|
+
rescue SyntaxError => err
|
130
|
+
message = err.message
|
131
|
+
success = false
|
132
|
+
end
|
133
|
+
|
134
|
+
begin
|
135
|
+
if report_file.nil? then
|
136
|
+
puts message
|
137
|
+
else
|
138
|
+
File.open(report_file, "w") do |rfile|
|
139
|
+
rfile.puts message
|
140
|
+
end
|
141
|
+
end
|
142
|
+
return success
|
143
|
+
rescue Errno::ENOENT
|
144
|
+
raise ReportFileError, "Error with file: #{report_file}"
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
# a class method to generate a config file from the class definition
|
150
|
+
#
|
151
|
+
# This calls each of the parameter methods, and creates a commented template
|
152
|
+
# with the comments and default lines
|
153
|
+
#
|
154
|
+
# @param [Boolean] local when set to true will limit the parameters to those defined in the
|
155
|
+
# immediate class and excludes any ancestors.
|
156
|
+
#
|
157
|
+
def self.generate_config(local=false)
|
158
|
+
me = self.new(nil, :local => local)
|
159
|
+
# everything should now exist
|
160
|
+
me.order.each do |key|
|
161
|
+
|
162
|
+
if me.comments.has_key?(key) then
|
163
|
+
me.comments[key].each do |comment|
|
164
|
+
puts "# #{comment}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
def_value = me.defaults[key]
|
168
|
+
default = def_value.nil? ? '' : def_value.inspect
|
169
|
+
|
170
|
+
puts "##{key.to_s} #{default}"
|
171
|
+
puts ""
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# extract only those parameters in a hash that are from the given class
|
176
|
+
#
|
177
|
+
# @param [Hash] full_config is the config from which to extract the intersecting options
|
178
|
+
# and it can be an instance of Jeckyl::Config or a hash
|
179
|
+
# @return [Hash] containing all of the intersecting parameters
|
180
|
+
#
|
181
|
+
# Note this returns a plain hash and not an instance of Jeckyl::Config
|
182
|
+
#
|
183
|
+
def self.intersection(full_config)
|
184
|
+
me = self.new # create the defaults for this class
|
185
|
+
my_hash = {}
|
186
|
+
me.order.each do |my_key|
|
187
|
+
if full_config.has_key?(my_key) then
|
188
|
+
my_hash[my_key] = full_config[my_key]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
return my_hash
|
192
|
+
end
|
193
|
+
|
194
|
+
# return a list of descendant classes in the current context. This is provided to help
|
195
|
+
# find classes for the jeckyl utility, e.g. to generate a default config file
|
196
|
+
#
|
197
|
+
# @return [Array] classes that are descendants of this class, sorted with the least ancestral
|
198
|
+
# first
|
199
|
+
#
|
200
|
+
def self.descendants
|
201
|
+
descs = Array.new
|
202
|
+
ObjectSpace.each_object {|obj| descs << obj if obj.kind_of?(Class) && obj < self}
|
203
|
+
descs.sort! {|a,b| a < b ? -1 : 1}
|
204
|
+
return descs
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# set the prefix to the parameter names that should be used for corresponding
|
209
|
+
# parameter methods defined for a subclass. Parameter names in config files
|
210
|
+
# are mapped onto parameter method by prefixing the methods with the results of
|
211
|
+
# this function. So, for a parameter named 'greeting', the parameter method used
|
212
|
+
# to check the parameter will be, by default, 'configure_greeting'.
|
213
|
+
#
|
214
|
+
# For example, to define parameter methods prefix with 'set' redefine this
|
215
|
+
# method to return 'set'. The greeting parameter method should then be called
|
216
|
+
# 'set_greeting'
|
217
|
+
#
|
218
|
+
def prefix
|
219
|
+
'configure'
|
220
|
+
end
|
221
|
+
|
222
|
+
# Delete those parameters that are in the given hash from this instance of Jeckyl::Config.
|
223
|
+
# Useful for tailoring parameter sets to specific uses (e.g. removing logging parameters)
|
224
|
+
#
|
225
|
+
# @param [Hash] conf_to_remove which is a hash or an instance of Jeckyl::Config
|
226
|
+
#
|
227
|
+
def complement(conf_to_remove)
|
228
|
+
self.delete_if {|key, value| conf_to_remove.has_key?(key)}
|
229
|
+
end
|
230
|
+
|
231
|
+
# Read, check and merge another parameter file into this one, being of the same config class.
|
232
|
+
#
|
233
|
+
# @param [String] conf_file - path to file to parse
|
234
|
+
#
|
235
|
+
def merge(conf_file)
|
236
|
+
|
237
|
+
self[:config_files] << conf_file
|
238
|
+
|
239
|
+
# and finally get the values from the config file itself
|
240
|
+
self.instance_eval(File.read(conf_file), conf_file)
|
241
|
+
|
242
|
+
rescue SyntaxError => err
|
243
|
+
raise ConfigSyntaxError, err.message
|
244
|
+
rescue Errno::ENOENT
|
245
|
+
# duff file path so tell the caller
|
246
|
+
raise ConfigFileMissing, "#{conf_file}"
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
protected
|
251
|
+
|
252
|
+
# create a description for the current parameter, to be used when generating a config template
|
253
|
+
#
|
254
|
+
# @param [*String] being one or more string arguments that are used to generate config file templates
|
255
|
+
# and documents
|
256
|
+
def comment(*strings)
|
257
|
+
@_comments[@_last_symbol] = strings unless @_last_symbol.nil?
|
258
|
+
end
|
259
|
+
|
260
|
+
# set default value(s) for the current parameter.
|
261
|
+
#
|
262
|
+
# @param [Object] val - any valid object as expected by the parameter method
|
263
|
+
def default(val)
|
264
|
+
return if @_last_symbol.nil? || @_defaults.has_key?(@_last_symbol)
|
265
|
+
@_defaults[@_last_symbol] = val
|
266
|
+
end
|
267
|
+
|
268
|
+
# the following are all helper methods to parse values and raise exceptions if the values are not correct
|
269
|
+
|
270
|
+
# file helpers - meanings should be apparent
|
271
|
+
|
272
|
+
# check that the parameter is a directory and that the directory is writable
|
273
|
+
#
|
274
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
275
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
276
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
277
|
+
# the parameter is not validly formed
|
278
|
+
#
|
279
|
+
# @param [String] - path
|
280
|
+
#
|
281
|
+
def a_writable_dir(path)
|
282
|
+
if FileTest.directory?(path) && FileTest.writable?(path) then
|
283
|
+
path
|
284
|
+
else
|
285
|
+
raise_config_error(path, "directory is not writable or does not exist")
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# check parameter is a readable file
|
290
|
+
#
|
291
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
292
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
293
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
294
|
+
# the parameter is not validly formed
|
295
|
+
#
|
296
|
+
# @param [String] - path to file
|
297
|
+
#
|
298
|
+
def a_readable_file(path)
|
299
|
+
if FileTest.readable?(path) then
|
300
|
+
path
|
301
|
+
else
|
302
|
+
raise_config_error(path, "file does not exist")
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# simple type helpers
|
307
|
+
|
308
|
+
# check the parameter is of the required type
|
309
|
+
#
|
310
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
311
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
312
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
313
|
+
# the parameter is not validly formed
|
314
|
+
#
|
315
|
+
# @param [Object] obj to check type of
|
316
|
+
# @param [Class] type, being a class constant such as Numeric, String
|
317
|
+
#
|
318
|
+
def a_type_of(obj, type)
|
319
|
+
if obj.kind_of?(type) then
|
320
|
+
obj
|
321
|
+
else
|
322
|
+
raise_config_error(obj, "value is not of required type: #{type}")
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# check that the parameter is within the required range
|
327
|
+
#
|
328
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
329
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
330
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
331
|
+
# the parameter is not validly formed
|
332
|
+
#
|
333
|
+
# @param [Numeric] val to check
|
334
|
+
# @param [Numeric] lower bound of range
|
335
|
+
# @param [Numeric] upper bound of range
|
336
|
+
#
|
337
|
+
def in_range(val, lower, upper)
|
338
|
+
raise_syntax_error("#{lower.to_s}..#{upper.to_s} is not a range") unless (lower .. upper).kind_of?(Range)
|
339
|
+
if (lower .. upper) === val then
|
340
|
+
val
|
341
|
+
else
|
342
|
+
raise_config_error(val, "value is not within required range: #{lower.to_s}..#{upper.to_s}")
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
# boolean helpers
|
348
|
+
|
349
|
+
# check parameter is a boolean, true or false but not strings "true" or "false"
|
350
|
+
#
|
351
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
352
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
353
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
354
|
+
# the parameter is not validly formed
|
355
|
+
#
|
356
|
+
# @param [Boolean] val to check
|
357
|
+
#
|
358
|
+
def a_boolean(val)
|
359
|
+
if val.kind_of?(TrueClass) || val.kind_of?(FalseClass) then
|
360
|
+
val
|
361
|
+
else
|
362
|
+
raise_config_error(val, "Value is not a Boolean")
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# check the parameter is a flag, being "true", "false", "yes", "no", "on", "off", or 1 , 0
|
367
|
+
# and return a proper boolean
|
368
|
+
#
|
369
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
370
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
371
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
372
|
+
# the parameter is not validly formed
|
373
|
+
#
|
374
|
+
# @param [String] val to check
|
375
|
+
#
|
376
|
+
def a_flag(val)
|
377
|
+
val = val.downcase if val.kind_of?(String)
|
378
|
+
case val
|
379
|
+
when "true", "yes", "on", 1
|
380
|
+
true
|
381
|
+
when "false", "no", "off", 0
|
382
|
+
false
|
383
|
+
else
|
384
|
+
raise_config_error(val, "Cannot convert to Boolean")
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
|
389
|
+
# compound objects
|
390
|
+
|
391
|
+
# check the parameter is an array
|
392
|
+
#
|
393
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
394
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
395
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
396
|
+
# the parameter is not validly formed
|
397
|
+
#
|
398
|
+
# @param [Array] ary to check
|
399
|
+
#
|
400
|
+
def an_array(ary)
|
401
|
+
if ary.kind_of?(Array) then
|
402
|
+
ary
|
403
|
+
else
|
404
|
+
raise_config_error(ary, "value is not an Array")
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# check the parameter is an array and the array is of the required type
|
409
|
+
#
|
410
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
411
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
412
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
413
|
+
# the parameter is not validly formed
|
414
|
+
#
|
415
|
+
# @param [Array] ary of values to check
|
416
|
+
# @param [Class] type being the class that the values must belong to
|
417
|
+
#
|
418
|
+
def an_array_of(ary, type)
|
419
|
+
raise_syntax_error("Provided a value that is a type: #{type.to_s}") unless type.class == Class
|
420
|
+
if ary.kind_of?(Array) then
|
421
|
+
ary.each do |element|
|
422
|
+
unless element.kind_of?(type) then
|
423
|
+
raise_config_error(element, "element of array is not of type: #{type}")
|
424
|
+
end
|
425
|
+
end
|
426
|
+
return ary
|
427
|
+
else
|
428
|
+
raise_config_error(ary, "value is not an Array")
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# check the parameter is a hash
|
433
|
+
#
|
434
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
435
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
436
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
437
|
+
# the parameter is not validly formed
|
438
|
+
#
|
439
|
+
# @param [Hash] hsh to check
|
440
|
+
#
|
441
|
+
def a_hash(hsh)
|
442
|
+
if hsh.kind_of?(Hash) then
|
443
|
+
true
|
444
|
+
else
|
445
|
+
raise_config_error(hsh, "value is not a Hash")
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
# strings and text and stuff
|
450
|
+
|
451
|
+
# check the parameter is a string
|
452
|
+
#
|
453
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
454
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
455
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
456
|
+
# the parameter is not validly formed
|
457
|
+
#
|
458
|
+
# @param [String] str to check
|
459
|
+
#
|
460
|
+
def a_string(str)
|
461
|
+
if str.kind_of?(String) then
|
462
|
+
str
|
463
|
+
else
|
464
|
+
raise_config_error(str.to_s, "is not a String")
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
# check the parameter is a string and matches the required pattern
|
469
|
+
#
|
470
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
471
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
472
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
473
|
+
# the parameter is not validly formed
|
474
|
+
#
|
475
|
+
# @param [String] str to match against the pattern
|
476
|
+
# @param [Regexp] pattern to match with
|
477
|
+
#
|
478
|
+
def a_matching_string(str, pattern)
|
479
|
+
raise_syntax_error("Attempt to pattern match without a Regexp") unless pattern.kind_of?(Regexp)
|
480
|
+
if pattern =~ a_string(str) then
|
481
|
+
str
|
482
|
+
else
|
483
|
+
raise_config_error(str, "does not match required pattern: #{pattern.source}")
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
# set membership - set is an array of members, usually symbols
|
488
|
+
#
|
489
|
+
# Jeckyl checking method to be used in parameter methods to check the validity of
|
490
|
+
# given parameters, returning the parameter if valid or else raising an exception
|
491
|
+
# which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
|
492
|
+
# the parameter is not validly formed
|
493
|
+
#
|
494
|
+
# @param [Symbol] symb being the symbol to check
|
495
|
+
# @param [Array] set containing the valid symbols that symb should belong to
|
496
|
+
#
|
497
|
+
def a_member_of(symb, set)
|
498
|
+
raise_syntax_error("Sets to test membership must be arrays") unless set.kind_of?(Array)
|
499
|
+
if set.include?(symb) then
|
500
|
+
symb
|
501
|
+
else
|
502
|
+
raise_config_error(symb, "is not a member of: #{set.join(', ')}")
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
private
|
508
|
+
|
509
|
+
# decides what to do with parameters that have not been defined.
|
510
|
+
# unless @_relax then it will raise an exception. Otherwise it will create a key value pair
|
511
|
+
#
|
512
|
+
# This method also remembers the method name as the key to prevent the parsers etc from
|
513
|
+
# having to carry this around just to do things like report on it.
|
514
|
+
#
|
515
|
+
def method_missing(symb, parameter)
|
516
|
+
|
517
|
+
@_last_symbol = symb
|
518
|
+
#@parameter = parameter
|
519
|
+
method_to_call = ("#{self.prefix}_" + symb.to_s).to_sym
|
520
|
+
set_method = self.method(method_to_call)
|
521
|
+
|
522
|
+
self[@_last_symbol] = set_method.call(parameter)
|
523
|
+
|
524
|
+
rescue NameError
|
525
|
+
#raise if @@debug
|
526
|
+
# no parser method defined.
|
527
|
+
unless @_relax then
|
528
|
+
# not tolerable
|
529
|
+
raise UnknownParameter, format_error(symb, parameter, "Unknown parameter")
|
530
|
+
else
|
531
|
+
# feeling relaxed, so lets store it anyway.
|
532
|
+
self[symb] = parameter
|
533
|
+
end
|
534
|
+
|
535
|
+
end
|
536
|
+
|
537
|
+
# get_defaults
|
538
|
+
#
|
539
|
+
# calls each method with the specified prefix with no parameters so that the defaults
|
540
|
+
# defined for each will be passed back and used to set the hash before the
|
541
|
+
# config file is evaluated.
|
542
|
+
#
|
543
|
+
def get_defaults(opts={})
|
544
|
+
flag_errors = opts[:flag_errors]
|
545
|
+
local = opts[:local]
|
546
|
+
|
547
|
+
# go through all of the methods
|
548
|
+
self.class.instance_methods(!local).each do |method_name|
|
549
|
+
if md = /^#{self.prefix}_/.match(method_name) then
|
550
|
+
|
551
|
+
# its a prefixed method so call it
|
552
|
+
|
553
|
+
pref_method = self.method(method_name.to_sym)
|
554
|
+
# get the corresponding symbol for the hash
|
555
|
+
@_last_symbol = md.post_match.to_sym
|
556
|
+
@_order << @_last_symbol
|
557
|
+
# and call the method with no parameters, which will
|
558
|
+
# call the comment method and the default method where defined
|
559
|
+
# and thereby capture their values
|
560
|
+
begin
|
561
|
+
a_value = pref_method.call(1)
|
562
|
+
rescue Exception
|
563
|
+
# ignore any errors, which are bound to result from passing in 1
|
564
|
+
end
|
565
|
+
begin
|
566
|
+
# now set the actual default by calling the method again and passing
|
567
|
+
# the captured default, providing a result which may be different if the method transforms
|
568
|
+
# the parameter!
|
569
|
+
param = @_defaults[@_last_symbol]
|
570
|
+
self[@_last_symbol] = pref_method.call(param) unless param.nil?
|
571
|
+
rescue Exception
|
572
|
+
raise if flag_errors
|
573
|
+
# ignore any errors raised
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
# really private helpers that should not be needed unless the parser method
|
580
|
+
# is custom
|
581
|
+
|
582
|
+
protected
|
583
|
+
|
584
|
+
# helper method to format exception messages. A config error should be raised
|
585
|
+
# when the given parameter does not match the checks.
|
586
|
+
#
|
587
|
+
# The exception is raised in the caller's context to ensure backtraces are accurate.
|
588
|
+
#
|
589
|
+
# @param [Object] value - the object that caused the error
|
590
|
+
# @param [String] message to include in the exception
|
591
|
+
#
|
592
|
+
def raise_config_error(value, message)
|
593
|
+
raise ConfigError, format_error(@_last_symbol, value, message), caller
|
594
|
+
end
|
595
|
+
|
596
|
+
# helper method to format exception messages. A syntax error should be raised
|
597
|
+
# when the check method has been used incorrectly. See check methods for examples.
|
598
|
+
#
|
599
|
+
# The exception is raised in the caller's context to ensure backtraces are accurate.
|
600
|
+
#
|
601
|
+
# @param [String] message to include in the exception
|
602
|
+
#
|
603
|
+
def raise_syntax_error(message)
|
604
|
+
raise ConfigSyntaxError, message, caller
|
605
|
+
end
|
606
|
+
|
607
|
+
# helper method to format an error
|
608
|
+
def format_error(key, value, message)
|
609
|
+
"[#{key}]: #{value} - #{message}"
|
610
|
+
end
|
611
|
+
|
612
|
+
end
|
613
|
+
|
614
|
+
# define an alias for backwards compatitbility
|
615
|
+
# @deprecated Please use Jeckyl::Config
|
616
|
+
Options = Config
|
617
|
+
|
618
|
+
end
|
619
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'jeckyl/errors'
|
3
|
+
require 'jeckyl'
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test/test_configurator')
|
5
|
+
|
6
|
+
conf_path = File.expand_path(File.dirname(__FILE__) + '/../test/conf.d')
|
7
|
+
|
8
|
+
report_path = File.expand_path(File.dirname(__FILE__) + '/../test/reports')
|
9
|
+
|
10
|
+
describe "Jeckyl Config Checker" do
|
11
|
+
|
12
|
+
# general tests
|
13
|
+
|
14
|
+
it "should checkout a simple config" do
|
15
|
+
conf_file = conf_path + '/jeckyl'
|
16
|
+
rep_file = report_path + '/ok.txt'
|
17
|
+
conf_ok = TestJeckyl.check_config(conf_file, rep_file)
|
18
|
+
conf_ok.should be_true
|
19
|
+
message = File.read(rep_file).chomp
|
20
|
+
message.should == "No errors found in: #{conf_file}"
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
it "should complain if the config file does not exist" do
|
25
|
+
conf_file = conf_path + "/never/likely/to/be/there"
|
26
|
+
rep_file = report_path + '/not_ok.txt'
|
27
|
+
conf_ok = TestJeckyl.check_config(conf_file, rep_file)
|
28
|
+
conf_ok.should be_false
|
29
|
+
message = File.read(rep_file).chomp
|
30
|
+
message.should == "No such config file: #{conf_file}"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should return false if the config file has a syntax error" do
|
34
|
+
conf_file = conf_path + "/syntax_error"
|
35
|
+
rep_file = report_path + '/not_ok.txt'
|
36
|
+
conf_ok = TestJeckyl.check_config(conf_file, rep_file)
|
37
|
+
conf_ok.should be_false
|
38
|
+
message = File.read(rep_file).chomp
|
39
|
+
message.should match(/^compile error/)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return false if the config file has an error" do
|
43
|
+
conf_file = conf_path + "/not_a_bool"
|
44
|
+
rep_file = report_path + '/not_ok.txt'
|
45
|
+
conf_ok = TestJeckyl.check_config(conf_file, rep_file)
|
46
|
+
conf_ok.should be_false
|
47
|
+
message = File.read(rep_file).chomp
|
48
|
+
message.should match(/^\[debug\]:/)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should fail if it cannot write to the report file" do
|
52
|
+
conf_file = conf_path + "/jeckyl"
|
53
|
+
rep_file = report_path + '/no_such_directory/ok.txt'
|
54
|
+
lambda{conf_ok = TestJeckyl.check_config(conf_file, rep_file)}.should raise_error(Jeckyl::ReportFileError)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|