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.
- data/Help/Command Line.rdoc +141 -0
- data/Help/Config Syntax.rdoc +229 -0
- data/Help/Config Types.rdoc +143 -0
- data/{History → History.rdoc} +9 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +144 -0
- data/lib/configurable.rb +7 -270
- data/lib/configurable/class_methods.rb +344 -367
- data/lib/configurable/config_classes.rb +3 -0
- data/lib/configurable/config_classes/list_config.rb +26 -0
- data/lib/configurable/config_classes/nest_config.rb +50 -0
- data/lib/configurable/config_classes/scalar_config.rb +91 -0
- data/lib/configurable/config_hash.rb +87 -112
- data/lib/configurable/config_types.rb +6 -0
- data/lib/configurable/config_types/boolean_type.rb +22 -0
- data/lib/configurable/config_types/float_type.rb +11 -0
- data/lib/configurable/config_types/integer_type.rb +11 -0
- data/lib/configurable/config_types/nest_type.rb +39 -0
- data/lib/configurable/config_types/object_type.rb +58 -0
- data/lib/configurable/config_types/string_type.rb +15 -0
- data/lib/configurable/conversions.rb +91 -0
- data/lib/configurable/module_methods.rb +0 -1
- data/lib/configurable/version.rb +1 -5
- metadata +73 -30
- data/README +0 -207
- data/lib/cdoc.rb +0 -413
- data/lib/cdoc/cdoc_html_generator.rb +0 -38
- data/lib/cdoc/cdoc_html_template.rb +0 -42
- data/lib/config_parser.rb +0 -563
- data/lib/config_parser/option.rb +0 -108
- data/lib/config_parser/switch.rb +0 -44
- data/lib/config_parser/utils.rb +0 -177
- data/lib/configurable/config.rb +0 -97
- data/lib/configurable/indifferent_access.rb +0 -35
- data/lib/configurable/nest_config.rb +0 -78
- data/lib/configurable/ordered_hash_patch.rb +0 -85
- data/lib/configurable/utils.rb +0 -186
- 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
|
data/lib/configurable/utils.rb
DELETED
@@ -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", ®EXP
|
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
|