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 +9 -0
- data/License.txt +22 -0
- data/README.md +178 -0
- data/Rakefile +12 -0
- data/diff_matcher.gemspec +29 -0
- data/doc/example_output.png +0 -0
- data/lib/diff_matcher.rb +2 -0
- data/lib/diff_matcher/difference.rb +200 -0
- data/lib/diff_matcher/version.rb +3 -0
- data/spec/diff_matcher/difference_spec.rb +351 -0
- data/spec/spec_helper.rb +3 -0
- metadata +75 -0
data/Gemfile
ADDED
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
|
+
[](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
|
+

|
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,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
|
data/lib/diff_matcher.rb
ADDED
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
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
|
+
|