configurable 0.7.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Help/Command Line.rdoc +141 -0
  2. data/Help/Config Syntax.rdoc +229 -0
  3. data/Help/Config Types.rdoc +143 -0
  4. data/{History → History.rdoc} +9 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.rdoc +144 -0
  7. data/lib/configurable.rb +7 -270
  8. data/lib/configurable/class_methods.rb +344 -367
  9. data/lib/configurable/config_classes.rb +3 -0
  10. data/lib/configurable/config_classes/list_config.rb +26 -0
  11. data/lib/configurable/config_classes/nest_config.rb +50 -0
  12. data/lib/configurable/config_classes/scalar_config.rb +91 -0
  13. data/lib/configurable/config_hash.rb +87 -112
  14. data/lib/configurable/config_types.rb +6 -0
  15. data/lib/configurable/config_types/boolean_type.rb +22 -0
  16. data/lib/configurable/config_types/float_type.rb +11 -0
  17. data/lib/configurable/config_types/integer_type.rb +11 -0
  18. data/lib/configurable/config_types/nest_type.rb +39 -0
  19. data/lib/configurable/config_types/object_type.rb +58 -0
  20. data/lib/configurable/config_types/string_type.rb +15 -0
  21. data/lib/configurable/conversions.rb +91 -0
  22. data/lib/configurable/module_methods.rb +0 -1
  23. data/lib/configurable/version.rb +1 -5
  24. metadata +73 -30
  25. data/README +0 -207
  26. data/lib/cdoc.rb +0 -413
  27. data/lib/cdoc/cdoc_html_generator.rb +0 -38
  28. data/lib/cdoc/cdoc_html_template.rb +0 -42
  29. data/lib/config_parser.rb +0 -563
  30. data/lib/config_parser/option.rb +0 -108
  31. data/lib/config_parser/switch.rb +0 -44
  32. data/lib/config_parser/utils.rb +0 -177
  33. data/lib/configurable/config.rb +0 -97
  34. data/lib/configurable/indifferent_access.rb +0 -35
  35. data/lib/configurable/nest_config.rb +0 -78
  36. data/lib/configurable/ordered_hash_patch.rb +0 -85
  37. data/lib/configurable/utils.rb +0 -186
  38. data/lib/configurable/validation.rb +0 -768
