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/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