oval 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.fixtures.yml +3 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.scripts/build-module.sh +25 -0
- data/.travis.yml +22 -0
- data/.yardopts +4 -0
- data/CHANGELOG +2 -0
- data/Gemfile +17 -0
- data/LICENSE +13 -0
- data/Modulefile +8 -0
- data/README.md +280 -0
- data/README_DEVEL.md +27 -0
- data/Rakefile +25 -0
- data/lib/oval.rb +18 -0
- data/lib/oval/anything.rb +12 -0
- data/lib/oval/array_item.rb +25 -0
- data/lib/oval/base.rb +58 -0
- data/lib/oval/class_decl_base.rb +27 -0
- data/lib/oval/collection.rb +125 -0
- data/lib/oval/hash_item.rb +41 -0
- data/lib/oval/instance_of.rb +11 -0
- data/lib/oval/kind_of.rb +11 -0
- data/lib/oval/one_of.rb +36 -0
- data/lib/oval/options.rb +61 -0
- data/lib/oval/subclass_of.rb +11 -0
- data/oval.gemspec +19 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/unit/oval/anything_spec.rb +38 -0
- data/spec/unit/oval/array_item_spec.rb +60 -0
- data/spec/unit/oval/base_spec.rb +184 -0
- data/spec/unit/oval/class_decl_base_spec.rb +73 -0
- data/spec/unit/oval/collection_spec.rb +267 -0
- data/spec/unit/oval/hash_item_spec.rb +146 -0
- data/spec/unit/oval/instance_of_spec.rb +44 -0
- data/spec/unit/oval/kind_of_spec.rb +45 -0
- data/spec/unit/oval/one_of_spec.rb +62 -0
- data/spec/unit/oval/options_spec.rb +215 -0
- data/spec/unit/oval/subclass_of_spec.rb +34 -0
- data/spec/unit/oval_spec.rb +75 -0
- metadata +123 -0
data/lib/oval.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Oval
|
2
|
+
require 'oval/anything'
|
3
|
+
require 'oval/collection'
|
4
|
+
require 'oval/instance_of'
|
5
|
+
require 'oval/kind_of'
|
6
|
+
require 'oval/one_of'
|
7
|
+
require 'oval/options'
|
8
|
+
require 'oval/subclass_of'
|
9
|
+
|
10
|
+
|
11
|
+
def ov_anything; Oval::Anything; end
|
12
|
+
def ov_collection; Oval::Collection; end
|
13
|
+
def ov_instance_of; Oval::InstanceOf; end
|
14
|
+
def ov_kind_of; Oval::KindOf; end
|
15
|
+
def ov_one_of; Oval::OneOf; end
|
16
|
+
def ov_options; Oval::Options; end
|
17
|
+
def ov_subclass_of; Oval::SubclassOf; end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'oval/base'
|
2
|
+
|
3
|
+
# Describe value that is arbitrary.
|
4
|
+
class Oval::Anything < Oval::Base
|
5
|
+
class << self
|
6
|
+
def instance; @instance ||= new; end
|
7
|
+
def []; instance; end
|
8
|
+
private :new
|
9
|
+
end
|
10
|
+
def validate(x,subject=nil); end
|
11
|
+
def initialize(); end
|
12
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'oval/base'
|
2
|
+
|
3
|
+
class Oval::ArrayItem < Oval::Base
|
4
|
+
|
5
|
+
def validate(item, i, subject = nil)
|
6
|
+
item_subject = subject.nil? ? nil : "#{subject}[#{i}]"
|
7
|
+
self.class.ensure_match(item,item_decl,item_subject)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.[](item_decl)
|
11
|
+
new(item_decl)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(item_decl)
|
15
|
+
self.item_decl = item_decl
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :item_decl
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def item_decl=(decl)
|
23
|
+
@item_decl = decl
|
24
|
+
end
|
25
|
+
end
|
data/lib/oval/base.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Oval; end
|
2
|
+
|
3
|
+
class Oval::DeclError < ArgumentError; end
|
4
|
+
class Oval::ValueError < ArgumentError; end
|
5
|
+
|
6
|
+
class Oval::Base
|
7
|
+
|
8
|
+
def self.ensure_equal(thing, decl, subject = nil)
|
9
|
+
unless (decl == Oval::Anything) or (thing == decl)
|
10
|
+
raise Oval::ValueError,
|
11
|
+
"Invalid value #{thing.inspect}#{for_subject(subject)}. Should be " +
|
12
|
+
"equal #{decl.inspect}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.ensure_match(thing, decl, subject = nil)
|
17
|
+
if decl.is_a? Oval::Base
|
18
|
+
decl.validate(thing,subject)
|
19
|
+
else
|
20
|
+
# "terminal symbol"
|
21
|
+
ensure_equal(thing, decl, subject)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.[](*args)#,subject = default_subject)
|
26
|
+
return new(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate(value, subject = nil)
|
30
|
+
raise NotImplementedError, "This method should be overwritten by a subclass"
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def self.for_subject(subject)
|
39
|
+
subject ? " for #{subject}" : ""
|
40
|
+
end
|
41
|
+
|
42
|
+
def for_subject(subject)
|
43
|
+
self.class.for_subject(subject)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.enumerate(items,op)
|
47
|
+
return 'none' if items.empty?
|
48
|
+
output = items[0..-2].map{|k| k.inspect}.join(', ')
|
49
|
+
output.empty? ? items[0].inspect : [output, items[-1].inspect].join(" #{op} ")
|
50
|
+
end
|
51
|
+
|
52
|
+
def enumerate(items,op)
|
53
|
+
self.class.enumerate(items,op)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
require 'oval/anything'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'oval/base'
|
2
|
+
class Oval::ClassDeclBase < Oval::Base
|
3
|
+
attr_reader :klass
|
4
|
+
def self.[](klass)
|
5
|
+
new(klass)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(klass)
|
9
|
+
self.klass = klass
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
def klass=(k)
|
14
|
+
validate_class(k)
|
15
|
+
@klass = k
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.myname; 'ClassDeclBase'; end
|
19
|
+
|
20
|
+
def validate_class(klass)
|
21
|
+
unless klass.is_a?(Class)
|
22
|
+
subject = self.class.name.sub(/^.*::/,'')
|
23
|
+
raise Oval::DeclError,
|
24
|
+
"Invalid class #{klass.inspect}#{for_subject(subject)}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'oval/base'
|
2
|
+
require 'oval/anything'
|
3
|
+
require 'oval/subclass_of'
|
4
|
+
require 'oval/array_item'
|
5
|
+
require 'oval/hash_item'
|
6
|
+
|
7
|
+
# Declare container (e.g. array or hash).
|
8
|
+
#
|
9
|
+
# **Example 1**: Desclare array of arbitrary elements:
|
10
|
+
#
|
11
|
+
# ```ruby
|
12
|
+
# Collection[Array]
|
13
|
+
# ```
|
14
|
+
#
|
15
|
+
# or
|
16
|
+
#
|
17
|
+
# ```ruby
|
18
|
+
# Collection[Array,Anything[]]
|
19
|
+
# ```
|
20
|
+
#
|
21
|
+
# or
|
22
|
+
#
|
23
|
+
# ```ruby
|
24
|
+
# Collection[InstanceOf[Array],Anything[]]
|
25
|
+
# ```
|
26
|
+
#
|
27
|
+
# **Example 2**: Declare Array of Strings:
|
28
|
+
#
|
29
|
+
# ```ruby
|
30
|
+
# Collection[Array,InstanceOf[String]]
|
31
|
+
# ```
|
32
|
+
#
|
33
|
+
# **Example 3**: Desclare any Hash:
|
34
|
+
#
|
35
|
+
# ```ruby
|
36
|
+
# Collection[Hash]
|
37
|
+
# ```
|
38
|
+
#
|
39
|
+
# **Example 4**: Desclare Hash with Symbol keys and Fixnum values
|
40
|
+
#
|
41
|
+
# ```ruby
|
42
|
+
# Collection[Hash,{Symbol => Fixnum}]
|
43
|
+
# ```
|
44
|
+
#
|
45
|
+
class Oval::Collection < Oval::Base
|
46
|
+
|
47
|
+
def validate(collection, subject = nil)
|
48
|
+
class_subject = subject.nil? ? nil : "#{subject}.class"
|
49
|
+
self.class.ensure_match(collection.class, class_decl, class_subject)
|
50
|
+
i = 0
|
51
|
+
collection.each { |item| item_validator.validate(item, i, subject); i+= 1}
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.[](class_decl,item_decl = Oval::Anything[])
|
55
|
+
new(class_decl,item_decl)
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(class_decl, item_decl = Oval::Anything[])
|
59
|
+
self.class_decl = class_decl
|
60
|
+
self.item_decl = item_decl
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.klass(class_decl)
|
64
|
+
class_decl.is_a?(Oval::SubclassOf) ? class_decl.klass : class_decl
|
65
|
+
end
|
66
|
+
|
67
|
+
def klass
|
68
|
+
self.class.klass(class_decl)
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_reader :class_decl
|
72
|
+
attr_reader :item_decl
|
73
|
+
attr_reader :class_validator
|
74
|
+
attr_reader :item_validator
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def class_decl=(decl)
|
79
|
+
self.class.validate_class_decl(decl)
|
80
|
+
@class_decl = decl
|
81
|
+
end
|
82
|
+
|
83
|
+
def item_decl=(decl)
|
84
|
+
bind_item_validator(decl)
|
85
|
+
@item_decl = decl
|
86
|
+
end
|
87
|
+
|
88
|
+
def bind_item_validator(item_decl)
|
89
|
+
@item_validator = select_item_validator[item_decl]
|
90
|
+
end
|
91
|
+
|
92
|
+
def select_class_validator
|
93
|
+
if klass.is_a?(Class) and klass <= Hash
|
94
|
+
Oval::HashClass
|
95
|
+
elsif klass.is_a?(Class) and klass <= Array
|
96
|
+
Oval::ArrayClass
|
97
|
+
else
|
98
|
+
# well, we also may have klass that is not a class, but I'm too lazy to
|
99
|
+
# handle all possible exceptions,
|
100
|
+
raise RuntimeError, "Invalid class #{klass.inspect} assigned to klass. " +
|
101
|
+
"It seems like we have a bug in #{self.class.name}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def select_item_validator
|
106
|
+
if klass.is_a?(Class) and klass <= Hash
|
107
|
+
Oval::HashItem
|
108
|
+
elsif klass.is_a?(Class) and klass <= Array
|
109
|
+
Oval::ArrayItem
|
110
|
+
else
|
111
|
+
# well, we also may have klass that is not a class, but I'm too lazy to
|
112
|
+
# handle all possible exceptions,
|
113
|
+
raise RuntimeError, "Invalid class #{klass.inspect} assigned to klass. " +
|
114
|
+
"It seems like we have a bug in #{self.class.name}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.validate_class_decl(decl)
|
119
|
+
klass = self.klass(decl)
|
120
|
+
unless klass.is_a?(Class) and ((klass<=Hash) or (klass<=Array))
|
121
|
+
raise Oval::DeclError, "Invalid collection class declarator " +
|
122
|
+
"#{decl.inspect}. Should be a (subclass of) Hash or Array"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'oval/base'
|
2
|
+
require 'oval/anything'
|
3
|
+
|
4
|
+
class Oval::HashItem < Oval::Base
|
5
|
+
|
6
|
+
def validate(item, i, subject = nil)
|
7
|
+
key_subject = subject.nil? ? nil: "#{subject} key"
|
8
|
+
val_subject = subject.nil? ? nil: "#{subject}[#{item[0].inspect}]"
|
9
|
+
self.class.ensure_match(item[0],key_decl,key_subject)
|
10
|
+
self.class.ensure_match(item[1],val_decl,val_subject)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.[](item_decl)
|
14
|
+
new(item_decl)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(item_decl)
|
18
|
+
self.item_decl = item_decl
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :key_decl
|
22
|
+
attr_reader :val_decl
|
23
|
+
|
24
|
+
def self.validate_item_decl(decl)
|
25
|
+
unless (decl.is_a?(Hash) and decl.size == 1)
|
26
|
+
raise Oval::DeclError, "Invalid item declaration #{decl.inspect}. " +
|
27
|
+
"Should be one-element Hash of type { key_decl => value_decl }"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def item_decl=(decl)
|
34
|
+
if decl.is_a?(Oval::Anything) or (decl.is_a?(Class) and decl == Oval::Anything)
|
35
|
+
@key_decl, @val_decl = [Oval::Anything[], Oval::Anything[]]
|
36
|
+
else
|
37
|
+
self.class.validate_item_decl(decl)
|
38
|
+
@key_decl, @val_decl = decl.first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'oval/class_decl_base'
|
2
|
+
|
3
|
+
class Oval::InstanceOf < Oval::ClassDeclBase
|
4
|
+
def validate(object, subject = nil)
|
5
|
+
unless object.instance_of?(klass)
|
6
|
+
raise Oval::ValueError,
|
7
|
+
"Invalid object #{object.inspect} of type #{object.class.name}" +
|
8
|
+
"#{for_subject(subject)}. Should be an instance of #{klass.name}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/oval/kind_of.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'oval/class_decl_base'
|
2
|
+
|
3
|
+
class Oval::KindOf < Oval::ClassDeclBase
|
4
|
+
def validate(object, subject = nil)
|
5
|
+
unless object.kind_of?(klass)
|
6
|
+
raise Oval::ValueError,
|
7
|
+
"Invalid object #{object.inspect} of type #{object.class.name}" +
|
8
|
+
"#{for_subject(subject)}. Should be a kind of #{klass.name}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/oval/one_of.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'oval/base'
|
2
|
+
|
3
|
+
# Describe a value that must match one of the shapes.
|
4
|
+
#
|
5
|
+
# **Example 1**: Value must be an Array or nil
|
6
|
+
#
|
7
|
+
# ```ruby
|
8
|
+
# OneOf[Container[Array], nil]
|
9
|
+
# ```
|
10
|
+
class Oval::OneOf < Oval::Base
|
11
|
+
def validate(thing, subject = nil)
|
12
|
+
ok = false
|
13
|
+
self.decls.each do |decl|
|
14
|
+
begin
|
15
|
+
self.class.ensure_match(thing, decl)
|
16
|
+
ok = true
|
17
|
+
break
|
18
|
+
rescue Oval::ValueError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
unless ok
|
22
|
+
raise Oval::ValueError, "Invalid value #{thing.inspect}#{for_subject(subject)}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(*decls)
|
27
|
+
self.decls = decls
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :decls
|
31
|
+
|
32
|
+
private
|
33
|
+
def decls=(decls)
|
34
|
+
@decls = decls
|
35
|
+
end
|
36
|
+
end
|
data/lib/oval/options.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'oval/base'
|
2
|
+
|
3
|
+
class Oval::Options < Oval::Base
|
4
|
+
def validate(options, subject = nil)
|
5
|
+
unless options.is_a?(Hash)
|
6
|
+
raise Oval::ValueError,
|
7
|
+
"Invalid options #{options.inspect} of type #{options.class.name}. " +
|
8
|
+
"Should be a Hash"
|
9
|
+
end
|
10
|
+
options.each {|name, value| validate_option(name, value, subject) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(decl)
|
14
|
+
self.decl = decl
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :decl
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def decl=(decl)
|
22
|
+
self.class.validate_decl(decl)
|
23
|
+
@decl = decl
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_option(name, value, subject = nil)
|
27
|
+
validate_option_name(name, subject)
|
28
|
+
validate_option_value(value, name, subject)
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate_option_name(name, subject = nil)
|
32
|
+
unless decl.include?(name)
|
33
|
+
allowed = enumerate(decl.keys.sort{|x,y| x.to_s <=> y.to_s}, 'and')
|
34
|
+
raise Oval::ValueError,
|
35
|
+
"Invalid option #{name.inspect}#{for_subject(subject)}. Allowed " +
|
36
|
+
"options are #{allowed}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_option_value(value, name, subject = nil)
|
41
|
+
subject = "#{subject}[#{name.inspect}]" if subject
|
42
|
+
self.class.ensure_match(value, decl[name], subject)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.validate_decl(decl)
|
46
|
+
unless decl.is_a?(Hash)
|
47
|
+
raise Oval::DeclError,
|
48
|
+
"Invalid declaration #{decl.inspect} of type #{decl.class.name}. " +
|
49
|
+
"Should be a Hash"
|
50
|
+
end
|
51
|
+
decl.each {|optname,optdecl| validate_option_name_decl(optname) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.validate_option_name_decl(optname)
|
55
|
+
unless optname.respond_to?(:to_s)
|
56
|
+
raise Oval::DeclError,
|
57
|
+
"Invalid name #{optname.inspect}. Should be convertible to String"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'oval/class_decl_base'
|
2
|
+
|
3
|
+
class Oval::SubclassOf < Oval::ClassDeclBase
|
4
|
+
def validate(thing, subject = nil)
|
5
|
+
unless thing.is_a?(Class) and (thing < self.klass)
|
6
|
+
raise Oval::ValueError,
|
7
|
+
"Invalid class #{thing.inspect}#{for_subject(subject)}. " +
|
8
|
+
"Should be subclass of #{klass.name}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|