configurable 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/History CHANGED
@@ -1,3 +1,12 @@
1
+ == 0.4.2 / 2009-03-30
2
+
3
+ * set delegate default no longer freezes value
4
+ * added :duplicate_default attribute to turn off default
5
+ value duplication
6
+ * standardized formatting of argument names
7
+ * added select and list_select validations
8
+ * added io validation + open_io method
9
+
1
10
  == 0.4.1 / 2009-03-23
2
11
 
3
12
  * Simplified internal API for nesting (removed initialize_<key>)
@@ -346,14 +346,13 @@ class ConfigParser
346
346
  block = case attributes[:type]
347
347
  when :switch then setup_switch(key, default_value, attributes)
348
348
  when :flag then setup_flag(key, default_value, attributes)
349
- when :list then setup_list(key, attributes)
349
+ when :list, :list_select then setup_list(key, attributes)
350
350
  when :hidden then return nil
351
- when nil then setup_option(key, attributes)
352
351
  else
353
352
  if respond_to?("setup_#{attributes[:type]}")
354
353
  send("setup_#{attributes[:type]}", key, default_value, attributes)
355
354
  else
356
- raise ArgumentError, "unsupported type: #{attributes[:type]}"
355
+ setup_option(key, attributes)
357
356
  end
358
357
  end
359
358
 
@@ -120,19 +120,15 @@ class ConfigParser
120
120
  # Attributes:
121
121
  #
122
122
  # :long the long key ("--key")
123
- # :arg_name the argument name ("KEY" or "A,B,C" for a comma split)
123
+ # :arg_name the argument name ("KEY")
124
124
  # :split the split character
125
125
  #
126
126
  def setup_list(key, attributes={})
127
127
  attributes[:long] ||= "--#{key}"
128
+ attributes[:long].to_s =~ /^(--)?(.*)$/
129
+ attributes[:arg_name] ||= $2.upcase
128
130
 
129
- if split = attributes[:split]
130
- attributes[:arg_name] ||= %w{A B C}.join(split)
131
- else
132
- attributes[:long].to_s =~ /^(--)?(.*)$/
133
- attributes[:arg_name] ||= $2.upcase
134
- end
135
-
131
+ split = attributes[:split]
136
132
  n = attributes[:n]
137
133
 
138
134
  lambda do |value|
@@ -173,6 +173,40 @@ module Configurable
173
173
  end
174
174
 
175
175
  protected
176
+
177
+ # Opens the file specified by io and yield it to the block. If io is an
178
+ # IO, it will be yielded immediately, and the mode is ignored. Nil io are
179
+ # simply ignored. The input io is always returned.
180
+ #
181
+ # === Usage
182
+ #
183
+ # open_io is used to compliment the io validation, to ensure that if a file
184
+ # is specified, it will be closed.
185
+ #
186
+ # class IoSample
187
+ # include Configurable
188
+ # config :output, $stdout, &c.io # can be an io or filepath
189
+ #
190
+ # def say_hello
191
+ # open_io(output, 'w') do |io|
192
+ # io << 'hello!'
193
+ # end
194
+ # end
195
+ # end
196
+ #
197
+ # In short, this method provides a way to responsibly handle IO and file
198
+ # configurations.
199
+ def open_io(io, mode='r')
200
+ case io
201
+ when String
202
+ dir = File.dirname(io)
203
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
204
+ File.open(io, mode) {|file| yield(file) }
205
+ when nil
206
+ else yield(io)
207
+ end
208
+ io
209
+ end
176
210
 
177
211
  # Initializes config. Default config values
178
212
  # are overridden as specified by overrides.
@@ -22,23 +22,25 @@ module Configurable
22
22
  # The writer method, by default key=
23
23
  attr_reader :writer
24
24
 
