object-inspector 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +45 -0
- data/README.md +335 -0
- data/Rakefile +13 -0
- data/examples/example.rb +131 -0
- data/features/error_display.feature +80 -0
- data/features/support/env.rb +1 -0
- data/features/syntax.feature +223 -0
- data/inspector.gemspec +24 -0
- data/lib/inspector.rb +27 -0
- data/lib/inspector/attribute_metadata.rb +19 -0
- data/lib/inspector/constraint.rb +18 -0
- data/lib/inspector/constraint/validators.rb +8 -0
- data/lib/inspector/constraint/validators/simple.rb +13 -0
- data/lib/inspector/constraint/validators/validity.rb +24 -0
- data/lib/inspector/constraint/violation.rb +34 -0
- data/lib/inspector/constraint/violation/list.rb +93 -0
- data/lib/inspector/constraints.rb +61 -0
- data/lib/inspector/constraints/email.rb +57 -0
- data/lib/inspector/constraints/empty.rb +21 -0
- data/lib/inspector/constraints/eq.rb +23 -0
- data/lib/inspector/constraints/false.rb +19 -0
- data/lib/inspector/constraints/have.rb +85 -0
- data/lib/inspector/constraints/predicate.rb +38 -0
- data/lib/inspector/constraints/true.rb +19 -0
- data/lib/inspector/constraints/valid.rb +31 -0
- data/lib/inspector/metadata.rb +75 -0
- data/lib/inspector/metadata/map.rb +24 -0
- data/lib/inspector/metadata/walker.rb +46 -0
- data/lib/inspector/property_metadata.rb +19 -0
- data/lib/inspector/type_metadata.rb +5 -0
- data/lib/inspector/validator.rb +26 -0
- data/lib/inspector/version.rb +3 -0
- data/lib/object_inspector.rb +1 -0
- data/spec/inspector/attribute_metadata_spec.rb +8 -0
- data/spec/inspector/constraint/violation/list_spec.rb +45 -0
- data/spec/inspector/constraints/false_spec.rb +18 -0
- data/spec/inspector/constraints_spec.rb +15 -0
- data/spec/inspector/metadata/map_spec.rb +38 -0
- data/spec/inspector/metadata/walker_spec.rb +7 -0
- data/spec/inspector/property_metadata_spec.rb +8 -0
- data/spec/inspector/type_metadata_spec.rb +7 -0
- data/spec/inspector/validator_spec.rb +65 -0
- data/spec/inspector_spec.rb +22 -0
- data/spec/shared_examples/metadata.rb +33 -0
- data/spec/spec_helper.rb +3 -0
- metadata +175 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraint
|
3
|
+
class Violation
|
4
|
+
autoload :List, 'inspector/constraint/violation/list'
|
5
|
+
|
6
|
+
attr_reader :constraint
|
7
|
+
|
8
|
+
def initialize(constraint)
|
9
|
+
@constraint = constraint
|
10
|
+
end
|
11
|
+
|
12
|
+
def positive?
|
13
|
+
@constraint.positive?
|
14
|
+
end
|
15
|
+
|
16
|
+
def negative?
|
17
|
+
!@constraint.positive?
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
expectation = @constraint.positive? ? 'should' : 'should_not'
|
22
|
+
|
23
|
+
"#{expectation}.#{@constraint}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"#<violated %{type} constraint %{constraint}>" % {
|
28
|
+
:type => @constraint.positive? ? 'positive' : 'negative',
|
29
|
+
:constraint => @constraint.inspect
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraint
|
3
|
+
class Violation
|
4
|
+
class List
|
5
|
+
attr_reader :violations, :children
|
6
|
+
|
7
|
+
def initialize(violations = [], children = {})
|
8
|
+
@violations = violations
|
9
|
+
@children = children
|
10
|
+
end
|
11
|
+
|
12
|
+
def empty?
|
13
|
+
@violations.empty? && @children.empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
def length
|
17
|
+
length = @violations.length
|
18
|
+
length += @children.values.map(&:length).reduce(:+) unless @children.empty?
|
19
|
+
|
20
|
+
length
|
21
|
+
end
|
22
|
+
|
23
|
+
def <<(violation)
|
24
|
+
unless violation.kind_of?(Inspector::Constraint::Violation)
|
25
|
+
raise "#{violation.inspect} is not a Inspector::Constraint::Violation"
|
26
|
+
end
|
27
|
+
|
28
|
+
@violations << violation
|
29
|
+
end
|
30
|
+
alias :push :<<
|
31
|
+
|
32
|
+
def []=(property_path, violations_list)
|
33
|
+
unless violations_list.kind_of?(Inspector::Constraint::Violation::List)
|
34
|
+
raise "#{violations_list.inspect} is not a Inspector::Constraint::Violation::List"
|
35
|
+
end
|
36
|
+
|
37
|
+
@children[property_path.to_s] = violations_list
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](property_path)
|
41
|
+
child_path, _, property_path = property_path.to_s.split(/(\.|\[|\])/, 2)
|
42
|
+
|
43
|
+
not_found = "cannot locate violations for #{property_path}"
|
44
|
+
violations_list = @children.fetch(child_path) { raise not_found }
|
45
|
+
|
46
|
+
if property_path
|
47
|
+
violations_list[property_path]
|
48
|
+
else
|
49
|
+
violations_list
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def each(path = "", &block)
|
54
|
+
@violations.each do |violation|
|
55
|
+
yield(path, violation)
|
56
|
+
end
|
57
|
+
|
58
|
+
@children.each do |sub_path, list|
|
59
|
+
prefix = "." unless path.empty? || sub_path.start_with?("[")
|
60
|
+
|
61
|
+
list.each("#{path}#{prefix}#{sub_path}", &block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
string = ""
|
67
|
+
|
68
|
+
unless @violations.empty?
|
69
|
+
string += @violations.map(&:to_s).join("\n")
|
70
|
+
string += "\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
@children.each do |path, list|
|
74
|
+
unless list.empty?
|
75
|
+
string += "#{path}:\n"
|
76
|
+
string += list.to_s.split("\n").map {|line| " #{line}"}.join("\n")
|
77
|
+
string += "\n"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
string
|
82
|
+
end
|
83
|
+
|
84
|
+
def inspect
|
85
|
+
"#<violations %{violations} children=%{children}>" % {
|
86
|
+
:violations => @violations.inspect,
|
87
|
+
:children => @children.inspect
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraints
|
3
|
+
autoload :Base, 'inspector/constraints/base'
|
4
|
+
autoload :False, 'inspector/constraints/false'
|
5
|
+
autoload :True, 'inspector/constraints/true'
|
6
|
+
autoload :Empty, 'inspector/constraints/empty'
|
7
|
+
autoload :Predicate, 'inspector/constraints/predicate'
|
8
|
+
autoload :Have, 'inspector/constraints/have'
|
9
|
+
autoload :Email, 'inspector/constraints/email'
|
10
|
+
autoload :Eq, 'inspector/constraints/eq'
|
11
|
+
autoload :Valid, 'inspector/constraints/valid'
|
12
|
+
|
13
|
+
def be_false
|
14
|
+
False.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def be_true
|
18
|
+
True.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def be_empty
|
22
|
+
Empty.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def have_at_least(n)
|
26
|
+
Have::AtLeast.new(n)
|
27
|
+
end
|
28
|
+
|
29
|
+
def have_at_most(n)
|
30
|
+
Have::AtMost.new(n)
|
31
|
+
end
|
32
|
+
|
33
|
+
def have(n)
|
34
|
+
Have::Exactly.new(n)
|
35
|
+
end
|
36
|
+
alias :have_exactly :have
|
37
|
+
|
38
|
+
def be_email
|
39
|
+
Email.new
|
40
|
+
end
|
41
|
+
alias :be_an_email :be_email
|
42
|
+
|
43
|
+
def eq(expected)
|
44
|
+
Eq.new(expected)
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate(options = {})
|
48
|
+
Valid.new(options[:as])
|
49
|
+
end
|
50
|
+
|
51
|
+
# be_empty => value.empty?
|
52
|
+
# be_nil => value.nil?
|
53
|
+
def method_missing(method, *args, &block)
|
54
|
+
prefix, *predicate = method.to_s.split("_")
|
55
|
+
predicate.shift if ["a", "an"].include?(predicate.first)
|
56
|
+
return Inspector::Constraints::Predicate.new(predicate.join("_"), *args, &block) if prefix == "be"
|
57
|
+
# return Inspector::Constraints::Has.new(method, *args, &block) if method.to_s =~ /^have_/
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraints
|
3
|
+
class Email
|
4
|
+
include Constraint
|
5
|
+
|
6
|
+
PATTERN = begin
|
7
|
+
if (RUBY_VERSION == '1.9.2' && RUBY_ENGINE == 'jruby' && JRUBY_VERSION <= '1.6.3') || RUBY_VERSION >= '1.9.2'
|
8
|
+
# There is an obscure bug in jruby 1.6 that prevents matching
|
9
|
+
# on unicode properties here. Remove this logic branch once
|
10
|
+
# a stable jruby release fixes this.
|
11
|
+
#
|
12
|
+
# http://jira.codehaus.org/browse/JRUBY-5622
|
13
|
+
#
|
14
|
+
# There is a similar bug in preview releases of 1.9.3
|
15
|
+
#
|
16
|
+
# http://redmine.ruby-lang.org/issues/5126
|
17
|
+
letter = 'a-zA-Z'
|
18
|
+
else
|
19
|
+
letter = 'a-zA-Z\p{L}' # Changed from RFC2822 to include unicode chars
|
20
|
+
end
|
21
|
+
digit = '0-9'
|
22
|
+
atext = "[#{letter}#{digit}\!\#\$\%\&\'\*+\/\=\?\^\_\`\{\|\}\~\-]"
|
23
|
+
dot_atom_text = "#{atext}+([.]#{atext}*)+"
|
24
|
+
dot_atom = dot_atom_text
|
25
|
+
no_ws_ctl = '\x01-\x08\x11\x12\x14-\x1f\x7f'
|
26
|
+
qtext = "[^#{no_ws_ctl}\\x0d\\x22\\x5c]" # Non-whitespace, non-control character except for \ and "
|
27
|
+
text = '[\x01-\x09\x11\x12\x14-\x7f]'
|
28
|
+
quoted_pair = "(\\x5c#{text})"
|
29
|
+
qcontent = "(?:#{qtext}|#{quoted_pair})"
|
30
|
+
quoted_string = "[\"]#{qcontent}+[\"]"
|
31
|
+
atom = "#{atext}+"
|
32
|
+
word = "(?:#{atom}|#{quoted_string})"
|
33
|
+
obs_local_part = "#{word}([.]#{word})*"
|
34
|
+
local_part = "(?:#{dot_atom}|#{quoted_string}|#{obs_local_part})"
|
35
|
+
dtext = "[#{no_ws_ctl}\\x21-\\x5a\\x5e-\\x7e]"
|
36
|
+
dcontent = "(?:#{dtext}|#{quoted_pair})"
|
37
|
+
domain_literal = "\\[#{dcontent}+\\]"
|
38
|
+
obs_domain = "#{atom}([.]#{atom})+"
|
39
|
+
domain = "(?:#{dot_atom}|#{domain_literal}|#{obs_domain})"
|
40
|
+
addr_spec = "#{local_part}\@#{domain}"
|
41
|
+
pattern = /\A#{addr_spec}\z/u
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid?(email)
|
45
|
+
!(PATTERN =~ email).nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"be_an_email"
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
"#<email>"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraints
|
3
|
+
class Empty
|
4
|
+
include Constraint
|
5
|
+
|
6
|
+
def valid?(actual)
|
7
|
+
actual = actual.to_s unless actual.respond_to?(:empty?)
|
8
|
+
|
9
|
+
actual.empty?
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"be_empty"
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"#<empty>"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraints
|
3
|
+
include Constraint
|
4
|
+
|
5
|
+
class Eq
|
6
|
+
def initialize(expected)
|
7
|
+
@expected = expected
|
8
|
+
end
|
9
|
+
|
10
|
+
def valid?(actual)
|
11
|
+
actual == @expected
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"eq"
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
"#<== #{@expected.inspect}>"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraints
|
3
|
+
module Have
|
4
|
+
include Constraint
|
5
|
+
|
6
|
+
class AtLeast
|
7
|
+
include Have
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
"have_at_least"
|
11
|
+
end
|
12
|
+
|
13
|
+
def compare(actual)
|
14
|
+
actual >= @expected
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class AtMost
|
19
|
+
include Have
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"have_at_most"
|
23
|
+
end
|
24
|
+
|
25
|
+
def compare(actual)
|
26
|
+
actual <= @expected
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Exactly
|
31
|
+
include Have
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"have_exactly"
|
35
|
+
end
|
36
|
+
|
37
|
+
def compare(actual)
|
38
|
+
actual == @expected
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(expected)
|
43
|
+
@expected = expected.to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid?(collection_or_owner)
|
47
|
+
collection = determine_collection(collection_or_owner)
|
48
|
+
return false if collection.nil?
|
49
|
+
query_method = determine_query_method(collection)
|
50
|
+
compare(collection.__send__(query_method))
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
"#<%{constraint} %{expected}%{collection}>" % {
|
55
|
+
:constraint => to_s.split('_').join(' '),
|
56
|
+
:expected => @expected.inspect,
|
57
|
+
:collection => " #{@collection_name}".rstrip
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def method_missing(method, *args, &block)
|
62
|
+
@collection_name = method
|
63
|
+
@args = args
|
64
|
+
@block = block
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def determine_collection(collection_or_owner)
|
71
|
+
collection_or_owner.__send__(@collection_name, *@args, &@block) if collection_or_owner.respond_to?(@collection_name)
|
72
|
+
collection_or_owner if determine_query_method(collection_or_owner)
|
73
|
+
# elsif (@plural_collection_name && collection_or_owner.respond_to?(@plural_collection_name))
|
74
|
+
# collection_or_owner.__send__(@plural_collection_name, *@args, &@block)
|
75
|
+
# else
|
76
|
+
# collection_or_owner.__send__(@collection_name, *@args, &@block)
|
77
|
+
# end
|
78
|
+
end
|
79
|
+
|
80
|
+
def determine_query_method(collection)
|
81
|
+
[:size, :length, :count].detect {|m| collection.respond_to?(m)}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraints
|
3
|
+
class Predicate
|
4
|
+
include Constraint
|
5
|
+
|
6
|
+
def initialize(method, *args, &block)
|
7
|
+
@method = method
|
8
|
+
@args = args
|
9
|
+
@block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid?(actual)
|
13
|
+
result = false
|
14
|
+
|
15
|
+
begin
|
16
|
+
result = actual.__send__("#{@method}?", *@args, &@block)
|
17
|
+
rescue NameError
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
result = actual.__send__("#{@method}s?", *@args, &@block)
|
22
|
+
@method = "#{@method}s"
|
23
|
+
rescue NameError
|
24
|
+
end
|
25
|
+
|
26
|
+
return result
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"be_#{@method}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"#<#{@method}>"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|