jeckyl 0.2.7 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
data/lib/jeckyl.rb CHANGED
@@ -11,8 +11,10 @@
11
11
  #
12
12
  # == JECKYL
13
13
  #
14
+ require 'optparse'
14
15
  require 'jeckyl/version'
15
16
  require 'jeckyl/errors'
17
+ require 'jeckyl/helpers'
16
18
 
17
19
  #
18
20
  # The Jeckyl configurator module, which is just a wrapper. See {file:README.md Readme} for details.
@@ -37,6 +39,7 @@ module Jeckyl
37
39
  #
38
40
  # More details are available in the {file:README.md Readme} file
39
41
  class Config < Hash
42
+
40
43
 
41
44
  # create a configuration hash by evaluating the parameters defined in the given config file.
42
45
  #
@@ -66,12 +69,18 @@ module Jeckyl
66
69
  # hash for comments accessed with the same symbol
67
70
  @_comments = {}
68
71
  # hash for input defaults
69
- @_defaults={}
72
+ @_defaults = {}
73
+ # hash for optparse options
74
+ @_options = {}
75
+ # hash for short descriptions
76
+ @_descriptions = {}
70
77
  # save order in which methods are defined for generating config files
71
78
  @_order = Array.new
72
79
 
73
80
  # get the defaults defined in the config parser
74
81
  get_defaults(:local=> local, :flag_errors => flag_errors_on_defaults)
82
+
83
+ self[:config_files] = Array.new
75
84
 
76
85
  return self if config_file.nil?
77
86
 
@@ -90,20 +99,30 @@ module Jeckyl
90
99
 
91
100
  # gives access to a hash containing an entry for each parameter and the comments
92
101
  # defined by the class definitions - used internally by class methods
93
- def comments
102
+ def _comments
94
103
  @_comments
95
104
  end
96
105
 
97
106
  # This contains an array of the parameter names - used internally by class methods
98
- def order
107
+ def _order
99
108
  @_order
100
109
  end
101
110
 
102
111
  # this contains a hash of the defaults for each parameter - used internally by class methods
103
- def defaults
112
+ def _defaults
104
113
  @_defaults
105
114
  end
106
115
 
116
+ # return hash of options - used internally to generate files etc
117
+ def _options
118
+ @_options
119
+ end
120
+
121
+ # return has of descriptions
122
+ def _descriptions
123
+ @_descriptions
124
+ end
125
+
107
126
  # a class method to check a given config file one item at a time
108
127
  #
109
128
  # This evaluates the given config file and reports if there are any errors to the
@@ -165,14 +184,26 @@ module Jeckyl
165
184
  def self.generate_config(local=false)
166
185
  me = self.new(nil, :local => local)
167
186
  # everything should now exist
168
- me.order.each do |key|
187
+ me._order.each do |key|
188
+
189
+ if me._descriptions.has_key?(key) then
190
+ puts "# #{me._descriptions[key]}"
191
+ puts "#"
192
+ end
169
193
 
170
- if me.comments.has_key?(key) then
171
- me.comments[key].each do |comment|
194
+ if me._comments.has_key?(key) then
195
+ me._comments[key].each do |comment|
172
196
  puts "# #{comment}"
173
197
  end
174
198
  end
175
- def_value = me.defaults[key]
199
+ # output an option description if needed
200
+ if me._options.has_key?(key) then
201
+ puts "#"
202
+ puts "# Optparse options for this parameter:"
203
+ puts "# #{me._options[key].join(", ")}"
204
+ puts "#"
205
+ end
206
+ def_value = me._defaults[key]
176
207
  default = def_value.nil? ? '' : def_value.inspect
177
208
 
178
209
  puts "##{key.to_s} #{default}"
@@ -191,7 +222,7 @@ module Jeckyl
191
222
  def self.intersection(full_config)
192
223
  me = self.new # create the defaults for this class
193
224
  my_hash = {}
194
- me.order.each do |my_key|
225
+ me._order.each do |my_key|
195
226
  if full_config.has_key?(my_key) then
196
227
  my_hash[my_key] = full_config[my_key]
197
228
  end
@@ -212,6 +243,35 @@ module Jeckyl
212
243
  return descs
213
244
  end
214
245
 
246
+ # get a config file option from the given command line args
247
+ #
248
+ # This is needed with the optparse methods for obvious reasons - the options
249
+ # can only be parsed once and you may want to parse them with a config file specified
250
+ # on the command line. This does it the old-fashioned way and strips the option
251
+ # from the command line arguments.
252
+ #
253
+ # Note that the optparse method also includes this option but just for the benefit of --help
254
+ #
255
+ # @param [Array] args which should usually be set to ARGV
256
+ # @param [String] c_file being the path to the config file, which will be
257
+ # updated with the command line option if specified.
258
+ #
259
+ def self.get_config_opt(args, c_file)
260
+ #c_file = nil
261
+ if arg_index = args.index('-c') then
262
+ # got a -c option so expect a file next
263
+ c_file = args[arg_index + 1]
264
+
265
+ # check the file exists
266
+ if c_file && FileTest.readable?(c_file) then
267
+ # it does so strip the args out
268
+ args.slice!(arg_index, 2)
269
+
270
+ end
271
+ end
272
+ return [args, c_file]
273
+ end
274
+
215
275
 
216
276
  # set the prefix to the parameter names that should be used for corresponding
217
277
  # parameter methods defined for a subclass. Parameter names in config files
@@ -238,15 +298,23 @@ module Jeckyl
238
298
 
239
299
  # Read, check and merge another parameter file into this one, being of the same config class.
240
300
  #
301
+ # If the file does not exist then silently ignore the merge
302
+ #
241
303
  # @param [String] conf_file - path to file to parse
242
304
  #
243
305
  def merge(conf_file)
244
306
 
245
- self[:config_files] << conf_file
246
-
247
- # get the values from the config file itself
248
- self.instance_eval(File.read(conf_file), conf_file)
249
-
307
+ if conf_file.kind_of?(Hash) then
308
+ self.merge!(conf_file)
309
+ else
310
+
311
+ return unless FileTest.exists?(conf_file)
312
+
313
+ self[:config_files] << conf_file
314
+
315
+ # get the values from the config file itself
316
+ self.instance_eval(File.read(conf_file), conf_file)
317
+ end
250
318
  rescue SyntaxError => err
251
319
  raise ConfigSyntaxError, err.message
252
320
  rescue Errno::ENOENT
@@ -254,12 +322,88 @@ module Jeckyl
254
322
  raise ConfigFileMissing, "#{conf_file}"
255
323
  end
256
324
 
325
+ # parse the given command line using the defined options
326
+ #
327
+ # @param [Array] args which should usually be ARGV
328
+ # @yield self and optparse object to allow incidental options to be added
329
+ # @return false if --help so that the caller can decide what to do (e.g. exit)
330
+ def optparse(args)
331
+
332
+ # ensure calls to parameter methods do not trample on things
333
+ @_last_symbol = nil
334
+
335
+ opts = OptionParser.new
336
+ # get the prefix for parameter methods (once)
337
+ prefix = self.prefix
338
+
339
+ opts.on('-c', '--config-file [FILENAME]', String, 'specify an alternative config file')
340
+
341
+ # need to define usage etc
342
+
343
+ # loop through each of the options saved
344
+ @_options.each_pair do |param, options|
345
+
346
+ options << @_descriptions[param] if @_descriptions.has_key?(param)
347
+
348
+ # opt_str = ''
349
+ # options.each do |os|
350
+ # opt_str << os.inspect
351
+ # end
352
+
353
+ # puts "#{param}: #{opt_str}"
354
+
355
+ # get the method itself to call with the given arg
356
+ pref_method = self.method("#{prefix}_#{param}".to_sym)
357
+
358
+ # now process the option
359
+ opts.on(*options) do |val|
360
+ # and save the results having passed it through the parameter method
361
+ self[param] = pref_method.call(val)
362
+
363
+ end
364
+ end
365
+
366
+ # allow non-jeckyl options to be added (without the checks!)
367
+ if block_given? then
368
+ # pass out self to allow parameters to be saved and the opts object
369
+ yield(self, opts)
370
+ end
371
+
372
+ # add in a little bit of help
373
+ opts.on_tail('-h', '--help', 'you are looking at it') do
374
+ puts opts
375
+ return false
376
+ end
377
+
378
+ opts.parse!(args)
379
+
380
+ return true
381
+
382
+ end
383
+
384
+ # output the hash as a formatted set
385
+ def to_s(opts={})
386
+ keys = self.keys.collect {|k| k.to_s}
387
+ cols = 0
388
+ keys.each {|k| cols = k.length if k.length > cols}
389
+ keys.sort.each do |key_s|
390
+ print ' '
391
+ print key_s.ljust(cols)
392
+ key = key_s.to_sym
393
+ desc = @_descriptions[key]
394
+ value = self[key].inspect
395
+ print ": #{value}"
396
+ print " (#{desc})" unless desc.nil?
397
+ puts
398
+ end
399
+ end
400
+
257
401
 
258
402
  protected
259
403
 
260
404
  # create a description for the current parameter, to be used when generating a config template
261
405
  #
262
- # @param [*String] being one or more string arguments that are used to generate config file templates
406
+ # @param [*String] strings being one or more string arguments that are used to generate config file templates
263
407
  # and documents
264
408
  def comment(*strings)
265
409
  @_comments[@_last_symbol] = strings unless @_last_symbol.nil?
@@ -273,244 +417,22 @@ module Jeckyl
273
417
  @_defaults[@_last_symbol] = val
274
418
  end
275
419
 
276
- # the following are all helper methods to parse values and raise exceptions if the values are not correct
277
-
278
- # file helpers - meanings should be apparent
279
-
280
- # check that the parameter is a directory and that the directory is writable
281
- #
282
- # Jeckyl checking method to be used in parameter methods to check the validity of
283
- # given parameters, returning the parameter if valid or else raising an exception
284
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
285
- # the parameter is not validly formed
286
- #
287
- # @param [String] - path
288
- #
289
- def a_writable_dir(path)
290
- if FileTest.directory?(path) && FileTest.writable?(path) then
291
- path
292
- else
293
- raise_config_error(path, "directory is not writable or does not exist")
294
- end
295
- end
296
-
297
- # check parameter is a readable file
420
+ # set optparse options for the parameter
298
421
  #
299
- # Jeckyl checking method to be used in parameter methods to check the validity of
300
- # given parameters, returning the parameter if valid or else raising an exception
301
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
302
- # the parameter is not validly formed
303
- #
304
- # @param [String] - path to file
305
- #
306
- def a_readable_file(path)
307
- if FileTest.readable?(path) then
308
- path
309
- else
310
- raise_config_error(path, "file does not exist")
311
- end
312
- end
313
-
314
- # simple type helpers
315
-
316
- # check the parameter is of the required type
317
- #
318
- # Jeckyl checking method to be used in parameter methods to check the validity of
319
- # given parameters, returning the parameter if valid or else raising an exception
320
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
321
- # the parameter is not validly formed
322
- #
323
- # @param [Object] obj to check type of
324
- # @param [Class] type, being a class constant such as Numeric, String
325
- #
326
- def a_type_of(obj, type)
327
- if obj.kind_of?(type) then
328
- obj
329
- else
330
- raise_config_error(obj, "value is not of required type: #{type}")
331
- end
422
+ # @param [Array] opts - options using the same format as optparse
423
+ def option(*opts)
424
+ @_options[@_last_symbol] = opts unless @_last_symbol.nil?
332
425
  end
333
426
 
334
- # check that the parameter is within the required range
427
+ # set optparse description for the parameter
335
428
  #
336
- # Jeckyl checking method to be used in parameter methods to check the validity of
337
- # given parameters, returning the parameter if valid or else raising an exception
338
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
339
- # the parameter is not validly formed
340
- #
341
- # @param [Numeric] val to check
342
- # @param [Numeric] lower bound of range
343
- # @param [Numeric] upper bound of range
344
- #
345
- def in_range(val, lower, upper)
346
- raise_syntax_error("#{lower.to_s}..#{upper.to_s} is not a range") unless (lower .. upper).kind_of?(Range)
347
- if (lower .. upper) === val then
348
- val
349
- else
350
- raise_config_error(val, "value is not within required range: #{lower.to_s}..#{upper.to_s}")
351
- end
352
- end
353
-
354
-
355
- # boolean helpers
356
-
357
- # check parameter is a boolean, true or false but not strings "true" or "false"
358
- #
359
- # Jeckyl checking method to be used in parameter methods to check the validity of
360
- # given parameters, returning the parameter if valid or else raising an exception
361
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
362
- # the parameter is not validly formed
363
- #
364
- # @param [Boolean] val to check
365
- #
366
- def a_boolean(val)
367
- if val.kind_of?(TrueClass) || val.kind_of?(FalseClass) then
368
- val
369
- else
370
- raise_config_error(val, "Value is not a Boolean")
371
- end
372
- end
373
-
374
- # check the parameter is a flag, being "true", "false", "yes", "no", "on", "off", or 1 , 0
375
- # and return a proper boolean
376
- #
377
- # Jeckyl checking method to be used in parameter methods to check the validity of
378
- # given parameters, returning the parameter if valid or else raising an exception
379
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
380
- # the parameter is not validly formed
381
- #
382
- # @param [String] val to check
383
- #
384
- def a_flag(val)
385
- val = val.downcase if val.kind_of?(String)
386
- case val
387
- when "true", "yes", "on", 1
388
- true
389
- when "false", "no", "off", 0
390
- false
391
- else
392
- raise_config_error(val, "Cannot convert to Boolean")
393
- end
394
- end
395
-
396
-
397
- # compound objects
398
-
399
- # check the parameter is an array
400
- #
401
- # Jeckyl checking method to be used in parameter methods to check the validity of
402
- # given parameters, returning the parameter if valid or else raising an exception
403
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
404
- # the parameter is not validly formed
405
- #
406
- # @param [Array] ary to check
407
- #
408
- def an_array(ary)
409
- if ary.kind_of?(Array) then
410
- ary
411
- else
412
- raise_config_error(ary, "value is not an Array")
413
- end
414
- end
415
-
416
- # check the parameter is an array and the array is of the required type
417
- #
418
- # Jeckyl checking method to be used in parameter methods to check the validity of
419
- # given parameters, returning the parameter if valid or else raising an exception
420
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
421
- # the parameter is not validly formed
422
- #
423
- # @param [Array] ary of values to check
424
- # @param [Class] type being the class that the values must belong to
425
- #
426
- def an_array_of(ary, type)
427
- raise_syntax_error("Provided a value that is a type: #{type.to_s}") unless type.class == Class
428
- if ary.kind_of?(Array) then
429
- ary.each do |element|
430
- unless element.kind_of?(type) then
431
- raise_config_error(element, "element of array is not of type: #{type}")
432
- end
433
- end
434
- return ary
435
- else
436
- raise_config_error(ary, "value is not an Array")
437
- end
438
- end
439
-
440
- # check the parameter is a hash
441
- #
442
- # Jeckyl checking method to be used in parameter methods to check the validity of
443
- # given parameters, returning the parameter if valid or else raising an exception
444
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
445
- # the parameter is not validly formed
446
- #
447
- # @param [Hash] hsh to check
448
- #
449
- def a_hash(hsh)
450
- if hsh.kind_of?(Hash) then
451
- true
452
- else
453
- raise_config_error(hsh, "value is not a Hash")
454
- end
455
- end
456
-
457
- # strings and text and stuff
458
-
459
- # check the parameter is a string
460
- #
461
- # Jeckyl checking method to be used in parameter methods to check the validity of
462
- # given parameters, returning the parameter if valid or else raising an exception
463
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
464
- # the parameter is not validly formed
465
- #
466
- # @param [String] str to check
467
- #
468
- def a_string(str)
469
- if str.kind_of?(String) then
470
- str
471
- else
472
- raise_config_error(str.to_s, "is not a String")
473
- end
474
- end
475
-
476
- # check the parameter is a string and matches the required pattern
477
- #
478
- # Jeckyl checking method to be used in parameter methods to check the validity of
479
- # given parameters, returning the parameter if valid or else raising an exception
480
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
481
- # the parameter is not validly formed
482
- #
483
- # @param [String] str to match against the pattern
484
- # @param [Regexp] pattern to match with
485
- #
486
- def a_matching_string(str, pattern)
487
- raise_syntax_error("Attempt to pattern match without a Regexp") unless pattern.kind_of?(Regexp)
488
- if pattern =~ a_string(str) then
489
- str
490
- else
491
- raise_config_error(str, "does not match required pattern: #{pattern.source}")
492
- end
493
- end
494
-
495
- # set membership - set is an array of members, usually symbols
496
- #
497
- # Jeckyl checking method to be used in parameter methods to check the validity of
498
- # given parameters, returning the parameter if valid or else raising an exception
499
- # which is either ConfigError if the parameter fails the check or ConfigSyntaxError if
500
- # the parameter is not validly formed
501
- #
502
- # @param [Symbol] symb being the symbol to check
503
- # @param [Array] set containing the valid symbols that symb should belong to
504
- #
505
- def a_member_of(symb, set)
506
- raise_syntax_error("Sets to test membership must be arrays") unless set.kind_of?(Array)
507
- if set.include?(symb) then
508
- symb
509
- else
510
- raise_config_error(symb, "is not a member of: #{set.join(', ')}")
511
- end
429
+ # @param [Array] str - options using the same format as optparse
430
+ def describe(str)
431
+ @_descriptions[@_last_symbol] = str unless @_last_symbol.nil?
512
432
  end
513
433
 
434
+ # add in all the parameter checking helper methods
435
+ include Jeckyl::Helpers
514
436
 
515
437
  private
516
438
 
@@ -556,15 +478,20 @@ module Jeckyl
556
478
  self.class.instance_methods(!local).each do |method_name|
557
479
  if md = /^#{self.prefix}_/.match(method_name) then
558
480
 
559
- # its a prefixed method so call it
481
+ # its a prefixed method so get it
560
482
 
561
483
  pref_method = self.method(method_name.to_sym)
562
484
  # get the corresponding symbol for the hash
563
485
  @_last_symbol = md.post_match.to_sym
564
486
  @_order << @_last_symbol
565
- # and call the method with no parameters, which will
566
- # call the comment method and the default method where defined
567
- # and thereby capture their values
487
+ # and call the method with any parameter, which will
488
+ # call the default method where defined and capture its value
489
+ # Note that a default is defined in the same terms as the input
490
+ # to its parameter method, and may therefore need to be processed
491
+ # by the method to get the desired value. For example, if a parameter
492
+ # method expects data expressed in MBytes, but passes on bytes then
493
+ # the method has to be called with the default to get the real or final
494
+ # default. This is done in the block after this one:
568
495
  begin
569
496
  a_value = pref_method.call(1)
570
497
  rescue Exception
data/spec/jeckyl_spec.rb CHANGED
@@ -7,6 +7,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../test/test_subclass')
7
7
 
8
8
  conf_path = File.expand_path(File.dirname(__FILE__) + '/../test/conf.d')
9
9
 
10
+
10
11
  describe "Jeckyl" do
11
12
 
12
13
  # general tests
@@ -30,6 +31,9 @@ describe "Jeckyl" do
30
31
  conf[:email].should == "robert@osburn-sharp.ath.cx"
31
32
  conf.has_key?(:sieve).should be_true
32
33
  conf[:config_files].length.should == 1
34
+ conf[:option_set][:peter].should == 37
35
+ conf[:offset].should == 134
36
+ conf[:start_day].should == 6
33
37
  end
34
38
 
35
39
 
@@ -89,6 +93,11 @@ describe "Jeckyl" do
89
93
  lambda{conf = TestJeckyl.new(conf_file)}.should raise_error(Jeckyl::ConfigError, /^\[pi\]:.*value is not of required type: Float$/)
90
94
  end
91
95
 
96
+ it "should raise an exception if it gets a negative instead of a positive" do
97
+ conf_file = conf_path + '/not_positive.rb'
98
+ lambda{conf = TestJeckyl.new(conf_file)}.should raise_error(Jeckyl::ConfigError, /^\[offset\]:.*value is not a positive number: \-\d+$/)
99
+ end
100
+
92
101
  end
93
102
 
94
103
  describe "Boolean Parameters" do
@@ -114,7 +123,7 @@ describe "Jeckyl" do
114
123
 
115
124
  it "should raise an exception if it gets a string instead of a hash" do
116
125
  conf_file = conf_path + '/not_a_hash'
