object-inspector 0.1.0
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/.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
|