oval 0.0.1
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/.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
|