25
- # An hash of metadata for self, used to present the
26
- # delegate in different contexts (ex on the command
27
- # line, in a web form, or a desktop app).
25
+ # An hash of metadata for self, used to present the delegate in different
26
+ # contexts (ex on the command line, in a web form, or a desktop app).
27
+ # Note that attributes should be set through []= and not through this
28
+ # reader.
28
29
  attr_reader :attributes
29
30
 
30
31
  # Initializes a new Delegate with the specified key and default value.
31
32
  def initialize(reader, writer="#{reader}=", default=nil, attributes={})
33
+ @attributes = attributes
34
+
32
35
  self.default = default
33
36
  self.reader = reader
34
37
  self.writer = writer
35
-
36
- @attributes = attributes
37
38
  end
38
39
 
39
40
  # Sets the value of an attribute.
40
41
  def []=(key, value)
41
42
  attributes[key] = value
43
+ reset_duplicable if key == :duplicate_default
42
44
  end
43
45
 
44
46
  # Returns the value for the specified attribute, or
@@ -49,13 +51,14 @@ module Configurable
49
51
 
50
52
  # Sets the default value for self.
51
53
  def default=(value)
52
- @duplicable = Delegate.duplicable_value?(value)
53
- @default = value.freeze
54
+ @default = value
55
+ reset_duplicable
54
56
  end
55
57
 
56
- # Returns the default value, or a duplicate of the default
57
- # value if specified and the default value is duplicable
58
- # (see Delegate.duplicable_value?)
58
+ # Returns the default value, or a duplicate of the default value if specified.
59
+ # The default value will not be duplicated unless duplicable (see
60
+ # Delegate.duplicable_value?). Duplication can also be turned off by
61
+ # specifying self[:duplicate_default] = false.
59
62
  def default(duplicate=true)
60
63
  duplicate && @duplicable ? @default.dup : @default
61
64
  end
@@ -86,5 +89,15 @@ module Configurable
86
89
  self.writer == another.writer &&
87
90
  self.default(false) == another.default(false)
88
91
  end
92
+
93
+ private
94
+
95
+ # resets marker indiciating whether or not a default value is duplicable
96
+ def reset_duplicable # :nodoc:
97
+ @duplicable = case attributes[:duplicate_default]
98
+ when true, nil then Delegate.duplicable_value?(@default)
99
+ else false
100
+ end
101
+ end
89
102
  end
90
103
  end
@@ -46,6 +46,14 @@ module Configurable
46
46
  # in Configurable::DEFAULT_ATTRIBUTES.
47
47
  def register(block, attributes)
48
48
  DEFAULT_ATTRIBUTES[block] = attributes
49
+ block
50
+ end
51
+
52
+ # Registers the default attributes of the source as the attributes
53
+ # of the target. Attributes are duplicated so they may be modifed.
54
+ def register_as(source, target)
55
+ DEFAULT_ATTRIBUTES[target] = DEFAULT_ATTRIBUTES[source].dup
56
+ target
49
57
  end
50
58
 
51
59
  # Returns input if it matches any of the validations as in would in a case
@@ -71,9 +79,9 @@ module Configurable
71
79
  case input
72
80
  when *validations then input
73
81
  else
74
- if block_given?
75
- yield(input)
76
- else
82
+ if block_given? && yield(input)
83
+ input
84
+ else
77
85
  raise ValidationError.new(input, validations)
78
86
  end
79
87
  end
@@ -82,7 +90,21 @@ module Configurable
82
90
  else raise ArgumentError, "validations must be nil, or an array of valid inputs"
83
91
  end
84
92
  end
85
-
93
+
94
+ # Helper to load the input into a valid object. If a valid object is not
95
+ # loaded as YAML, or if an error occurs, the original input is returned.
96
+ def load_if_yaml(input, *validations)
97
+ begin
98
+ yaml = YAML.load(input)
99
+ case yaml
100
+ when *validations then yaml
101
+ else input
102
+ end
103
+ rescue(ArgumentError)
104
+ input
105
+ end
106
+ end
107
+
86
108
  # Returns a block that calls validate using the block input
