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