@@ -1,78 +0,0 @@
1
- require 'configurable/config'
2
-
3
- module Configurable
4
-
5
- # NestConfigs are used to nest configurable classes.
6
- class NestConfig < Config
7
-
8
- # The nested configurable class
9
- attr_reader :nest_class
10
-
11
- # Initializes a new NestConfig
12
- def initialize(nest_class, reader, writer="#{reader}=", attributes={}, init=true)
13
- self.nest_class = nest_class
14
- self.reader = reader
15
- self.writer = writer
16
- @attributes = attributes
17
- @init = init
18
- end
19
-
20
- # Returns a hash of the default configuration values for nest_class.
21
- def default
22
- default = {}
23
- nest_class.configurations.each_pair do |key, delegate|
24
- default[key] = delegate.default
25
- end
26
- default
27
- end
28
-
29
- # Calls the reader on the reciever to retreive an instance of the
30
- # nest_class and returns it's config. Returns nil if the reader
31
- # returns nil.
32
- def get(receiver)
33
- if instance = receiver.send(reader)
34
- instance.config
35
- else
36
- nil
37
- end
38
- end
39
-
40
- # Calls the reader on the reciever to retrieve an instance of the
41
- # nest_class, and reconfigures it with value. The instance will
42
- # be initialized by init if necessary.
43
- #
44
- # If value is an instance of the nest_class, then it will be set
45
- # by calling writer.
46
- def set(receiver, value)
47
- if value.kind_of?(nest_class)
48
- receiver.send(writer, value)
49
- else
50
- configurable = receiver.send(reader) || init(receiver)
51
- configurable.reconfigure(value)
52
- end
53
- end
54
-
55
- # Initializes an instance of nest_class and sets it on the receiver. The
56
- # instance is initialized by calling nest_class.new with no arguments.
57
- def init(receiver)
58
- receiver.send(writer, nest_class.new)
59
- end
60
-
61
- # Returns an inspection string.
62
- def inspect
63
- "#<#{self.class}:#{object_id} reader=#{reader} writer=#{writer} nest_class=#{nest_class.inspect} >"
64
- end
65
-
66
- protected
67
-
68
- # sets nest_class, checking that the nested class
69
- # is both a Class and Configurable
70
- def nest_class=(nest_class) # :nodoc:
71
- unless nest_class.kind_of?(Class) && nest_class.ancestors.include?(Configurable)
72
- raise ArgumentError, "not a Configurable class: #{nest_class}"
73
- end
74
-
75
- @nest_class = nest_class
76
- end
77
- end
78
- end
@@ -1,85 +0,0 @@
1
- module Configurable
2
-
3
- # Beginning with ruby 1.9, Hash tracks the order of insertion and methods
4
- # like each_pair return pairs in order. Configurable leverages this feature
5
- # to keep configurations in order for the command line documentation produced
6
- # by ConfigParser.
7
- #
8
- # Pre-1.9 ruby implementations require a patched Hash that tracks insertion
9
- # order. This very thin subclass of hash does that for ASET insertions and
10
- # each_pair. OrderedHashPatches are used as the configurations object in
11
- # Configurable classes for pre-1.9 ruby implementations and for nothing else.
12
- #
13
- # OrderedHashPatch is only loaded for pre-1.9 ruby implementations.
14
- class OrderedHashPatch < Hash
15
- def initialize
16
- super
17
- @insertion_order = []
18
- end
19
-
20
- # ASET insertion, tracking insertion order.
21
- def []=(key, value)
22
- @insertion_order << key unless @insertion_order.include?(key)
23
- super
24
- end
25
-
26
- # Keys, sorted into insertion order
27
- def keys
28
- super.sort_by do |key|
29
- @insertion_order.index(key) || length
30
- end
31
- end
32
-
33
- # Yields each key-value pair to the block in insertion order.
34
- def each_pair
35
- keys.each do |key|
36
- yield(key, fetch(key))
37
- end
38
- end
39
-
40
- # Merges another into self in a way that preserves insertion order.
41
- def merge!(another)
42
- another.each_pair do |key, value|
43
- self[key] = value
44
- end
45
- end
46
-
47
- # Ensures the insertion order of duplicates is separate from parents.
48
- def initialize_copy(orig)
49
- super
50
- @insertion_order = orig.instance_variable_get(:@insertion_order).dup
51
- end
52
-
53
- # Overridden to load an array of [key, value] pairs in order (see to_yaml).
54
- # The default behavior for loading from a hash of key-value pairs is
55
- # preserved, but the insertion order will not be preserved.
56
- def yaml_initialize( tag, val )
57
- @insertion_order ||= []
58
-
59
- if Array === val
60
- val.each do |k, v|
61
- self[k] = v
62
- end
63
- else
64
- super
65
- end
66
- end
67
-
68
- # Overridden to preserve insertion order by serializing self as an array
69
- # of [key, value] pairs.
70
- def to_yaml( opts = {} )
71
- YAML::quick_emit( object_id, opts ) do |out|
72
- out.seq( taguri, to_yaml_style ) do |seq|
73
- each_pair do |key, value|
74
- seq.add( [key, value] )
75
- end
76
- end
77
- end
78
- end
79
- end
80
-
81
- module ClassMethods
82
- remove_const(:CONFIGURATIONS_CLASS)
83
- CONFIGURATIONS_CLASS = OrderedHashPatch
84
- end
85
- end
@@ -1,186 +0,0 @@
1
- module Configurable
2
-
3
- # Utility methods to dump and load Configurable class configurations.
4
- module Utils
5
- module_function
6
-
7
- default_dump_block = lambda do |key, config|
8
- # note: this causes order to be lost...
9
- YAML.dump({key => config.default})[5..-1]
10
- end
11
-
12
- # A block performing the default YAML dump.
13
- DEFAULT_DUMP = default_dump_block
14
-
15
- default_load_block = lambda do |base, key, value|
16
- base[key] ||= value
17
- end
18
-
19
- # A block performing the default load.
20
- DEFAULT_LOAD = default_load_block
21
-
22
- # Dumps configurations to target as yaml. Configs are output in order,
23
- # and symbol keys are be stringified if configurations has been extended
24
- # with IndifferentAccess (this produces a nicer config file).
25
- #
26
- # class DumpExample
27
- # include Configurable
28
- #
29
- # config :sym, :value # a symbol config
30
- # config 'str', 'value' # a string config
31
- # end
32
- #
33
- # Utils.dump(DumpExample.configurations, "\n")
34
- # # => %q{
35
- # # sym: :value
36
- # # str: value
37
- # # }
38
- #
39
- # Dump may be provided with a block to format each (key, Config) pair;
40
- # the block results are pushed directly to target, so newlines must be
41
- # specified manually.
42
- #
43
- # Utils.dump(DumpExample.configurations, "\n") do |key, delegate|
44
- # yaml = YAML.dump({key => delegate.default})[5..-1]
45
- # "# #{delegate[:desc]}\n#{yaml}\n"
46
- # end
47
- # # => %q{
48
- # # # a symbol config
49
- # # sym: :value
50
- # #
51
- # # # a string config
52
- # # str: value
53
- # #
54
- # # }
55
- #
56
- def dump(configs, target="")
57
- return dump(configs, target, &DEFAULT_DUMP) unless block_given?
58
-
59
- stringify = configs.kind_of?(IndifferentAccess)
60
- configs.each_pair do |key, config|
61
- key = key.to_s if stringify && key.kind_of?(Symbol)
62
- target << yield(key, config)
63
- end
64
-
65
- target
66
- end
67
-
68
- # Dumps the configurations to the specified file. If recurse is true,
69
- # nested configs are each dumped to their own file, based on the nesting
70
- # key. For instance if you nested a in b:
71
- #
72
- # a_configs = {
73
- # 'key' => Config.new(:r, :w, 'a default')}
74
- # b_configs = {
75
- # 'key' => Config.new(:r, :w, 'b default')}
76
- # 'a' => Config.new(:r, :w, ConfigHash.new(a_configs))}}
77
- #
78
- # Utils.dump_file(b_configs, 'b.yml')
79
- # File.read('b.yml') # => "key: b default"
80
- # File.read('b/a.yml') # => "key: a default"
81
- #
82
- # In this way, each nested config gets it's own file. The load_file method
83
- # can recursively load configurations from this file structure. When recurse
84
- # is false, all configs are dumped to a single file.
85
- #
86
- # dump_file uses a method that collects all dumps in a preview array before
87
- # dumping, so that the dump results can be redirected other places than the
88
- # file system. If preview is set to false, no files will be created. The
89
- # preview dumps are always returned by dump_file.
90
- #
91
- # ==== Note
92
- # For load_file to correctly load a recursive dump, all config hashes
93
- # must use String keys. Symbol keys are allowed if the config hashes use
94
- # IndifferentAccess; all other keys will not load properly. By default
95
- # Configurable is set up to satisfy these conditions.
96
- #
97
- # 1.8 Bug: Currently dump_file with recurse=false will cause order to be
98
- # lost in nested configs. See http://bahuvrihi.lighthouseapp.com/projects/21202-configurable/tickets/8
99
- def dump_file(configs, path, recurse=false, preview=false, &block)
100
- return dump_file(configs, path, recurse, preview, &DEFAULT_DUMP) unless block_given?
101
-
102
- current = ""
103
- dumps = [[path, current]]
104
-
105
- dump(configs, current) do |key, config|
106
- if recurse && config.kind_of?(NestConfig)
107
- dumps.concat(dump_file(config.nest_class.configurations, recursive_path(key, path), true, true, &block))
108
- ""
109
- else
110
- yield(key, config)
111
- end
112
- end
113
-
114
- dumps.each do |dump_path, content|
115
- dir = File.dirname(dump_path)
116
- Dir.mkdir(dir) unless File.exists?(dir)
117
- File.open(dump_path, "w") do |io|
118
- io << content
119
- end
120
- end unless preview
121
-
122
- dumps
123
- end
124
-
125
- # Loads the string as YAML.
126
- def load(str)
127
- YAML.load(str)
128
- end
129
-
130
- # Loads the file contents as YAML. If recurse is true, a hash will be
131
- # recursively loaded. A block may be provided to set recursively loaded
132
- # values in the hash loaded from the path.
133
- def load_file(path, recurse=false, &block)
134
- return load_file(path, recurse, &DEFAULT_LOAD) if recurse && !block_given?
135
- base = File.directory?(path) ? {} : (YAML.load_file(path) || {})
136
-
137
- if recurse
138
- # determine the files/dirs to load recursively
139
- # and add them to paths by key (ie the base
140
- # name of the path, minus any extname)
141
- paths = {}
142
- files, dirs = Dir.glob("#{path.chomp(File.extname(path))}/*").partition do |sub_path|
143
- File.file?(sub_path)
144
- end
145
-
146
- # directories are added to paths first so they can be
147
- # overridden by the files (appropriate since the file
148
- # will recursively load the directory if it exists)
149
- dirs.each do |dir|
150
- paths[File.basename(dir)] = dir
151
- end
152
-
153
- # when adding files, check that no two files map to
154
- # the same key (ex a.yml, a.yaml).
155
- files.each do |filepath|
156
- key = File.basename(filepath).chomp(File.extname(filepath))
157
- if existing = paths[key]
158
- if File.file?(existing)
159
- confict = [File.basename(paths[key]), File.basename(filepath)].sort
160
- raise "multiple files load the same key: #{confict.inspect}"
161
- end
162
- end
163
-
164
- paths[key] = filepath
165
- end
166
-
167
- # recursively load each file and reverse merge
168
- # the result into the base
169
- paths.each_pair do |key, recursive_path|
170
- value = load_file(recursive_path, true, &block)
171
- yield(base, key, value)
172
- end
173
- end
174
-
175
- base
176
- end
177
-
178
- # A helper to create and prepare a recursive dump path.
179
- def recursive_path(key, path)
180
- ext = File.extname(path)
181
- dir = path.chomp(ext)
182
-
183
- "#{File.join(dir, key.to_s)}#{ext}"
184
- end
185
- end
186
- end
@@ -1,768 +0,0 @@
1
- autoload(:YAML, 'yaml')
2
-
3
- module Configurable
4
- # A hash of (block, default attributes) for config blocks. The
5
- # attributes for nil will be merged with those for the block.
6
- DEFAULT_ATTRIBUTES = Hash.new({})
7
- DEFAULT_ATTRIBUTES[nil] = {:reader => true, :writer => true}
8
-
9
- # Validation generates blocks for common validations and transformations of
10
- # configurations set through Configurable. In general these blocks load
11
- # string inputs as YAML and validate the results; non-string inputs are
12
- # simply validated.
13
- #
14
- # integer = Validation.integer
15
- # integer.class # => Proc
16
- # integer.call(1) # => 1
17
- # integer.call('1') # => 1
18
- # integer.call(nil) # => ValidationError
19
- #
20
- #--
21
- # Developers: note the unusual syntax for declaring constants that are
22
- # blocks defined by lambda... ex:
23
- #
24
- # block = lambda {}
25
- # CONST = block
26
- #
27
- # This syntax plays well with RDoc, which otherwise gets jacked when you
28
- # do it all in one step.
29
- module Validation
30
-
31
- # Raised when a Validation block fails.
32
- class ValidationError < ArgumentError
33
- def initialize(input, validations)
34
- super case
35
- when validations.empty?
36
- "no validations specified"
37
- else
38
- "expected #{validations.inspect} but was: #{input.inspect}"
39
- end
40
- end
41
- end
42
-
43
- # Raised when validate_api fails.
44
- class ApiError < ArgumentError
45
- def initialize(input, methods)
46
- super case
47
- when methods.empty?
48
- "no api specified"
49
- else
50
- "expected api #{methods.inspect} for: #{input.inspect}"
51
- end
52
- end
53
- end
54
-
55
- module_function
56
-
57
- # Registers the default attributes with the specified block
58
- # in Configurable::DEFAULT_ATTRIBUTES.
59
- def register(attributes={}, &block)
60
- DEFAULT_ATTRIBUTES[block] = attributes
61
- block
62
- end
63
-
64
- # Registers the default attributes of the source as the attributes
65
- # of the target. Overridding or additional attributes are merged
66
- # to the defaults.
67
- def register_as(source, target, attributes={})
68
- DEFAULT_ATTRIBUTES[target] = DEFAULT_ATTRIBUTES[source].dup.merge!(attributes)
69
- target
70
- end
71
-
72
- # Returns the attributes registered to the block.
73
- def attributes(block)
74
- DEFAULT_ATTRIBUTES[block] || {}
75
- end
76
-
77
- # Returns input if it matches any of the validations as in would in a case
78
- # statement. Raises a ValidationError otherwise. For example:
79
- #
80
- # validate(10, [Integer, nil])
81
- #
82
- # Does the same as:
83
- #
84
- # case 10
85
- # when Integer, nil then input
86
- # else raise ValidationError.new(...)
87
- # end
88
- #
89
- # A block may be provided to handle invalid inputs; if provided it will be
90
- # called with the input and a ValidationError will not be raised unless the
91
- # block returns false. Note the validations input must be an Array or nil;
92
- # validate will raise an ArgumentError otherwise. All inputs are
93
- # considered VALID if validations == nil.
94
- def validate(input, validations)
95
- case validations
96
- when Array
97
-
98
- case input
99
- when *validations then input
100
- else
101
- if block_given? && yield(input)
102
- input
103
- else
104
- raise ValidationError.new(input, validations)
105
- end
106
- end
107
-
108
- when nil then input
109
- else raise ArgumentError, "validations must be an array of valid inputs or nil"
110
- end
111
- end
112
-
113
- # Returns input if it responds to all of the specified methods. Raises an
114
- # ApiError otherwise. For example:
115
- #
116
- # validate_api(10, [:to_s, :to_f]) # => 10
117
- # validate_api(Object.new, [:to_s, :to_f]) # !> ApiError
118
- #
119
- # A block may be provided to handle invalid inputs; if provided it will be
120
- # called with the input and an ApiError will not be raised unless the
121
- # block returns false. Note the methods input must be an Array or nil;
122
- # validate_api will raise an ArgumentError otherwise. All inputs are
123
- # considered VALID if methods == nil.
124
- def validate_api(input, methods)
125
- case methods
126
- when Array
127
- unless methods.all? {|m| input.respond_to?(m) }
128
- if block_given? && yield(input)
129
- input
130
- else
131
- raise ApiError.new(input, methods)
132
- end
133
- end
134
- when nil
135
- else raise ArgumentError, "methods must be an array or nil"
136
- end
137
-
138
- input
139
- end
140
-
141
- # Helper to load the input into a valid object. If a valid object is not
142
- # loaded as YAML, or if an error occurs, the original input is returned.
143
- def load_if_yaml(input, *validations)
144
- begin
145
- yaml = YAML.load(input)
146
- case yaml
147
- when *validations then yaml
148
- else input
149
- end
150
- rescue(ArgumentError)
151
- input
152
- end
153
- end
154
-
155
- # Returns a block that calls validate using the block input
156
- # and validations.
157
- def check(*validations)
158
- lambda {|input| validate(input, validations) }
159
- end
160
-
161
- # Guesses and returns a block for the example value.
162
- def guess(value)
163
- case value
164
- when true then switch
165
- when false then flag
166
- when Numeric then numeric
167
- when Array then list
168
- when String then string
169
- when Time then time
170
- when Range then range
171
- when Regexp then regexp
172
- else yaml
173
- end
174
- end
175
-
176
- # Returns a block that calls validate_api using the block input
177
- # and methods.
178
- def api(*methods)
179
- lambda do |input|
180
- validate_api(input, methods)
181
- end
182
- end
183
-
184
- # Returns a block that loads input strings as YAML, then
185
- # calls validate with the result and validations. Non-string
186
- # inputs are validated directly.
187
- #
188
- # b = yaml(Integer, nil)
189
- # b.class # => Proc
190
- # b.call(1) # => 1
191
- # b.call("1") # => 1
192
- # b.call(nil) # => nil
193
- # b.call("str") # => ValidationError
194
- #
195
- # If no validations are specified, the result will be
196
- # returned without validation.
197
- def yaml(*validations)
198
- validations = nil if validations.empty?
199
- lambda do |input|
200
- input = YAML.load(input) if input.kind_of?(String)
201
- validate(input, validations)
202
- end
203
- end
204
-
205
- # Returns a block that checks the input is a string.
206
- #
207
- # string.class # => Proc
208
- # string.call('str') # => 'str'
209
- # string.call(nil) # => ValidationError
210
- # string.call(:sym) # => ValidationError
211
- #
212
- def string(); STRING; end
213
- string_validation_block = lambda do |input|
214
- validate(input, [String])
215
- end
216
-
217
- # default attributes {:type => :string, :example => "string"}
218
- STRING = string_validation_block
219
- register :type => :string, :example => "string", &STRING
220
-
221
- # Same as string but allows nil. Note the special
222
- # behavior of the nil string '~' -- rather than
223
- # being treated as a string, it is processed as nil
224
- # to be consistent with the other [class]_or_nil
225
- # methods.
226
- #
227
- # string_or_nil.call('~') # => nil
228
- # string_or_nil.call(nil) # => nil
229
- def string_or_nil(); STRING_OR_NIL; end
230
- string_or_nil_validation_block = lambda do |input|
231
- input = validate(input, [String, nil])
232
- case input
233
- when nil, '~' then nil
234
- else input
235
- end
236
- end
237
-
238
- STRING_OR_NIL = string_or_nil_validation_block
239
- register_as STRING, STRING_OR_NIL
240
-
241
- # Returns a block that checks the input is a symbol.
242
- # String inputs are loaded as yaml first.
243
- #
244
- # symbol.class # => Proc
245
- # symbol.call(:sym) # => :sym
246
- # symbol.call(':sym') # => :sym
247
- # symbol.call(nil) # => ValidationError
248
- # symbol.call('str') # => ValidationError
249
- #
250
- def symbol(); SYMBOL; end
251
-
252
- # default attributes {:type => :symbol, :example => ":sym"}
253
- SYMBOL = yaml(Symbol)
254
- register :type => :symbol, :example => ":sym", &SYMBOL
255
-
256
- # Same as symbol but allows nil:
257
- #
258
- # symbol_or_nil.call('~') # => nil
259
- # symbol_or_nil.call(nil) # => nil
260
- def symbol_or_nil(); SYMBOL_OR_NIL; end
261
-
262
- SYMBOL_OR_NIL = yaml(Symbol, nil)
263
- register_as SYMBOL, SYMBOL_OR_NIL
264
-
265
- # Returns a block that checks the input is a symbol.
266
- # String inputs are directly converted to a symbol.
267
- #
268
- # strbol.class # => Proc
269
- # strbol.call(:sym) # => :sym
270
- # strbol.call(':sym') # => :":sym"
271
- # strbol.call('str') # => :sym
272
- # strbol.call(nil) # => ValidationError
273
- #
274
- def strbol(); STRBOL; end
275
-
276
- # default attributes {:type => :symbol, :example => ":sym"}
277
- STRBOL = lambda do |input|
278
- if input.kind_of?(String)
279
- input = input.to_sym
280
- end
281
-
282
- validate(input, [Symbol])
283
- end
284
- register :type => :symbol, :example => ":sym", &STRBOL
285
-
286
- # Same as strbol but allows nil. Tilde is considered a string
287
- # equivalent of nil (this behavior is consistent with the YAML
288
- # methods but obviously inconsistent with the strbol behavior).
289
- #
290
- # strbol_or_nil.call('~') # => nil
291
- # strbol_or_nil.call(nil) # => nil
292
- def strbol_or_nil(); STRBOL_OR_NIL; end
293
-
294
- STRBOL_OR_NIL = lambda do |input|
295
- input = case input
296
- when "~" then nil
297
- when String then input.to_sym
298
- else input
299
- end
300
-
301
- validate(input, [Symbol, nil])
302
- end
303
- register_as STRBOL, STRBOL_OR_NIL
304
-
305
- # Returns a block that checks the input is true, false or nil.
306
- # String inputs are loaded as yaml first.
307
- #
308
- # boolean.class # => Proc
309
- # boolean.call(true) # => true
310
- # boolean.call(false) # => false
311
- # boolean.call(nil) # => nil
312
- #
313
- # boolean.call('true') # => true
314
- # boolean.call('yes') # => true
315
- # boolean.call('FALSE') # => false
316
- #
317
- # boolean.call(1) # => ValidationError
318
- # boolean.call("str") # => ValidationError
319
- #
320
- def boolean(); BOOLEAN; end
321
-
322
- # default attributes {:type => :boolean, :example => "true, yes"}
323
- BOOLEAN = yaml(true, false, nil)
324
- register :type => :boolean, :example => "true, yes", &BOOLEAN
325
-
326
- # Same as boolean.
327
- def switch(); SWITCH; end
328
-
329
- # default attributes {:type => :switch}
330
- SWITCH = yaml(true, false, nil)
331
- register :type => :switch, &SWITCH
332
-
333
- # Same as boolean.
334
- def flag(); FLAG; end
335
-
336
- # default attributes {:type => :flag}
337
- FLAG = yaml(true, false, nil)
338
- register :type => :flag, &FLAG
339
-
340
- # Returns a block that checks the input is an array.
341
- # String inputs are loaded as yaml first.
342
- #
343
- # array.class # => Proc
344
- # array.call([1,2,3]) # => [1,2,3]
345
- # array.call('[1, 2, 3]') # => [1,2,3]
346
- # array.call(nil) # => ValidationError
347
- # array.call('str') # => ValidationError
348
- #
349
- def array(); ARRAY; end
350
-
351
- # default attributes {:type => :array, :example => "[a, b, c]"}
352
- ARRAY = yaml(Array)
353
- register :type => :array, :example => "[a, b, c]", &ARRAY
354
-
355
- # Same as array but allows nil:
356
- #
357
- # array_or_nil.call('~') # => nil
358
- # array_or_nil.call(nil) # => nil
359
- def array_or_nil(); ARRAY_OR_NIL; end
360
-
361
- ARRAY_OR_NIL = yaml(Array, nil)
362
- register_as ARRAY, ARRAY_OR_NIL
363
-
364
- # Returns a block that checks the input is an array,
365
- # then yamlizes each string value of the array.
366
- #
367
- # list.class # => Proc
368
- # list.call([1,2,3]) # => [1,2,3]
369
- # list.call(['1', 'str']) # => [1,'str']
370
- # list.call('str') # => ValidationError
371
- # list.call(nil) # => ValidationError
372
- #
373
- def list(&validation)
374
- return LIST unless validation
375
-
376
- block = lambda do |input|
377
- args = validate(input, [Array]).collect do |arg|
378
- arg.kind_of?(String) ? YAML.load(arg) : arg
379
- end
380
- args.collect! {|arg| validation.call(arg) }
381
- args
382
- end
383
-
384
- register_as(LIST, block, :validation => attributes(validation))
385
- end
386
-
387
- list_block = lambda do |input|
388
- validate(input, [Array]).collect do |arg|
389
- arg.kind_of?(String) ? YAML.load(arg) : arg
390
- end
391
- end
392
-
393
- # default attributes {:type => :list, :split => ','}
394
- LIST = list_block
395
- register :type => :list, :split => ',', &LIST
396
-
397
- # Returns a block that checks the input is a hash.
398
- # String inputs are loaded as yaml first.
399
- #
400
- # hash.class # => Proc
401
- # hash.call({'key' => 'value'}) # => {'key' => 'value'}
402
- # hash.call('key: value') # => {'key' => 'value'}
403
- # hash.call(nil) # => ValidationError
404
- # hash.call('str') # => ValidationError
405
- #
406
- def hash(); HASH; end
407
-
408
- # default attributes {:type => :hash, :example => "{one: 1, two: 2}"}
409
- HASH = yaml(Hash)
410
- register :type => :hash, :example => "{one: 1, two: 2}", &HASH
411
-
412
- # Same as hash but allows nil:
413
- #
414
- # hash_or_nil.call('~') # => nil
415
- # hash_or_nil.call(nil) # => nil
416
- def hash_or_nil(); HASH_OR_NIL; end
417
-
418
- HASH_OR_NIL = yaml(Hash, nil)
419
- register_as HASH, HASH_OR_NIL
420
-
421
- # Returns a block that checks the input is an integer.
422
- # String inputs are loaded as yaml first.
423
- #
424
- # integer.class # => Proc
425
- # integer.call(1) # => 1
426
- # integer.call('1') # => 1
427
- # integer.call(1.1) # => ValidationError
428
- # integer.call(nil) # => ValidationError
429
- # integer.call('str') # => ValidationError
430
- #
431
- def integer(); INTEGER; end
432
-
433
- # default attributes {:type => :integer, :example => "2"}
434
- INTEGER = yaml(Integer)
435
- register :type => :integer, :example => "2", &INTEGER
436
-
437
- # Same as integer but allows nil:
438
- #
439
- # integer_or_nil.call('~') # => nil
440
- # integer_or_nil.call(nil) # => nil
441
- def integer_or_nil(); INTEGER_OR_NIL; end
442
-
443
- INTEGER_OR_NIL = yaml(Integer, nil)
444
- register_as INTEGER, INTEGER_OR_NIL
445
-
446
- # Returns a block that checks the input is a float.
447
- # String inputs are loaded as yaml first.
448
- #
449
- # float.class # => Proc
450
- # float.call(1.1) # => 1.1
451
- # float.call('1.1') # => 1.1
452
- # float.call('1.0e+6') # => 1e6
453
- # float.call(1) # => ValidationError
454
- # float.call(nil) # => ValidationError
455
- # float.call('str') # => ValidationError
456
- #
457
- def float(); FLOAT; end
458
-
459
- # default attributes {:type => :float, :example => "2.2, 2.0e+2"}
460
- FLOAT = yaml(Float)
461
- register :type => :float, :example => "2.2, 2.0e+2", &FLOAT
462
-
463
- # Same as float but allows nil:
464
- #
465
- # float_or_nil.call('~') # => nil
466
- # float_or_nil.call(nil) # => nil
467
- def float_or_nil(); FLOAT_OR_NIL; end
468
-
469
- FLOAT_OR_NIL = yaml(Float, nil)
470
- register_as FLOAT, FLOAT_OR_NIL
471
-
472
- # Returns a block that checks the input is a number.
473
- # String inputs are loaded as yaml first.
474
- #
475
- # numeric.class # => Proc
476
- # numeric.call(1.1) # => 1.1
477
- # numeric.call(1) # => 1
478
- # numeric.call(1e6) # => 1e6
479
- # numeric.call('1.1') # => 1.1
480
- # numeric.call('1.0e+6') # => 1e6
481
- # numeric.call(nil) # => ValidationError
482
- # numeric.call('str') # => ValidationError
483
- #
484
- def numeric(); NUMERIC; end
485
-
486
- # default attributes {:type => :numeric, :example => "2, 2.2, 2.0e+2"}
487
- NUMERIC = yaml(Numeric)
488
- register :type => :numeric, :example => "2, 2.2, 2.0e+2", &NUMERIC
489
-
490
- # Same as numeric but allows nil:
491
- #
492
- # numeric_or_nil.call('~') # => nil
493
- # numeric_or_nil.call(nil) # => nil
494
- def numeric_or_nil(); NUMERIC_OR_NIL; end
495
-
496
- NUMERIC_OR_NIL = yaml(Numeric, nil)
497
- register_as NUMERIC, NUMERIC_OR_NIL
498
-
499
- # Returns a block that checks the input is a regexp. String inputs are
500
- # loaded as yaml; if the result is not a regexp, it is converted to
501
- # a regexp using Regexp#new.
502
- #
503
- # regexp.class # => Proc
504
- # regexp.call(/regexp/) # => /regexp/
505
- # regexp.call('regexp') # => /regexp/
506
- #
507
- # yaml_str = '!ruby/regexp /regexp/'
508
- # regexp.call(yaml_str) # => /regexp/
509
- #
510
- # # use of ruby-specific flags can turn on/off
511
- # # features like case insensitive matching
512
- # regexp.call('(?i)regexp') # => /(?i)regexp/
513
- #
514
- def regexp(); REGEXP; end
515
- regexp_block = lambda do |input|
516
- if input.kind_of?(String)
517
- input = load_if_yaml(input, Regexp)
518
- end
519
-
520
- if input.kind_of?(String)
521
- input = Regexp.new(input)
522
- end
523
-
524
- validate(input, [Regexp])
525
- end
526
-
527
- # default attributes {:type => :regexp, :example => "/regexp/i"}
528
- REGEXP = regexp_block
529
- register :type => :regexp, :example => "/regexp/i", &REGEXP
530
-
531
- # Same as regexp but allows nil. Note the special behavior of the nil
532
- # string '~' -- rather than being converted to a regexp, it is processed
533
- # as nil to be consistent with the other [class]_or_nil methods.
534
- #
535
- # regexp_or_nil.call('~') # => nil
536
- # regexp_or_nil.call(nil) # => nil
537
- def regexp_or_nil(); REGEXP_OR_NIL; end
538
- regexp_or_nil_block = lambda do |input|
539
- case input
540
- when nil, '~' then nil
541
- else REGEXP[input]
542
- end
543
- end
544
-
545
- REGEXP_OR_NIL = regexp_or_nil_block
546
- register_as REGEXP, REGEXP_OR_NIL
547
-
548
- # Returns a block that checks the input is a range. String inputs are
549
- # loaded as yaml (a '!ruby/range' prefix is added before loading if
550
- # if it is not specified).
551
- #
552
- # range.class # => Proc
553
- # range.call(1..10) # => 1..10
554
- # range.call('1..10') # => 1..10
555
- # range.call('a..z') # => 'a'..'z'
556
- # range.call('-10...10') # => -10...10
557
- # range.call(nil) # => ValidationError
558
- # range.call('1.10') # => ValidationError
559
- # range.call('a....z') # => ValidationError
560
- #
561
- # yaml_str = "!ruby/range \nbegin: 1\nend: 10\nexcl: false\n"
562
- # range.call(yaml_str) # => 1..10
563
- #
564
- def range(); RANGE; end
565
- range_block = lambda do |input|
566
- if input.kind_of?(String)
567
- input = "!ruby/range #{input}" unless input =~ /\A\s*!ruby\/range\s/
568
- input = load_if_yaml(input, Range)
569
- end
570
-
571
- validate(input, [Range])
572
- end
573
-
574
- # default attributes {:type => :range, :example => "min..max"}
575
- RANGE = range_block
576
- register :type => :range, :example => "min..max", &RANGE
577
-
578
- # Same as range but allows nil:
579
- #
580
- # range_or_nil.call('~') # => nil
581
- # range_or_nil.call(nil) # => nil
582
- def range_or_nil(); RANGE_OR_NIL; end
583
- range_or_nil_block = lambda do |input|
584
- case input
585
- when nil, '~' then nil
586
- else RANGE[input]
587
- end
588
- end
589
-
590
- RANGE_OR_NIL = range_or_nil_block
591
- register_as RANGE, RANGE_OR_NIL
592
-
593
- # Returns a block that checks the input is a Time. String inputs are
594
- # loaded using Time.parse and then converted into times. Parsed times
595
- # are local unless specified otherwise.
596
- #
597
- # time.class # => Proc
598
- #
599
- # now = Time.now
600
- # time.call(now) # => now
601
- #
602
- # time.call('2008-08-08 20:00:00.00 +08:00').getutc.strftime('%Y/%m/%d %H:%M:%S')
603
- # # => '2008/08/08 12:00:00'
604
- #
605
- # time.call('2008-08-08').strftime('%Y/%m/%d %H:%M:%S')
606
- # # => '2008/08/08 00:00:00'
607
- #
608
- # time.call(1) # => ValidationError
609
- # time.call(nil) # => ValidationError
610
- #
611
- # Warning: Time.parse will parse a valid time (Time.now)
612
- # even when no time is specified:
613
- #
614
- # time.call('str').strftime('%Y/%m/%d %H:%M:%S')
615
- # # => Time.now.strftime('%Y/%m/%d %H:%M:%S')
616
- #
617
- def time()
618
- # adding this here is a compromise to lazy-load the parse
619
- # method (autoload doesn't work since Time already exists)
620
- require 'time' unless Time.respond_to?(:parse)
621
- TIME
622
- end
623
-
624
- time_block = lambda do |input|
625
- input = Time.parse(input) if input.kind_of?(String)
626
- validate(input, [Time])
627
- end
628
-
629
- # default attributes {:type => :time, :example => "2008-08-08 08:00:00"}
630
- TIME = time_block
631
- register :type => :time, :example => "2008-08-08 08:00:00", &TIME
632
-
633
- # Same as time but allows nil:
634
- #
635
- # time_or_nil.call('~') # => nil
636
- # time_or_nil.call(nil) # => nil
637
- def time_or_nil(); TIME_OR_NIL; end
638
-
639
- time_or_nil_block = lambda do |input|
640
- case input
641
- when nil, '~' then nil
642
- else TIME[input]
643
- end
644
- end
645
-
646
- TIME_OR_NIL = time_or_nil_block
647
- register_as TIME, TIME_OR_NIL
648
-
649
- # Returns a block that only allows the specified options. Select can take
650
- # a block that will validate the input individual value.
651
- #
652
- # s = select(1,2,3, &integer)
653
- # s.class # => Proc
654
- # s.call(1) # => 1
655
- # s.call('3') # => 3
656
- #
657
- # s.call(nil) # => ValidationError
658
- # s.call(0) # => ValidationError
659
- # s.call('4') # => ValidationError
660
- #
661
- # The select block is registered with these default attributes:
662
- #
663
- # {:type => :select, :options => options}
664
- #
665
- def select(*options, &validation)
666
- register(
667
- :type => :select,
668
- :options => options,
669
- :validation => attributes(validation)
670
- ) do |input|
671
- input = validation.call(input) if validation
672
- validate(input, options)
673
- end
674
- end
675
-
676
- # Returns a block that checks the input is an array, and that each member
677
- # of the array is included in options. A block may be provided to validate
678
- # the individual values.
679
- #
680
- # s = list_select(1,2,3, &integer)
681
- # s.class # => Proc
682
- # s.call([1]) # => [1]
683
- # s.call([1, '3']) # => [1, 3]
684
- # s.call([]) # => []
685
- #
686
- # s.call(1) # => ValidationError
687
- # s.call([nil]) # => ValidationError
688
- # s.call([0]) # => ValidationError
689
- # s.call(['4']) # => ValidationError
690
- #
691
- # The list_select block is registered with these default attributes:
692
- #
693
- # {:type => :list_select, :options => options, :split => ','}
694
- #
695
- def list_select(*options, &validation)
696
- register(
697
- :type => :list_select,
698
- :options => options,
699
- :split => ',',
700
- :validation => attributes(validation)
701
- ) do |input|
702
- args = validate(input, [Array])
703
- args.collect! {|arg| validation.call(arg) } if validation
704
- args.each {|arg| validate(arg, options) }
705
- end
706
- end
707
-
708
- # Returns a block validating the input is an IO, a string, or an integer.
709
- # String inputs are expected to be filepaths and integer inputs are expected
710
- # to be valid file descriptors, but io does not open an IO immediately.
711
- #
712
- # io.class # => Proc
713
- # io.call($stdout) # => $stdout
714
- # io.call('/path/to/file') # => '/path/to/file'
715
- # io.call(1) # => 1
716
- # io.call(nil) # => ValidationError
717
- #
718
- # An IO api can be specified to allow other objects to be validated. This
719
- # is useful for duck-typing an IO when a known subset of methods are needed.
720
- #
721
- # array_io = io(:<<)
722
- # array_io.call($stdout) # => $stdout
723
- # array_io.call([]) # => []
724
- # array_io.call(nil) # => ApiError
725
- #
726
- # Note that by default io configs will not be duplicated (duplicate IOs
727
- # flush separately, and this can result in disorder. see
728
- # http://gist.github.com/88808).
729
- def io(*api)
730
- if api.empty?
731
- IO_OR_STRING
732
- else
733
- block = lambda do |input|
734
- validate(input, [IO, String, Integer]) do
735
- validate_api(input, api)
736
- end
737
- end
738
-
739
- register_as IO_OR_STRING, block
740
- end
741
- end
742
-
743
- # default attributes {:type => :io, :dup => false, :example => "/path/to/file"}
744
- IO_OR_STRING = check(IO, String, Integer)
745
- register :type => :io, :dup => false, :example => "/path/to/file", &IO_OR_STRING
746
-
747
- # Same as io but allows nil:
748
- #
749
- # io_or_nil.call(nil) # => nil
750
- #
751
- def io_or_nil(*api)
752
- if api.empty?
753
- IO_STRING_OR_NIL
754
- else
755
- block = lambda do |input|
756
- validate(input, [IO, String, Integer, nil]) do
757
- validate_api(input, api)
758
- end
759
- end
760
-
761
- register_as IO_STRING_OR_NIL, block
762
- end
763
- end
764
-
765
- IO_STRING_OR_NIL = check(IO, String, Integer, nil)
766
- register_as IO_OR_STRING, IO_STRING_OR_NIL
767
- end
768
- end