jeckyl 0.2.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/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
|