87
109
  # and validations.
88
110
  def check(*validations)
@@ -127,8 +149,11 @@ module Configurable
127
149
  input = validate(input, [String])
128
150
  eval %Q{"#{input}"}
129
151
  end
152
+
153
+ # default attributes {:type => :string, :example => "string"}
130
154
  STRING = string_validation_block
131
-
155
+ register STRING, :type => :string, :example => "string"
156
+
132
157
  # Same as string but allows nil. Note the special
133
158
  # behavior of the nil string '~' -- rather than
134
159
  # being treated as a string, it is processed as nil
@@ -145,8 +170,10 @@ module Configurable
145
170
  else eval %Q{"#{input}"}
146
171
  end
147
172
  end
173
+
148
174
  STRING_OR_NIL = string_or_nil_validation_block
149
-
175
+ register_as STRING, STRING_OR_NIL
176
+
150
177
  # Returns a block that checks the input is a symbol.
151
178
  # String inputs are loaded as yaml first.
152
179
  #
@@ -157,15 +184,20 @@ module Configurable
157
184
  # symbol.call('str') # => ValidationError
158
185
  #
159
186
  def symbol(); SYMBOL; end
187
+
188
+ # default attributes {:type => :symbol, :example => ":sym"}
160
189
  SYMBOL = yaml(Symbol)
161
-
190
+ register SYMBOL, :type => :symbol, :example => ":sym"
191
+
162
192
  # Same as symbol but allows nil:
163
193
  #
164
194
  # symbol_or_nil.call('~') # => nil
165
195
  # symbol_or_nil.call(nil) # => nil
166
196
  def symbol_or_nil(); SYMBOL_OR_NIL; end
197
+
167
198
  SYMBOL_OR_NIL = yaml(Symbol, nil)
168
-
199
+ register_as SYMBOL, SYMBOL_OR_NIL
200
+
169
201
  # Returns a block that checks the input is true, false or nil.
170
202
  # String inputs are loaded as yaml first.
171
203
  #
@@ -182,7 +214,10 @@ module Configurable
182
214
  # boolean.call("str") # => ValidationError
183
215
  #
184
216
  def boolean(); BOOLEAN; end
217
+
218
+ # default attributes {:type => :boolean, :example => "true, yes"}
185
219
  BOOLEAN = yaml(true, false, nil)
220
+ register BOOLEAN, :type => :boolean, :example => "true, yes"
186
221
 
187
222
  # Same as boolean.
188
223
  def switch(); SWITCH; end
@@ -209,9 +244,9 @@ module Configurable
209
244
  #
210
245
  def array(); ARRAY; end
211
246
 
212
- # default attributes {:arg_name => "'[a, b, c]'"}
247
+ # default attributes {:type => :array, :example => "[a, b, c]"}
213
248
  ARRAY = yaml(Array)
214
- register ARRAY, :arg_name => "'[a, b, c]'"
249
+ register ARRAY, :type => :array, :example => "[a, b, c]"
215
250
 
216
251
  # Same as array but allows nil:
217
252
  #
@@ -219,9 +254,8 @@ module Configurable
219
254
  # array_or_nil.call(nil) # => nil
220
255
  def array_or_nil(); ARRAY_OR_NIL; end
221
256
 
222
- # default attributes {:arg_name => "'[a, b, c]'"}
223
257
  ARRAY_OR_NIL = yaml(Array, nil)
224
- register ARRAY_OR_NIL, :arg_name => "'[a, b, c]'"
258
+ register_as ARRAY, ARRAY_OR_NIL
225
259
 
226
260
  # Returns a block that checks the input is an array,
227
261
  # then yamlizes each string value of the array.
@@ -254,9 +288,9 @@ module Configurable
254
288
  #
255
289
  def hash(); HASH; end
256
290
 
