object-inspector 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +45 -0
  4. data/README.md +335 -0
  5. data/Rakefile +13 -0
  6. data/examples/example.rb +131 -0
  7. data/features/error_display.feature +80 -0
  8. data/features/support/env.rb +1 -0
  9. data/features/syntax.feature +223 -0
  10. data/inspector.gemspec +24 -0
  11. data/lib/inspector.rb +27 -0
  12. data/lib/inspector/attribute_metadata.rb +19 -0
  13. data/lib/inspector/constraint.rb +18 -0
  14. data/lib/inspector/constraint/validators.rb +8 -0
  15. data/lib/inspector/constraint/validators/simple.rb +13 -0
  16. data/lib/inspector/constraint/validators/validity.rb +24 -0
  17. data/lib/inspector/constraint/violation.rb +34 -0
  18. data/lib/inspector/constraint/violation/list.rb +93 -0
  19. data/lib/inspector/constraints.rb +61 -0
  20. data/lib/inspector/constraints/email.rb +57 -0
  21. data/lib/inspector/constraints/empty.rb +21 -0
  22. data/lib/inspector/constraints/eq.rb +23 -0
  23. data/lib/inspector/constraints/false.rb +19 -0
  24. data/lib/inspector/constraints/have.rb +85 -0
  25. data/lib/inspector/constraints/predicate.rb +38 -0
  26. data/lib/inspector/constraints/true.rb +19 -0
  27. data/lib/inspector/constraints/valid.rb +31 -0
  28. data/lib/inspector/metadata.rb +75 -0
  29. data/lib/inspector/metadata/map.rb +24 -0
  30. data/lib/inspector/metadata/walker.rb +46 -0
  31. data/lib/inspector/property_metadata.rb +19 -0
  32. data/lib/inspector/type_metadata.rb +5 -0
  33. data/lib/inspector/validator.rb +26 -0
  34. data/lib/inspector/version.rb +3 -0
  35. data/lib/object_inspector.rb +1 -0
  36. data/spec/inspector/attribute_metadata_spec.rb +8 -0
  37. data/spec/inspector/constraint/violation/list_spec.rb +45 -0
  38. data/spec/inspector/constraints/false_spec.rb +18 -0
  39. data/spec/inspector/constraints_spec.rb +15 -0
  40. data/spec/inspector/metadata/map_spec.rb +38 -0
  41. data/spec/inspector/metadata/walker_spec.rb +7 -0
  42. data/spec/inspector/property_metadata_spec.rb +8 -0
  43. data/spec/inspector/type_metadata_spec.rb +7 -0
  44. data/spec/inspector/validator_spec.rb +65 -0
  45. data/spec/inspector_spec.rb +22 -0
  46. data/spec/shared_examples/metadata.rb +33 -0
  47. data/spec/spec_helper.rb +3 -0
  48. 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,19 @@
1
+ module Inspector
2
+ module Constraints
3
+ class False
4
+ include Constraint
5
+
6
+ def valid?(actual)
7
+ !actual
8
+ end
9
+
10
+ def to_s
11
+ "be_false"
12
+ end
13
+
14
+ def inspect
15
+ "#<false>"
16
+ end
17
+ end
18
+ end
19
+ 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
@@ -0,0 +1,19 @@
1
+ module Inspector
2
+ module Constraints
3
+ class True
4
+ include Constraint
5
+
6
+ def valid?(actual)
7
+ !!actual
8
+ end
9
+
10
+ def to_s
11
+ "be_true"
12
+ end
13
+
14
+ def inspect
15
+ "#<true>"
16
+ end
17
+ end
18
+ end
19
+ end