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