257
- # default attributes {:arg_name => "'{one: 1, two: 2}'"}
291
+ # default attributes {:type => :hash, :example => "{one: 1, two: 2}"}
258
292
  HASH = yaml(Hash)
259
- register HASH, :arg_name => "'{one: 1, two: 2}'"
293
+ register HASH, :type => :hash, :example => "{one: 1, two: 2}"
260
294
 
261
295
  # Same as hash but allows nil:
262
296
  #
@@ -264,9 +298,8 @@ module Configurable
264
298
  # hash_or_nil.call(nil) # => nil
265
299
  def hash_or_nil(); HASH_OR_NIL; end
266
300
 
267
- # default attributes {:arg_name => "'{one: 1, two: 2}'"}
268
301
  HASH_OR_NIL = yaml(Hash, nil)
269
- register HASH_OR_NIL, :arg_name => "'{one: 1, two: 2}'"
302
+ register_as HASH, HASH_OR_NIL
270
303
 
271
304
  # Returns a block that checks the input is an integer.
272
305
  # String inputs are loaded as yaml first.
@@ -279,15 +312,20 @@ module Configurable
279
312
  # integer.call('str') # => ValidationError
280
313
  #
281
314
  def integer(); INTEGER; end
315
+
316
+ # default attributes {:type => :integer, :example => "2"}
282
317
  INTEGER = yaml(Integer)
283
-
318
+ register INTEGER, :type => :integer, :example => "2"
319
+
284
320
  # Same as integer but allows nil:
285
321
  #
286
322
  # integer_or_nil.call('~') # => nil
287
323
  # integer_or_nil.call(nil) # => nil
288
324
  def integer_or_nil(); INTEGER_OR_NIL; end
325
+
289
326
  INTEGER_OR_NIL = yaml(Integer, nil)
290
-
327
+ register_as INTEGER, INTEGER_OR_NIL
328
+
291
329
  # Returns a block that checks the input is a float.
292
330
  # String inputs are loaded as yaml first.
293
331
  #
@@ -300,14 +338,19 @@ module Configurable
300
338
  # float.call('str') # => ValidationError
301
339
  #
302
340
  def float(); FLOAT; end
341
+
342
+ # default attributes {:type => :float, :example => "2.2, 2.0e+2"}
303
343
  FLOAT = yaml(Float)
304
-
344
+ register FLOAT, :type => :float, :example => "2.2, 2.0e+2"
345
+
305
346
  # Same as float but allows nil:
306
347
  #
307
348
  # float_or_nil.call('~') # => nil
308
349
  # float_or_nil.call(nil) # => nil
309
350
  def float_or_nil(); FLOAT_OR_NIL; end
351
+
310
352
  FLOAT_OR_NIL = yaml(Float, nil)
353
+ register_as FLOAT, FLOAT_OR_NIL
311
354
 
312
355
  # Returns a block that checks the input is a number.
313
356
  # String inputs are loaded as yaml first.
@@ -322,14 +365,19 @@ module Configurable
322
365
  # num.call('str') # => ValidationError
323
366
  #
324
367
  def num(); NUMERIC; end
368
+
369
+ # default attributes {:type => :num, :example => "2, 2.2, 2.0e+2"}
325
370
  NUMERIC = yaml(Numeric)
326
-
371
+ register NUMERIC, :type => :num, :example => "2, 2.2, 2.0e+2"
372
+
327
373
  # Same as num but allows nil:
328
374
  #
329
375
  # num_or_nil.call('~') # => nil
330
376
  # num_or_nil.call(nil) # => nil
331
377
  def num_or_nil(); NUMERIC_OR_NIL; end
378
+
332
379
  NUMERIC_OR_NIL = yaml(Numeric, nil)
380
+ register_as NUMERIC, NUMERIC_OR_NIL
333
381
 
334
382
  # Returns a block that checks the input is a regexp. String inputs are
335
383
  # loaded as yaml; if the result is not a regexp, it is converted to
