diff_matcher 1.0.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/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
+