diff_matcher 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "rake"
7
+ gem "rspec" #, "~> 2.0.1"
8
+ gem "simplecov"
9
+ end
data/License.txt ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2011 PlayUp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,178 @@
1
+ DiffMatcher
2
+ ===
3
+
4
+ [![build status](http://travis-ci.org/playup/diff_matcher.png)](http://travis-ci.org/playup/diff_matcher)
5
+
6
+ Generates a diff by matching against expected values, classes, regexes and/or procs.
7
+
8
+ DiffMatcher performs recursive matches on values contained in hashes, arrays and combinations thereof.
9
+
10
+ Values in a containing object match when:
11
+
12
+ - actual == expected
13
+ - actual.is_a? expected # when expected is a class
14
+ - expected.match actual # when expected is a regexp
15
+ - expected.call actual # when expected is a proc
16
+
17
+
18
+ Installation
19
+ ---
20
+
21
+ gem install diff_matcher
22
+
23
+
24
+ Usage
25
+ ---
26
+
27
+ require 'diff_matcher'
28
+
29
+ DiffMatcher::difference(actual, expected, opts={})
30
+
31
+ When `expected` != `actual`
32
+
33
+ puts DiffMatcher::difference(1, 2)
34
+ # => - 1+ 2
35
+ # => Where, - 1 missing, + 1 additional
36
+
37
+ When `expected` == `actual`
38
+
39
+ p DiffMatcher::difference(1, 1)
40
+ # => nil
41
+
42
+ When `actual` is an instance of the `expected`
43
+
44
+ p DiffMatcher::difference(String, '1')
45
+ # => nil
46
+
47
+ When `actual` is a string that matches the `expected` regex
48
+
49
+ p DiffMatcher::difference(/[a-z]/, "a")
50
+ # => nil
51
+
52
+ When `actual` is passed to an `expected` proc and it returns true
53
+
54
+ is_boolean = lambda { |x| [FalseClass, TrueClass].include? x.class }
55
+ p DiffMatcher::difference(is_boolean, true)
56
+ # => nil
57
+
58
+ When `actual` is missing one of the `expected` values
59
+
60
+ puts DiffMatcher::difference([1, 2], [1])
61
+ # => [
62
+ # => - 2
63
+ # => ]
64
+ # => Where, - 1 missing
65
+
66
+ When `actual` has additional values to the `expected`
67
+
68
+ puts DiffMatcher::difference([1], [1, 2])
69
+ # => [
70
+ # => + 2
71
+ # => ]
72
+ # => Where, - 1 additional
73
+
74
+ ### Options
75
+
76
+ `:ignore_additional=>true` will match even if `actual` has additional items
77
+
78
+ p DiffMatcher::difference([1], [1, 2], :ignore_additional=>true)
79
+ # => nil
80
+
81
+ `:verbose=>true` shows only missing and additional items in the output
82
+
83
+ puts DiffMatcher::difference([Fixnum, 2], [1], :quiet=>true)
84
+ # => [
85
+ # => - 2
86
+ # => ]
87
+ # => Where, - 1 missing
88
+
89
+ `:verbose=>true` shows all matched items in the output
90
+
91
+ puts DiffMatcher::difference([Fixnum, 2], [1], :verbose=>true)
92
+ # => [
93
+ # = > : 1,
94
+ # => - 2
95
+ # => ]
96
+ # => Where, - 1 missing, : 1 match_class
97
+
98
+ #### prefixes
99
+
100
+ NB. The `: 1` from above includes a `:` prefix that shows the `1` was matched against a class (ie. `Fixnum`)
101
+
102
+ The items shown in a difference are prefixed as follows:
103
+
104
+ missing => "- "
105
+ additional => "+ "
106
+ match value =>
107
+ match regexp => "~ "
108
+ match class => ": "
109
+ match proc => "{ "
110
+
111
+ #### colours
112
+
113
+ Colours (defined in colour schemes) can also appear in the difference.
114
+
115
+ Using the `:default` colour scheme items shown in a difference are coloured as follows:
116
+
117
+ missing => red
118
+ additional => yellow
119
+ match value =>
120
+ match regexp => green
121
+ match class => blue
122
+ match proc => cyan
123
+
124
+
125
+ puts DiffMatcher::difference(
126
+ { :a=>{ :a1=>11 }, :b=>[ 21, 22 ], :c=>/\d/, :d=>Fixnum, :e=>lambda { |x| (4..6).includes? x },
127
+ { :a=>{ :a1=>10, :a2=>12 }, :b=>[ 21 ], :c=>'3' , :d=>4 , :e=>5 },
128
+ :verbose=>true, :color_scheme=>:white_background
129
+ )
130
+
131
+ ![example output](https://raw.github.com/playup/diff_matcher/master/doc/example_output.png)
132
+
133
+
134
+ Similar gems
135
+ ---
136
+
137
+ ### String differs
138
+ * <http://difflcs.rubyforge.org> (A resonably fast diff algorithm using longest common substrings)
139
+ * <http://github.com/samg/diffy> (Provides a convenient interfaces to Unix diff)
140
+ * <http://github.com/pvande/differ> (A simple gem for generating string diffs)
141
+ * <http://github.com/shuber/sub_diff> (Apply regular expression replacements to strings while presenting the result in a “diff” like format)
142
+ * <http://github.com/rattle/diffrenderer> (Takes two pieces of source text/html and creates a neato html diff output)
143
+
144
+ ### Object differs
145
+ * <http://github.com/tinogomes/ssdiff> (Super Stupid Diff)
146
+ * <http://github.com/postmodern/tdiff> (Calculates the differences between two tree-like structures)
147
+ * <http://github.com/Blargel/easy_diff> (Recursive diff, merge, and unmerge for hashes and arrays)
148
+
149
+
150
+ Why another differ?
151
+ ---
152
+
153
+ This gem came about because [rspec](http://github.com/rspec/rspec-expectations) doesn't have a decent differ for matching hashes and/or JSON.
154
+ It started out as a [pull request](http://github.com/rspec/rspec-expectations/pull/79), to be implemented as a
155
+ `be_hash_matching` [rspec matcher](https://www.relishapp.com/rspec/rspec-expectations),
156
+ but seemed useful enough to be its own stand alone gem.
157
+
158
+ Out of the similar gems above, [easy_diff](http://github.com/Blargel/easy_diff) looks like a good alternative to this gem.
159
+ It has extra functionality in also being able to recursively merge hashes and arrays.
160
+ [sub_diff](http://github.com/shuber/sub_diff) can use regular expressions in its match and subsequent diff
161
+
162
+ DiffMatcher can match using not only regexes but classes and procs.
163
+ And the difference string that it outputs can be formatted in several ways as needed.
164
+
165
+
166
+ Contributing
167
+ ---
168
+
169
+ Fork, write some tests and send a pull request (bonus points for topic branches).
170
+
171
+
172
+ Status
173
+ ---
174
+
175
+ Our company is using this gem to test our JSON API which has got it to a stable v1.0.0 release.
176
+
177
+ There's a [pull request](http://github.com/rspec/rspec-expectations/pull/79) to use this gem in a `be_hash_matching`
178
+ [rspec matcher](https://www.relishapp.com/rspec/rspec-expectations).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require "rspec/core/rake_task"
6
+
7
+ task "default" => "spec"
8
+
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = 'spec/**/*_spec.rb'
11
+ t.rspec_opts = ["--colour", "--format", "nested"]
12
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "diff_matcher/version"
4
+
5
+ Gem::Specification.new do |s|
6
+
7
+ s.name = "diff_matcher"
8
+ s.version = DiffMatcher::VERSION.dup
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Playup"]
11
+ s.email = "chris@playup.com"
12
+ s.homepage = "http://github.com/playup/diff_matcher"
13
+
14
+ s.summary = %q{Generates a diff by matching against expected values, classes, regexes and/or procs.}
15
+ s.description = <<EOF
16
+ DiffMatcher performs recursive matches on values contained in hashes, arrays and combinations thereof.
17
+
18
+ Values in a containing object match when:
19
+
20
+ - actual == expected
21
+ - actual.is_a? expected # when expected is a class
22
+ - expected.match actual # when expected is a regexp
23
+ - expected.call actual # when expected is a proc
24
+ EOF
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ s.require_paths = ["lib"]
29
+ end
Binary file
@@ -0,0 +1,2 @@
1
+ require 'diff_matcher/difference'
2
+ require 'diff_matcher/version'
@@ -0,0 +1,200 @@
1
+ module DiffMatcher
2
+
3
+ def self.difference(expected, actual, opts={})
4
+ difference = Difference.new(expected, actual, opts)
5
+ difference.matching? ? nil : difference.to_s
6
+ end
7
+
8
+ class Difference
9
+ RESET = "\e[0m"
10
+ BOLD = "\e[1m"
11
+
12
+ RED = "\e[31m"
13
+ GREEN = "\e[32m"
14
+ YELLOW = "\e[33m"
15
+ BLUE = "\e[34m"
16
+ MAGENTA = "\e[35m"
17
+ CYAN = "\e[36m"
18
+
19
+ COLOR_SCHEMES = {
20
+ :default=>{
21
+ :missing => [RED , "-"],
22
+ :additional => [YELLOW, "+"],
23
+ :match_value => [nil , nil],
24
+ :match_regexp => [GREEN , "~"],
25
+ :match_class => [BLUE , ":"],
26
+ :match_proc => [CYAN , "{"]
27
+ },
28
+ :white_background=> {
29
+ :missing => [RED , "-"],
30
+ :additional => [MAGENTA, "+"],
31
+ :match_value => [nil , nil],
32
+ :match_regexp => [GREEN , "~"],
33
+ :match_class => [BLUE , ":"],
34
+ :match_proc => [CYAN , "{"]
35
+ }
36
+ }
37
+
38
+ attr_reader :dif
39
+
40
+ def initialize(expected, actual, opts={})
41
+ @ignore_additional = opts[:ignore_additional]
42
+ @quiet = opts[:quiet]
43
+ @verbose = opts[:verbose]
44
+ @color_enabled = opts[:color_enabled] || !!opts[:color_scheme]
45
+ @color_scheme = COLOR_SCHEMES[opts[:color_scheme] || :default]
46
+ @difference = difference(expected, actual)
47
+ end
48
+
49
+ def matching?
50
+ @match ||= @difference ? item_types.map { |item_type|
51
+ @color_scheme[item_type]
52
+ }.reduce(0) { |count, (color, prefix)|
53
+ count + @difference.scan("#{color}#{prefix}").size
54
+ } == 0 : true
55
+ end
56
+
57
+ def to_s
58
+ if @difference
59
+ msg = "\e[0m" + @difference.split("\n").join("\n\e[0m")
60
+ where = @color_scheme.keys.collect { |item_type|
61
+ unless item_type == :match_value
62
+ color, prefix = @color_scheme[item_type]
63
+ count = msg.scan("#{color}#{prefix}").size
64
+ "#{color}#{prefix} #{BOLD}#{count} #{item_type}#{RESET}" if count > 0
65
+ end
66
+ }.compact.join(", ")
67
+ msg << "\nWhere, #{where}" if where.size > 0
68
+
69
+ @color_enabled ? msg : msg.gsub(/\e\[\d+m/, "")
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def item_types
76
+ @item_types ||= @ignore_additional ? [:missing] : [:missing, :additional]
77
+ end
78
+
79
+ def item_types_shown
80
+ @item_types_shown ||= lambda {
81
+ ret = [:different] + item_types
82
+ ret += [:additional] unless @quiet
83
+ ret.uniq
84
+ }.call
85
+ end
86
+
87
+ def matches_shown
88
+ @matches_shown ||= lambda {
89
+ ret = []
90
+ unless @quiet
91
+ ret += [:match_class, :match_proc, :match_regexp]
92
+ ret += [:match_value] if @verbose
93
+ end
94
+ ret
95
+ }.call
96
+ end
97
+
98
+ def difference(expected, actual, reverse=false)
99
+ if actual.is_a? expected.class
100
+ left = diff(expected, actual, true)
101
+ right = diff(actual, expected)
102
+ items_to_s(
103
+ expected,
104
+ (item_types_shown).reduce([]) { |a, method|
105
+ a + send(method, left, right, expected.class).compact.map { |item| markup(method, item) }
106
+ }
107
+ )
108
+ else
109
+ difference_to_s(expected, actual, reverse)
110
+ end
111
+ end
112
+
113
+ def diff(expected, actual, reverse=false)
114
+ if expected.is_a?(Hash)
115
+ expected.keys.reduce({}) { |h, k|
116
+ h.update(k => actual.has_key?(k) ? difference(actual[k], expected[k], reverse) : expected[k])
117
+ }
118
+ elsif expected.is_a?(Array)
119
+ expected, actual = [expected, actual].map { |x| x.each_with_index.reduce({}) { |h, (v, i)| h.update(i=>v) } }
120
+ #diff(expected, actual, reverse) # XXX - is there a test case for this?
121
+ diff(expected, actual)
122
+ else
123
+ actual
124
+ end if expected.is_a? actual.class
125
+ end
126
+
127
+ def compare(right, expected_class, default=nil)
128
+ if [Hash, Array].include? expected_class
129
+ right && right.keys.tap { |keys| keys.sort if expected_class == Array }.map { |k|
130
+ yield k
131
+ }
132
+ else
133
+ [default]
134
+ end
135
+ end
136
+
137
+ def different(left, right, expected_class)
138
+ compare(right, expected_class, difference_to_s(right, left)) { |k|
139
+ "#{"#{k.inspect}=>" if expected_class == Hash}#{right[k]}" if right[k] and left.has_key?(k)
140
+ }
141
+ end
142
+
143
+ def missing(left, right, expected_class)
144
+ compare(left, expected_class) { |k|
145
+ "#{"#{k.inspect}=>" if expected_class == Hash}#{left[k].inspect}" unless right.has_key?(k)
146
+ }
147
+ end
148
+
149
+ def additional(left, right, expected_class)
150
+ missing(right, left, expected_class)
151
+ end
152
+
153
+ def match?(expected, actual)
154
+ case expected
155
+ when Class ; [actual.is_a?(expected) , :match_class ]
156
+ when Proc ; [expected.call(actual) , :match_proc ]
157
+ when Regexp; [actual.is_a?(String) && actual.match(expected) , :match_regexp ]
158
+ else [actual == expected , :match_value ]
159
+ end
160
+ end
161
+
162
+ def items_to_s(expected, items)
163
+ case expected
164
+ when Hash ; "{\n#{items.join(",\n")}\n}\n"
165
+ when Array; "[\n#{items.join(",\n")}\n]\n"
166
+ else items.join.strip
167
+ end if items.size > 0
168
+ end
169
+
170
+ def match_regexp_to_s(expected, actual)
171
+ if actual.is_a? String
172
+ color, prefix = @color_scheme[:match_regexp]
173
+ actual.sub(expected, "#{color}(\e[1m#{actual[expected, 0]}#{RESET}#{color})#{RESET}")
174
+ end
175
+ end
176
+
177
+ def match_to_s(expected, actual, match_type)
178
+ actual = match_regexp_to_s(expected, actual) if match_type == :match_regexp
179
+ markup(match_type, actual) if matches_shown.include? match_type
180
+ end
181
+
182
+ def difference_to_s(expected, actual, reverse=false)
183
+ match, match_type = match? *(reverse ? [actual, expected] : [expected, actual])
184
+ if match
185
+ match_to_s(expected, actual, match_type)
186
+ else
187
+ "#{markup(:missing, expected.inspect)}#{markup(:additional, actual.inspect)}"
188
+ end
189
+ end
190
+
191
+ def markup(item_type, item)
192
+ if item_type == :different
193
+ item.split("\n").map {|line| " #{line}"}.join("\n") if item
194
+ else
195
+ color, prefix = @color_scheme[item_type]
196
+ "#{color}#{prefix+' ' if prefix}#{BOLD if color and item_type != :match_regexp}#{RESET if item_type == :match_regexp}#{item}#{RESET if color}" if item
197
+ end if item
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,3 @@
1
+ module DiffMatcher
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,351 @@
1
+ require "spec_helper"
2
+
3
+ RUBY_1_9 = (RUBY_VERSION =~ /^1\.9/)
4
+
5
+ def opts_to_s(opts)
6
+ opts_strs = opts.map { |k,v| ":#{k}=>#{v}" if v }.compact
7
+ opts_strs.size > 0 ? ", " + opts_strs * ", " : ""
8
+ end
9
+
10
+ def fix_EOF_problem(s)
11
+ # <<-EOF isn't working like its meant to :(
12
+ whitespace = s.split("\n")[-1][/^[ ]+/]
13
+ indentation = whitespace ? whitespace.size : 0
14
+ s.gsub("\n#{" " * indentation}", "\n").strip
15
+ end
16
+
17
+ describe DiffMatcher do
18
+ subject { DiffMatcher::difference(expected, actual, opts) }
19
+
20
+ shared_examples_for "a differ" do |expected, same, different, difference="", opts={}|
21
+ context "with #{opts.size > 0 ? opts_to_s(opts) : "no opts"}" do
22
+ describe "difference(#{expected.inspect}, #{same.inspect}#{opts_to_s(opts)})" do
23
+ let(:expected) { expected }
24
+ let(:actual ) { same }
25
+ let(:opts ) { opts }
26
+
27
+ it { should be_nil }
28
+ end
29
+
30
+ describe "#new(#{expected.inspect}, #{different.inspect}#{opts_to_s(opts)})" do
31
+ let(:expected) { expected }
32
+ let(:actual ) { different }
33
+ let(:opts ) { opts }
34
+
35
+ it { should_not be_nil } unless RUBY_1_9
36
+ it {
37
+ difference.is_a?(Regexp) ?
38
+ should =~ difference :
39
+ should == fix_EOF_problem(difference)
40
+ } if RUBY_1_9
41
+ end
42
+ end
43
+ end
44
+
45
+ describe "when expected is an instance," do
46
+ context "of Fixnum," do
47
+ expected, same, different =
48
+ 1,
49
+ 1,
50
+ 2
51
+
52
+ it_behaves_like "a differ", expected, same, different,
53
+ <<-EOF
54
+ - 1+ 2
55
+ Where, - 1 missing, + 1 additional
56
+ EOF
57
+ end
58
+
59
+ context "of String," do
60
+ expected, same, different =
61
+ "a",
62
+ "a",
63
+ "b"
64
+
65
+ it_behaves_like "a differ", expected, same, different,
66
+ <<-EOF
67
+ - "a"+ "b"
68
+ Where, - 1 missing, + 1 additional
69
+ EOF
70
+
71
+ context "when actual is of a different class" do
72
+ different = 0
73
+
74
+ it_behaves_like "a differ", expected, same, different,
75
+ <<-EOF
76
+ - "a"+ 0
77
+ Where, - 1 missing, + 1 additional
78
+ EOF
79
+ end
80
+
81
+ context "when actual is nil" do
82
+ different = nil
83
+
84
+ it_behaves_like "a differ", expected, same, different,
85
+ <<-EOF
86
+ - "a"+ nil
87
+ Where, - 1 missing, + 1 additional
88
+ EOF
89
+ end
90
+ end
91
+
92
+ context "of nil," do
93
+ expected, same, different =
94
+ nil,
95
+ nil,
96
+ false
97
+
98
+ it_behaves_like "a differ", expected, same, different,
99
+ <<-EOF
100
+ - nil+ false
101
+ Where, - 1 missing, + 1 additional
102
+ EOF
103
+ end
104
+
105
+ context "of Array," do
106
+ expected, same, different =
107
+ [ 1 ],
108
+ [ 1 ],
109
+ [ 2 ]
110
+
111
+ it_behaves_like "a differ", expected, same, different,
112
+ <<-EOF
113
+ [
114
+ - 1+ 2
115
+ ]
116
+ Where, - 1 missing, + 1 additional
117
+ EOF
118
+
119
+ context "where actual has additional items" do
120
+ expected, same, different =
121
+ [ 1, 2 ],
122
+ [ 1, 2, 3 ],
123
+ [ 0, 2, 3 ]
124
+
125
+ it_behaves_like "a differ", expected, same, different,
126
+ <<-EOF, :ignore_additional=>true
127
+ [
128
+ - 1+ 0,
129
+ + 3
130
+ ]
131
+ Where, - 1 missing, + 2 additional
132
+ EOF
133
+
134
+ it_behaves_like "a differ", expected, same, different,
135
+ <<-EOF, :ignore_additional=>true, :quiet=>true
136
+ [
137
+ - 1+ 0
138
+ ]
139
+ Where, - 1 missing, + 1 additional
140
+ EOF
141
+
142
+ it_behaves_like "a differ", expected, same, different,
143
+ <<-EOF, :ignore_additional=>true, :verbose=>true
144
+ [
145
+ - 1+ 0,
146
+ 2,
147
+ + 3
148
+ ]
149
+ Where, - 1 missing, + 2 additional
150
+ EOF
151
+ end
152
+
153
+ context "where actual has missing items" do
154
+ expected, same, different =
155
+ [ 1, 2, 3 ],
156
+ [ 1, 2, 3 ],
157
+ [ 1, 2 ]
158
+
159
+ it_behaves_like "a differ", expected, same, different,
160
+ <<-EOF
161
+ [
162
+ - 3
163
+ ]
164
+ Where, - 1 missing
165
+ EOF
166
+
167
+ it_behaves_like "a differ", expected, same, different,
168
+ <<-EOF, :verbose=>true
169
+ [
170
+ 1,
171
+ 2,
172
+ - 3
173
+ ]
174
+ Where, - 1 missing
175
+ EOF
176
+ end
177
+ end
178
+
179
+ context "of Hash," do
180
+ expected, same, different =
181
+ { "a"=>1 },
182
+ { "a"=>1 },
183
+ { "a"=>2 }
184
+
185
+ it_behaves_like "a differ", expected, same, different,
186
+ <<-EOF
187
+ {
188
+ "a"=>- 1+ 2
189
+ }
190
+ Where, - 1 missing, + 1 additional
191
+ EOF
192
+
193
+ context "with keys of differing classes" do
194
+ expected, same, different =
195
+ { "a"=>{ "b"=>1 } },
196
+ { "a"=>{ "b"=>1 } },
197
+ { "a"=>[ "b", 1 ] }
198
+
199
+ it_behaves_like "a differ", expected, same, different,
200
+ <<-EOF
201
+ {
202
+ "a"=>- {"b"=>1}+ ["b", 1]
203
+ }
204
+ Where, - 1 missing, + 1 additional
205
+ EOF
206
+ end
207
+
208
+ context "with matching hash descendents" do
209
+ expected, same, different =
210
+ { "a"=>{ "b"=>{ "c"=>1 } } },
211
+ { "a"=>{ "b"=>{ "c"=>1 } } },
212
+ { "b"=>{ "c"=>1 } }
213
+
214
+ describe "it won't match the descendents" do
215
+ it_behaves_like "a differ", expected, same, different,
216
+ <<-EOF
217
+ {
218
+ - "a"=>{"b"=>{"c"=>1}},
219
+ + "b"=>{"c"=>1}
220
+ }
221
+ Where, - 1 missing, + 1 additional
222
+ EOF
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ describe "when expected is," do
229
+ context "a class," do
230
+ expected, same, different =
231
+ String,
232
+ "a",
233
+ 1
234
+
235
+ it_behaves_like "a differ", expected, same, different,
236
+ <<-EOF
237
+ - String+ 1
238
+ Where, - 1 missing, + 1 additional
239
+ EOF
240
+ end
241
+ end
242
+
243
+ describe "when expected is," do
244
+ context "a Regex," do
245
+ expected, same, different =
246
+ /[a-z]/,
247
+ "a",
248
+ "A"
249
+
250
+ it_behaves_like "a differ", expected, same, different,
251
+ <<-EOF
252
+ - /[a-z]/+ "A"
253
+ Where, - 1 missing, + 1 additional
254
+ EOF
255
+
256
+ context "and when actual is not a String," do
257
+ different = :a
258
+
259
+ it_behaves_like "a differ", expected, same, different,
260
+ <<-EOF
261
+ - /[a-z]/+ :a
262
+ Where, - 1 missing, + 1 additional
263
+ EOF
264
+ end
265
+ end
266
+ end
267
+
268
+ describe "when expected is," do
269
+ context "a proc," do
270
+ expected, same, different =
271
+ lambda { |x| [FalseClass, TrueClass].include? x.class },
272
+ true,
273
+ "true"
274
+
275
+ it_behaves_like "a differ", expected, same, different,
276
+ /- #<Proc.*?>\+ \"true\"\nWhere, - 1 missing, \+ 1 additional/
277
+ end
278
+ end
279
+
280
+ context "when expected has multiple items," do
281
+ expected, same, different =
282
+ [ 1, 2, /\d/, Fixnum, lambda { |x| (4..6).include? x } ],
283
+ [ 1, 2, "3" , 4 , 5 ],
284
+ [ 0, 2, "3" , 4 , 5 ]
285
+
286
+ describe "it shows regex, class, proc matches and" do
287
+ it_behaves_like "a differ", expected, same, different,
288
+ <<-EOF
289
+ [
290
+ - 1+ 0,
291
+ ~ (3),
292
+ : 4,
293
+ { 5
294
+ ]
295
+ Where, - 1 missing, + 1 additional, ~ 1 match_regexp, : 1 match_class, { 1 match_proc
296
+ EOF
297
+ end
298
+
299
+ describe "it doesn't show matches and" do
300
+ it_behaves_like "a differ", expected, same, different,
301
+ <<-EOF, :quiet=>true
302
+ [
303
+ - 1+ 0
304
+ ]
305
+ Where, - 1 missing, + 1 additional
306
+ EOF
307
+ end
308
+
309
+ describe "it shows all matches and" do
310
+ it_behaves_like "a differ", expected, same, different,
311
+ <<-EOF ,:verbose=>true
312
+ [
313
+ - 1+ 0,
314
+ 2,
315
+ ~ (3),
316
+ : 4,
317
+ { 5
318
+ ]
319
+ Where, - 1 missing, + 1 additional, ~ 1 match_regexp, : 1 match_class, { 1 match_proc
320
+ EOF
321
+ end
322
+
323
+ describe "it shows matches in color and" do
324
+ it_behaves_like "a differ", expected, same, different,
325
+ <<-EOF ,:verbose=>true, :color_scheme=>:default
326
+ \e[0m[
327
+ \e[0m \e[31m- \e[1m1\e[0m\e[33m+ \e[1m0\e[0m,
328
+ \e[0m 2,
329
+ \e[0m \e[32m~ \e[0m\e[32m(\e[1m3\e[0m\e[32m)\e[0m\e[0m,
330
+ \e[0m \e[34m: \e[1m4\e[0m,
331
+ \e[0m \e[36m{ \e[1m5\e[0m
332
+ \e[0m]
333
+ Where, \e[31m- \e[1m1 missing\e[0m, \e[33m+ \e[1m1 additional\e[0m, \e[32m~ \e[1m1 match_regexp\e[0m, \e[34m: \e[1m1 match_class\e[0m, \e[36m{ \e[1m1 match_proc\e[0m
334
+ EOF
335
+
336
+ context "on a white background" do
337
+ it_behaves_like "a differ", expected, same, different,
338
+ <<-EOF ,:verbose=>true, :color_scheme=>:white_background
339
+ \e[0m[
340
+ \e[0m \e[31m- \e[1m1\e[0m\e[35m+ \e[1m0\e[0m,
341
+ \e[0m 2,
342
+ \e[0m \e[32m~ \e[0m\e[32m(\e[1m3\e[0m\e[32m)\e[0m\e[0m,
343
+ \e[0m \e[34m: \e[1m4\e[0m,
344
+ \e[0m \e[36m{ \e[1m5\e[0m
345
+ \e[0m]
346
+ Where, \e[31m- \e[1m1 missing\e[0m, \e[35m+ \e[1m1 additional\e[0m, \e[32m~ \e[1m1 match_regexp\e[0m, \e[34m: \e[1m1 match_class\e[0m, \e[36m{ \e[1m1 match_proc\e[0m
347
+ EOF
348
+ end
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,3 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+ require "diff_matcher"
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: diff_matcher
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.0.0
6
+ platform: ruby
7
+ authors:
8
+ - Playup
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-09-17 00:00:00 +10:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: |
18
+ DiffMatcher performs recursive matches on values contained in hashes, arrays and combinations thereof.
19
+
20
+ Values in a containing object match when:
21
+
22
+ - actual == expected
23
+ - actual.is_a? expected # when expected is a class
24
+ - expected.match actual # when expected is a regexp
25
+ - expected.call actual # when expected is a proc
26
+
27
+ email: chris@playup.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files: []
33
+
34
+ files:
35
+ - Gemfile
36
+ - License.txt
37
+ - README.md
38
+ - Rakefile
39
+ - diff_matcher.gemspec
40
+ - doc/example_output.png
41
+ - lib/diff_matcher.rb
42
+ - lib/diff_matcher/difference.rb
43
+ - lib/diff_matcher/version.rb
44
+ - spec/diff_matcher/difference_spec.rb
45
+ - spec/spec_helper.rb
46
+ has_rdoc: true
47
+ homepage: http://github.com/playup/diff_matcher
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options: []
52
+
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.6.2
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Generates a diff by matching against expected values, classes, regexes and/or procs.
74
+ test_files: []
75
+