@@ -349,10 +397,7 @@ module Configurable
349
397
  def regexp(); REGEXP; end
350
398
  regexp_block = lambda do |input|
351
399
  if input.kind_of?(String)
352
- begin
353
- input = validate(YAML.load(input), [Regexp]) {|obj| input }
354
- rescue(ArgumentError)
355
- end
400
+ input = load_if_yaml(input, Regexp)
356
401
  end
357
402
 
358
403
  if input.kind_of?(String)
@@ -361,7 +406,10 @@ module Configurable
361
406
 
362
407
  validate(input, [Regexp])
363
408
  end
409
+
410
+ # default attributes {:type => :regexp, :example => "/regexp/i"}
364
411
  REGEXP = regexp_block
412
+ register REGEXP, :type => :regexp, :example => "/regexp/i"
365
413
 
366
414
  # Same as regexp but allows nil. Note the special behavior of the nil
367
415
  # string '~' -- rather than being converted to a regexp, it is processed
@@ -376,7 +424,9 @@ module Configurable
376
424
  else REGEXP[input]
377
425
  end
378
426
  end
427
+
379
428
  REGEXP_OR_NIL = regexp_or_nil_block
429
+ register_as REGEXP, REGEXP_OR_NIL
380
430
 
381
431
  # Returns a block that checks the input is a range. String inputs are
382
432
  # loaded as yaml; if the result is still a string, it is split into a
@@ -398,10 +448,7 @@ module Configurable
398
448
  def range(); RANGE; end
399
449
  range_block = lambda do |input|
400
450
  if input.kind_of?(String)
401
- begin
402
- input = validate(YAML.load(input), [Range]) {|obj| input }
403
- rescue(ArgumentError)
404
- end
451
+ input = load_if_yaml(input, Range)
405
452
  end
406
453
 
407
454
  if input.kind_of?(String) && input =~ /^([^.]+)(\.{2,3})([^.]+)$/
@@ -410,8 +457,11 @@ module Configurable
410
457
 
411
458
  validate(input, [Range])
412
459
  end
460
+
461
+ # default attributes {:type => :range, :example => "min..max"}
413
462
  RANGE = range_block
414
-
463
+ register RANGE, :type => :range, :example => "min..max"
464
+
415
465
  # Same as range but allows nil:
416
466
  #
417
467
  # range_or_nil.call('~') # => nil
@@ -423,7 +473,9 @@ module Configurable
423
473
  else RANGE[input]
424
474
  end
425
475
  end
476
+
426
477
  RANGE_OR_NIL = range_or_nil_block
478
+ register_as RANGE, RANGE_OR_NIL
427
479
 
428
480
  # Returns a block that checks the input is a Time. String inputs are
429
481
  # loaded using Time.parse and then converted into times. Parsed times
@@ -460,8 +512,11 @@ module Configurable
460
512
  input = Time.parse(input) if input.kind_of?(String)
461
513
  validate(input, [Time])
462
514
  end
515
+
516
+ # default attributes {:type => :time, :example => "2008-08-08 08:00:00"}
463
517
  TIME = time_block
464
-
518
+ register TIME, :type => :time, :example => "2008-08-08 08:00:00"
519
+
465
520
  # Same as time but allows nil:
466
521
  #
467
522
  # time_or_nil.call('~') # => nil
@@ -474,7 +529,119 @@ module Configurable
474
529
  else TIME[input]
475
530
  end
476
531
  end
532
+
477
533
  TIME_OR_NIL = time_or_nil_block
