configurable 0.7.0 → 1.0.0

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