oval 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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