configurable 0.4.1 → 0.4.2

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