117
- lambda{conf = TestJeckyl.new(conf_file)}.should raise_error(Jeckyl::ConfigError, /^\[options\]:.*value is not a Hash$/)
126
+ lambda{conf = TestJeckyl.new(conf_file)}.should raise_error(Jeckyl::ConfigError, /^\[option_set\]:.*value is not a Hash$/)
118
127
  end
119
128
 
120
129
  it "should raise an exception if it gets an array of strings instead of a integers" do
@@ -197,6 +206,21 @@ describe "Jeckyl" do
197
206
  conf[:config_files].length.should == 2
198
207
  conf[:pi].should == 3.14592
199
208
  end
209
+
210
+ it "should merge another hash" do
211
+ conf_file = conf_path + '/jeckyl'
212
+ conf = TestJeckyl.new(conf_file)
213
+ merge_file = File.join(conf_path, 'merger.rb')
214
+ merge = TestJeckyl.new(merge_file)
215
+ conf.merge(merge)
216
+ conf[:log_dir].should match(/reports$/)
217
+ conf[:log_level].should == :debug
218
+ conf[:log_rotation].should == 6
219
+ conf[:email].should == "robert@osburn-associates.ath.cx"
220
+ conf[:config_files].length.should == 1
221
+ conf[:pi].should == 3.14592
222
+
223
+ end
200
224
  end
201
225
 
202
226
  end
data/test/conf.d/jeckyl CHANGED
@@ -2,8 +2,10 @@
2
2
  # Jeckyl test file
3
3
  #
4
4
 
5
+ root = File.expand_path('../..', File.dirname(__FILE__))
6
+
5
7
  # should be a writable directory
6
- log_dir "./test"
8
+ log_dir File.join(root, "test")
7
9
 
8
10
  #should be a valid symbol
9
11
  log_level :verbose
@@ -25,7 +27,7 @@ sieve [2, 5, 7, 10, 15]
25
27
 
26
28
  # hash of anything
27
29
  my_opts = {:peter=>37, :paul=>40, :birds=>true}
28
- options my_opts
30
+ option_set my_opts
29
31
 
30
32
  # string formatted as email address
31
33
  email "robert@osburn-sharp.ath.cx"
@@ -44,3 +46,10 @@ flag "no"
44
46
  flag 1
45
47
  flag 0
46
48
 
49
+
50
+ # new things
51
+ offset 134
52
+
53
+ start_day 6
54
+
55
+
@@ -0,0 +1,55 @@
1
+ #
2
+ # Jeckyl test file
3
+ #
4
+
5
+ root = File.expand_path('../..', File.dirname(__FILE__))
6
+
7
+ # should be a writable directory
8
+ log_dir File.join(root, "test")
9
+
10
+ #should be a valid symbol
11
+ log_level :debug
12
+
13
+ # should be an integer
14
+ log_rotation 10
15
+
16
+ # can be a float or any numeric
17
+ threshold 2.3
18
+ threshold 9.6
19
+
20
+ # must be a float
21
+ pi 3.1459276
22
+
23
+ # array of anything
24
+ collection ["names", 1, true ]
25
+ # array of integers
26
+ sieve [2, 5, 7, 10, 15]
27
+
28
+ # hash of anything
29
+ my_opts = {:peter=>37, :paul=>40, :birds=>true}
30
+ option_set my_opts
31
+
32
+ # string formatted as email address
33
+ email "robert@osburn-sharp.ath.cx"
34
+
35
+ # real booleans
36
+ debug false
37
+ debug true
38
+
39
+ # generous booleans
40
+ flag "true"
41
+ flag "false"
42
+ flag "off"
43
+ flag "on"
44
+ flag "yes"
45
+ flag "no"
46
+ flag 1
47
+ flag 0
48
+
49
+
50
+ # new things
51
+ offset 134
52
+
53
+ start_day 6
54
+
55
+ log_level :debug
@@ -25,7 +25,7 @@ sieve [2, 5, 7, 10, 15]
25
25
 
26
26
  # hash of anything
27
27
  my_opts = {:peter=>37, :paul=>40, :birds=>true}
28
- options my_opts
28
+ option_set my_opts
29
29
 
30
30
  # string formatted as email address
31
31
  email "robert@osburn-associates.ath.cx"