user_input 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/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +61 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/user_input/option_parser.rb +213 -0
- data/lib/user_input/type_safe_hash.rb +134 -0
- data/lib/user_input.rb +246 -0
- data/spec/option_parser_spec.rb +158 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/type_safe_hash_spec.rb +123 -0
- data/spec/user_input_spec.rb +284 -0
- metadata +81 -0
data/lib/user_input.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
# Adds the method from_user_input to various built-in classes as both a class and instance method.
|
2
|
+
# On a class, the method returns a validated instance of that class if it can be coerced into one
|
3
|
+
# (or nil if not). On an instance, it validates more strictly against the value of that instance.
|
4
|
+
|
5
|
+
class String
|
6
|
+
# All strings validate as strings
|
7
|
+
def String.from_user_input(value)
|
8
|
+
return value.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
# instance form does a straight comparison of the string with self.
|
12
|
+
def from_user_input(value)
|
13
|
+
if (self == value.to_s)
|
14
|
+
return value.to_s
|
15
|
+
else
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Boolean
|
22
|
+
# Must be a string that is either "true" or "false"
|
23
|
+
def Boolean.from_user_input(value)
|
24
|
+
if (value.kind_of?(TrueClass) || value.kind_of?(FalseClass) ||
|
25
|
+
/(true|false|on|off|y|n|enabled|disabled)/ =~ value.to_s)
|
26
|
+
return !!(/(true|on|y|enabled)/ =~ value.to_s)
|
27
|
+
else
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class TrueClass
|
34
|
+
# Either a 'positive' string or an actual instance of true.
|
35
|
+
def from_user_input(value)
|
36
|
+
if (value.kind_of?(TrueClass) || /(true|on|y|enabled)/ =~ value.to_s)
|
37
|
+
return true
|
38
|
+
else
|
39
|
+
return nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class FalseClass
|
45
|
+
# Either a 'negative' string or an actual instance of false.
|
46
|
+
def from_user_input(value)
|
47
|
+
if (value.kind_of?(FalseClass) || /(false|off|n|disabled)/ =~ value.to_s)
|
48
|
+
return false
|
49
|
+
else
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Date
|
56
|
+
# Check for a string of the regex /[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{4}/
|
57
|
+
# and make a Date object out of it
|
58
|
+
def Date.from_user_input(value)
|
59
|
+
if (value.kind_of?(Date))
|
60
|
+
return value
|
61
|
+
end
|
62
|
+
begin
|
63
|
+
return Date.parse(value.to_s)
|
64
|
+
rescue ArgumentError
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Integer
|
71
|
+
# All characters must be numbers, except for the first which may be -
|
72
|
+
def Integer.from_user_input(value)
|
73
|
+
if (value.kind_of?(Integer) || /^\s*-?[0-9]+\s*$/ =~ value.to_s)
|
74
|
+
return value.to_i
|
75
|
+
else
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# instance form does a straight comparison of value.to_i with self.
|
81
|
+
def from_user_input(value)
|
82
|
+
if (!value.kind_of?(Float) && self == value.to_i)
|
83
|
+
return value.to_i
|
84
|
+
else
|
85
|
+
return nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Float
|
91
|
+
# All characters must be numbers, except there can be up to one decimal
|
92
|
+
# and a negative sign at the front
|
93
|
+
def Float.from_user_input(value)
|
94
|
+
if (value.kind_of?(Float) || /^\s*-?[0-9]*(\.[0-9]+)?\s*$/ =~ value.to_s)
|
95
|
+
return value.to_f
|
96
|
+
else
|
97
|
+
return nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# instance form does a straight comparison of value.to_f with self.
|
102
|
+
def from_user_input(value)
|
103
|
+
if (self == value.to_f)
|
104
|
+
return value.to_f
|
105
|
+
else
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class Regexp
|
112
|
+
# Returns the string if value matches self's regex, returns nil otherwise.
|
113
|
+
def from_user_input(value)
|
114
|
+
if (value.kind_of?(String) && matches = self.match(value))
|
115
|
+
return matches
|
116
|
+
else
|
117
|
+
return nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Range
|
123
|
+
def from_user_input(value)
|
124
|
+
value = self.first.class.from_user_input(value)
|
125
|
+
|
126
|
+
if(!value || !(self === value))
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
|
130
|
+
return value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class Array
|
135
|
+
def Array.from_user_input(value)
|
136
|
+
return [*value]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Checks each element of the value array to ensure that they match against
|
140
|
+
# the first element of self
|
141
|
+
def from_user_input(value)
|
142
|
+
value = [*value]
|
143
|
+
# eliminate the obvious
|
144
|
+
if (self.length != 1)
|
145
|
+
raise ArgumentError, "Must supply only one element to an array you're calling from_user_input on."
|
146
|
+
end
|
147
|
+
innertype = self[0]
|
148
|
+
# now check whether the inner elements of the array match
|
149
|
+
output = value.collect {|innervalue|
|
150
|
+
# if innertype is not an array, but value is, we need to flatten it
|
151
|
+
if (!innertype.kind_of?(Array) && innervalue.kind_of?(Array))
|
152
|
+
innervalue = innervalue[0]
|
153
|
+
end
|
154
|
+
innertype.from_user_input(innervalue); # returns
|
155
|
+
}.compact()
|
156
|
+
|
157
|
+
if (output.length > 0)
|
158
|
+
return output
|
159
|
+
else
|
160
|
+
return nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class Hash
|
166
|
+
def from_user_input(value)
|
167
|
+
if (self.length != 1)
|
168
|
+
raise ArgumentError, "Must supply only one element to a hash you're calling from_user_input on."
|
169
|
+
end
|
170
|
+
if (!value.kind_of?(Hash))
|
171
|
+
return nil
|
172
|
+
end
|
173
|
+
keytype = nil
|
174
|
+
valtype = nil
|
175
|
+
self.each {|k,v| keytype = k; valtype = v}
|
176
|
+
output = {}
|
177
|
+
value.each {|k, v|
|
178
|
+
if (!(k = keytype.from_user_input(k)).nil? && !(v = valtype.from_user_input(v)).nil?)
|
179
|
+
output[k] = v
|
180
|
+
end
|
181
|
+
}
|
182
|
+
if (output.length > 0)
|
183
|
+
return output
|
184
|
+
else
|
185
|
+
return nil
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class Set
|
191
|
+
def from_user_input(value)
|
192
|
+
each {|i|
|
193
|
+
val = i.from_user_input(value)
|
194
|
+
return val if val
|
195
|
+
}
|
196
|
+
return nil
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
class Symbol
|
201
|
+
def Symbol.from_user_input(value)
|
202
|
+
raise ArgumentError, "You should never arbitrarily turn user input into symbols. It can cause leaks that could lead to DoS."
|
203
|
+
end
|
204
|
+
|
205
|
+
# instance form does a straight comparison of value.to_sym with self.
|
206
|
+
def from_user_input(value)
|
207
|
+
if (self.to_s == value.to_s)
|
208
|
+
return self
|
209
|
+
else
|
210
|
+
return nil
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class IPAddr
|
216
|
+
def IPAddr.from_user_input(value)
|
217
|
+
if (value.kind_of?(self))
|
218
|
+
return value
|
219
|
+
end
|
220
|
+
begin
|
221
|
+
return self.new(value.to_s)
|
222
|
+
rescue ArgumentError
|
223
|
+
return nil
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
class Class
|
229
|
+
def from_user_input(value)
|
230
|
+
if (value.kind_of?(Class) && value <= self)
|
231
|
+
return value
|
232
|
+
elsif (value.kind_of?(self))
|
233
|
+
return value
|
234
|
+
end
|
235
|
+
return nil
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class Object
|
240
|
+
def from_user_input(value)
|
241
|
+
if (value == self)
|
242
|
+
return value
|
243
|
+
end
|
244
|
+
return nil
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'lib/user_input/option_parser'
|
3
|
+
|
4
|
+
describe UserInput::OptionParser do
|
5
|
+
IOptionParser = UserInput::OptionParser
|
6
|
+
|
7
|
+
it "Should optionally take an argument for program prefix, setting it to $0 if none set" do
|
8
|
+
IOptionParser.new().program_prefix.should == $0
|
9
|
+
IOptionParser.new("blah").program_prefix.should == "blah"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "Should let you set the program prefix after initialization" do
|
13
|
+
opt = IOptionParser.new
|
14
|
+
opt.program_prefix.should == $0
|
15
|
+
opt.program_prefix = "blah"
|
16
|
+
opt.program_prefix.should == "blah"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "Should generate a default banner if none specified" do
|
20
|
+
IOptionParser.new().banner.should be_kind_of(String)
|
21
|
+
end
|
22
|
+
it "Should let you set the banner" do
|
23
|
+
opt = IOptionParser.new
|
24
|
+
opt.banner = "blah"
|
25
|
+
opt.banner.should == "blah"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "Should yield the object for use if a block is given to the constructor" do
|
29
|
+
IOptionParser.new("boom") {|p|
|
30
|
+
p.should be_kind_of(IOptionParser)
|
31
|
+
p.program_prefix.should == "boom"
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
it "Should not allow multicharacter short options or single character long options" do
|
36
|
+
IOptionParser.new {|p|
|
37
|
+
proc { p.flag "boom", "boom", "what?" }.should raise_error(ArgumentError)
|
38
|
+
proc { p.flag "a", "a", "what?" }.should raise_error(ArgumentError)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
before :each do
|
43
|
+
@opt = IOptionParser.new("testing")
|
44
|
+
@opt.flag "a", "abba", "stuff goes here"
|
45
|
+
@opt.flag "b", "boom", "this one goes boom" do raise "BOOM" end
|
46
|
+
@opt.argument "c", "cool", "this one is awesome", "whatever"
|
47
|
+
@opt.gap
|
48
|
+
@opt.argument "d", "dumb", "this one is dumb. It wants nothing but an integer.", 5, Integer
|
49
|
+
@opt.argument "e", "everything", "this one's really clever, it's always 'everything'", "what?" do "everything" end
|
50
|
+
@opt.argument "f", "fugged-aboudit", "this one has a hyphen, which makes it scary", "stuff"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "Should have defined the right methods" do
|
54
|
+
[:abba?, :boom?, :cool, :dumb, :everything, :fugged_aboudit].each {|i|
|
55
|
+
@opt.respond_to?(i).should be_true
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
it "Should set the correct defaults" do
|
60
|
+
@opt.abba?.should be_false
|
61
|
+
@opt.boom?.should be_false
|
62
|
+
@opt.cool.should == "whatever"
|
63
|
+
@opt.dumb.should == 5
|
64
|
+
@opt.everything.should == "what?"
|
65
|
+
@opt.fugged_aboudit.should == "stuff"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "Should return itself from both parse and parse!" do
|
69
|
+
@opt.parse(["-a"]).should == @opt
|
70
|
+
@opt.parse!(["-a"]).should == @opt
|
71
|
+
end
|
72
|
+
|
73
|
+
it "Should parse a simple short flag" do
|
74
|
+
@opt.parse(["-a"])
|
75
|
+
@opt.abba?.should be_true
|
76
|
+
end
|
77
|
+
|
78
|
+
it "Should parse a simple long flag" do
|
79
|
+
@opt.parse(["--abba"])
|
80
|
+
@opt.abba?.should be_true
|
81
|
+
end
|
82
|
+
|
83
|
+
it "Should raise an error if we try to set the exploding flag" do
|
84
|
+
proc { @opt.parse(["-b"]) }.should raise_error("BOOM")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "Should raise if an argument isn't supplied to a normal argument" do
|
88
|
+
proc { @opt.parse(["-c"]) }.should raise_error(ArgumentError)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "Should parse a simple argument with properly specified" do
|
92
|
+
@opt.parse(["-c", "stuff"])
|
93
|
+
@opt.cool.should == "stuff"
|
94
|
+
end
|
95
|
+
|
96
|
+
it "Should parse a simple argument in its long form properly specified" do
|
97
|
+
@opt.parse(["--cool", "stuff"])
|
98
|
+
@opt.cool.should == "stuff"
|
99
|
+
end
|
100
|
+
|
101
|
+
it "Should deal with a hyphen in the command line argument" do
|
102
|
+
@opt.parse(["--fugged-aboudit", "boom"])
|
103
|
+
@opt.fugged_aboudit.should == "boom"
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should parse a flag and an argument separately" do
|
107
|
+
@opt.parse(["-a", "-c", "stuff"])
|
108
|
+
@opt.abba?.should be_true
|
109
|
+
@opt.cool.should == "stuff"
|
110
|
+
end
|
111
|
+
|
112
|
+
it "Should validate input using from_user_input" do
|
113
|
+
proc { @opt.parse(["-d", "whatever"]) }.should raise_error(ArgumentError)
|
114
|
+
@opt.dumb.should == 5
|
115
|
+
@opt.parse(["-d", "99"])
|
116
|
+
@opt.dumb.should == 99
|
117
|
+
end
|
118
|
+
|
119
|
+
it "Should validate input using a proc object" do
|
120
|
+
@opt.parse(["-e", "stufffff"])
|
121
|
+
@opt.everything.should == "everything"
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should parse correctly if you specify multiple arguments in a group" do
|
125
|
+
@opt.parse(["-acd", "what", "1"])
|
126
|
+
@opt.abba?.should be_true
|
127
|
+
@opt.cool.should == "what"
|
128
|
+
@opt.dumb.should == 1
|
129
|
+
end
|
130
|
+
|
131
|
+
it "Should raise an error if you supply a flag or argument it doesn't understand" do
|
132
|
+
proc { @opt.parse(["-z"]) }.should raise_error(ArgumentError)
|
133
|
+
proc { @opt.parse(["--zoom"]) }.should raise_error(ArgumentError)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should parse destructively if you use parse!" do
|
137
|
+
arr = ["-a"]
|
138
|
+
@opt.parse!(arr)
|
139
|
+
arr.should == []
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should stop parsing on finding a non-flag word unexpectedly and return the remainder" do
|
143
|
+
arr = ["-a", "boom", "whatever"]
|
144
|
+
@opt.parse!(arr)
|
145
|
+
arr.should == ["boom", "whatever"]
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should stop parsing on finding --, but should still consume it" do
|
149
|
+
arr = ["-a", "--", "whatever"]
|
150
|
+
@opt.parse!(arr)
|
151
|
+
arr.should == ["whatever"]
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should return a string from to_s" do
|
155
|
+
# Possibly this spec should include an example to compare against, but that seems too rigid.
|
156
|
+
@opt.to_s.should be_kind_of(String)
|
157
|
+
end
|
158
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'lib/user_input/type_safe_hash'
|
3
|
+
|
4
|
+
describe UserInput::TypeSafeHash do
|
5
|
+
TypeSafeHash = UserInput::TypeSafeHash
|
6
|
+
|
7
|
+
describe "initializer" do
|
8
|
+
it "should take a normal hash as an argument" do
|
9
|
+
TypeSafeHash.new(:a => :b).should == TypeSafeHash.new(:a => :b)
|
10
|
+
end
|
11
|
+
it "should take another type safe hash as an argument" do
|
12
|
+
TypeSafeHash.new(TypeSafeHash.new(:a => :b)).should == TypeSafeHash.new(:a => :b)
|
13
|
+
end
|
14
|
+
it "should raise an error for anything else" do
|
15
|
+
proc {
|
16
|
+
TypeSafeHash.new("boom")
|
17
|
+
}.should raise_error(ArgumentError)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
before :each do
|
22
|
+
@hash = TypeSafeHash.new("blah" => "blorp", 1 => 2, 3 => [1, 2, 3])
|
23
|
+
@str_hash = TypeSafeHash.new("blah" => "waht", "woop" => "wonk", "woo" => "fluh")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should properly implement to_hash to return the original hash object" do
|
27
|
+
@hash.to_hash.should be_kind_of(Hash)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should compare to another TypeSafeHash as equal if they have the same real hash" do
|
31
|
+
(@hash == TypeSafeHash.new(@hash.to_hash)).should be_true
|
32
|
+
(@hash == TypeSafeHash.new()).should be_false
|
33
|
+
end
|
34
|
+
it "should compare to any other object as not equal" do
|
35
|
+
(@hash == Hash.new).should be_false
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "fetch" do
|
39
|
+
it "should validate as user input any requests for keys" do
|
40
|
+
@hash.fetch("blah", String).should == "blorp"
|
41
|
+
@hash.fetch("blah", /orp/)[0].should == "blorp".match(/orp/)[0]
|
42
|
+
@hash.fetch("blah", Integer).should be_nil
|
43
|
+
|
44
|
+
@hash.fetch(1, Integer).should == 2
|
45
|
+
@hash.fetch(1, 2).should == 2
|
46
|
+
@hash.fetch(1, 3).should be_nil
|
47
|
+
|
48
|
+
@hash.fetch(3, [Integer]).should == [1,2,3]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should flatten an array value to a single value if the requested type is not an array" do
|
52
|
+
@hash.fetch(3, Integer).should == 1
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should take a default value for failed matching" do
|
56
|
+
@hash.fetch("blah", /woople/, "woople").should == "woople"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should be aliased to the [] method" do
|
60
|
+
@hash["blah", String].should == "blorp"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should properly implement each_key" do
|
65
|
+
keys = []
|
66
|
+
@hash.each_key {|x| keys.push(x)}
|
67
|
+
keys == @hash.to_hash.keys
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should properly implement each_pair" do
|
71
|
+
out = {}
|
72
|
+
@str_hash.each_pair(String) {|key, val| out[key]=val}
|
73
|
+
out.should == @str_hash.to_hash
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should properly implement each_match, returning only keys that match the regex" do
|
77
|
+
out = []
|
78
|
+
@str_hash.each_match(/woo/, String) {|match, val| out.push([match[0],val]) }
|
79
|
+
out.sort.should == [["woo", "fluh"], ["woo", "wonk"]].sort
|
80
|
+
end
|
81
|
+
|
82
|
+
it "empty? should return true for an empty hash and false for one with values" do
|
83
|
+
@hash.empty?.should == false
|
84
|
+
TypeSafeHash.new.empty?.should == true
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should implement has_key? and all of its aliases (include?, key?, member?)" do
|
88
|
+
@hash.has_key?(3).should be_true
|
89
|
+
@hash.has_key?(4).should be_false
|
90
|
+
@hash.include?(3).should be_true
|
91
|
+
@hash.include?(4).should be_false
|
92
|
+
@hash.key?(3).should be_true
|
93
|
+
@hash.key?(4).should be_false
|
94
|
+
@hash.member?(3).should be_true
|
95
|
+
@hash.member?(4).should be_false
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should implement inspect as an alias to its real hash's inspect" do
|
99
|
+
@hash.inspect.should == @hash.to_hash.inspect
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should implement keys as returning the real hash's keys, both sorted and unsorted" do
|
103
|
+
@str_hash.keys.should == @str_hash.to_hash.keys
|
104
|
+
@str_hash.keys(true).should == @str_hash.to_hash.keys.sort
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should implement values as returning the values from the real hash" do
|
108
|
+
@hash.values.should == @hash.to_hash.values
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should return the number of items in the hash from length" do
|
112
|
+
@hash.length.should == @hash.to_hash.length
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should implement to_s as returning the internal hash's string representation" do
|
116
|
+
@hash.to_s.should == @hash.to_hash.to_s
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should implement TypeSafeHash.from_user_input as accepting only a hash" do
|
120
|
+
TypeSafeHash.from_user_input({}).should == TypeSafeHash.new({})
|
121
|
+
TypeSafeHash.from_user_input("Blah").should be_nil
|
122
|
+
end
|
123
|
+
end
|