534
+ register_as TIME, TIME_OR_NIL
535
+
536
+ # Returns a block that only allows the specified values. Select can take
537
+ # a block that will validate each individual value.
538
+ #
539
+ # s = select(1,2,3, &integer)
540
+ # s.class # => Proc
541
+ # s.call(1) # => 1
542
+ # s.call('3') # => 3
543
+ #
544
+ # s.call(nil) # => ValidationError
545
+ # s.call(0) # => ValidationError
546
+ # s.call('4') # => ValidationError
547
+ #
548
+ # The select block is registered with these default attributes:
549
+ #
550
+ # {:type => :select, :values => values}
551
+ #
552
+ def select(*values, &validation)
553
+ block = lambda do |input|
554
+ input = validation.call(input) if validation
555
+ validate(input, values)
556
+ end
557
+
558
+ register(block, :type => :select, :values => values)
559
+ end
560
+
561
+ # Returns a block that checks the input is an array, and that each member
562
+ # of the array is one of the specified values. A block may be provided
563
+ # to validate each individual value.
564
+ #
565
+ # s = list_select(1,2,3, &integer)
566
+ # s.class # => Proc
567
+ # s.call([1]) # => [1]
568
+ # s.call([1, '3']) # => [1, 3]
569
+ # s.call([]) # => []
570
+ #
571
+ # s.call(1) # => ValidationError
572
+ # s.call([nil]) # => ValidationError
573
+ # s.call([0]) # => ValidationError
574
+ # s.call(['4']) # => ValidationError
575
+ #
576
+ # The list_select block is registered with these default attributes:
577
+ #
578
+ # {:type => :list_select, :values => values, :split => ','}
579
+ #
580
+ def list_select(*values, &validation)
581
+ block = lambda do |input|
582
+ args = validate(input, [Array])
583
+ args.collect! {|arg| validation.call(arg) } if validation
584
+ args.each {|arg| validate(arg, values) }
585
+ end
586
+
587
+ register(block, :type => :list_select, :values => values, :split => ',')
588
+ end
589
+
590
+ # Returns a block validating the input is an IO or a string. String inputs
591
+ # are expected to be filepaths, but io does not open a file immediately.
592
+ #
593
+ # io.class # => Proc
594
+ # io.call($stdout) # => $stdout
595
+ # io.call('/path/to/file') # => '/path/to/file'
596
+ #
597
+ # io.call(nil) # => ValidationError
598
+ # io.call(10) # => ValidationError
599
+ #
600
+ # An IO api can be specified to allow other objects to be validated. This
601
+ # is useful for duck-typing an IO when a known subset of methods are needed.
602
+ #
603
+ # array_io = io(:<<)
604
+ # array_io.call($stdout) # => $stdout
605
+ # array_io.call([]) # => []
606
+ # array_io.call(nil) # => ValidationError
607
+ #
608
+ def io(*api)
609
+ if api.empty?
610
+ IO_OR_STRING
611
+ else
612
+ block = lambda do |input|
613
+ validate(input, [IO, String]) do
614
+ api.all? {|m| input.respond_to?(m) }
615
+ end
616
+ end
617
+
618
+ register_as IO_OR_STRING, block
619
+ end
620
+ end
621
+
622
+ # default attributes {:type => :io, :example => "/path/to/file"}
623
+ IO_OR_STRING = check(IO, String)
624
+ register IO_OR_STRING, :type => :io, :example => "/path/to/file"
625
+
626
+ # Same as io but allows nil:
627
+ #
628
+ # io_or_nil.call(nil) # => nil
629
+ #
630
+ def io_or_nil(*api)
631
+ if api.empty?
632
+ IO_STRING_OR_NIL
633
+ else
634
+ block = lambda do |input|
635
+ validate(input, [IO, String, nil]) do
636
+ api.all? {|m| input.respond_to?(m) }
637
+ end
638
+ end
639
+
640
+ register_as IO_STRING_OR_NIL, block
641
+ end
642
+ end
478
643
 
644
+ IO_STRING_OR_NIL = check(IO, String, nil)
645
+ register_as IO_OR_STRING, IO_STRING_OR_NIL
479
646
  end
480
647
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configurable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Chiang
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-23 00:00:00 -06:00
12
+ date: 2009-03-30 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency