DiffMatcher === [![Build Status](https://secure.travis-ci.org/diff-matcher/diff_matcher.png)](https://travis-ci.org/diff-matcher/diff_matcher) [![Gem Version](https://badge.fury.io/rb/diff_matcher.png)](http://badge.fury.io/rb/diff_matcher) [![Dependency Status](https://gemnasium.com/diff-matcher/diff_matcher.png)](https://gemnasium.com/diff-matcher/diff_matcher) [![still maintained](http://stillmaintained.com/diff-matcher/diff_matcher.png)](http://stillmaintained.com/diff-matcher/diff_matcher) [![diff_matcher API Documentation](https://www.omniref.com/ruby/gems/diff_matcher.png)](https://www.omniref.com/ruby/gems/diff_matcher) Generates a diff by matching against user-defined matchers written in ruby. DiffMatcher matches input data (eg. from a JSON API) against values, ranges, classes, regexes, procs, custom matchers, RSpec matchers and/or easily composed, nested combinations thereof to produce an easy to read diff string. Actual input values are matched against expected matchers in the following way: ``` ruby actual.is_a? expected # when expected is a class expected.match actual # when expected is a regexp expected.call actual # when expected is a proc expected.matches? actual # when expected implements an RSpec Matcher interface actual == expected # when expected is anything else expected.diff actual # when expected is a built-in DiffMatcher ``` Using these building blocks, more complicated nested matchers can be composed. eg. ``` ruby expected = { :a=>{ :a1=>11 }, :b=>[ 21, 22 ], :c=>/\d/, :d=>Fixnum, :e=>lambda { |x| (4..6).include? x } }, actual = { :a=>{ :a1=>10, :a2=>12 }, :b=>[ 21 ], :c=>'3' , :d=>4 , :e=>5 }, puts DiffMatcher::difference(expected, actual, :color_scheme=>:white_background) ``` ![example output](https://raw.github.com/playup/diff_matcher/master/doc/diff_matcher.gif) Installation --- gem install diff_matcher Usage --- ``` ruby require 'diff_matcher' DiffMatcher::difference(expected, actual, opts={}) ``` #### Simple matchers Using plain ruby objects produces the following diffs: ``` +-------------+--------+-------------+ | expected | actual | diff | +-------------+--------+-------------+ | 1 | 2 | - 1+ 2 | | 1 | 1 | | | String | 1 | - String+ 1 | | String | "1" | | | /[a-z]/ | 1 | -/[a-z]/+ 1 | | /[a-z]/ | "a" | | | 1..3 | 4 | - 1..3+ 4 | | 1..3 | 3 | | | is_boolean | true | | +-------------+--------+-------------+ Where: is_boolean = lambda { |x| [FalseClass, TrueClass].include? x.class } ``` When `actual` is missing one of the `expected` values ``` ruby expected = [1, 2] puts DiffMatcher::difference(expected, [1]) # => [ # => 1 # => - 2 # => ] # => Where, - 1 missing ``` When `actual` has additional values to the `expected` ``` ruby expected = [1] puts DiffMatcher::difference(expected, [1, 2]) # => [ # => 1 # => + 2 # => ] # => Where, + 1 additional ``` #### More complicated matchers Sometimes you'll need to wrap plain ruby objects with DiffMatcher's built-in matchers, to provide extra matching abilities. When `expected` is a `Hash`, but has optional keys, wrap the `Hash` with a built-in `Matcher` ``` ruby exp = {:name=>String, :age=>Fixnum} expected = DiffMatcher::Matcher.new(exp, :optional_keys=>[:age]) puts DiffMatcher::difference(expected, {:name=>0}) # => { # => :name=>- String+ 0 # => } # => Where, - 1 missing, + 1 additional ``` When multiple `expected` values can be matched against, simply wrap them in `Matcher`s and `||` them together ``` ruby exp1 = Fixnum exp2 = Float expected = DiffMatcher::Matcher.new(exp1) || DiffMatcher::Matcher.new(exp2) puts DiffMatcher::difference(expected, "3") # => - Float+ "3" # => Where, - 1 missing, + 1 additional ``` Or to do the same thing using a shorter syntax ``` ruby exp1 = Fixnum exp2 = Float expected = DiffMatcher::Matcher[exp1, exp2] puts DiffMatcher::difference(expected, "3") # => - Float+ "3" # => Where, - 1 missing, + 1 additional ``` When `actual` is an array of *unknown* size use an `AllMatcher` to match against *all* the elements in the array ``` ruby exp = Fixnum expected = DiffMatcher::AllMatcher.new(exp) puts DiffMatcher::difference(expected, [1, 2, "3"]) # => [ # => : 1, # => : 2, # => - Fixnum+ "3" # => ] # => Where, - 1 missing, + 1 additional, : 2 match_class ``` When `actual` is an array with a *limited* size use an `AllMatcher` to match against *all* the elements in the array adhering to the limits of `:min` and or `:max` or `:size` (where `:size` is a Fixnum or range of Fixnum). ``` ruby exp = Fixnum expected = DiffMatcher::AllMatcher.new(exp, :min=>3) puts DiffMatcher::difference(expected, [1, 2]) # => [ # => : 1, # => : 2, # => - Fixnum # => ] # => Where, - 1 missing, : 2 match_class ``` ``` ruby exp = Fixnum expected = DiffMatcher::AllMatcher.new(exp, :size=>3..5) puts DiffMatcher::difference(expected, [1, 2]) # => [ # => : 1, # => : 2, # => - Fixnum # => ] # => Where, - 1 missing, : 2 match_class ``` When `actual` is an array of unknown size *and* `expected` can take multiple forms use a `Matcher` to `||` them together, then wrap that with an `AllMatcher` to match against *all* the elements in the array in any of the forms. ``` ruby exp1 = Fixnum exp2 = Float expected = DiffMatcher::AllMatcher.new( DiffMatcher::Matcher[Fixnum, Float] ) puts DiffMatcher::difference(expected, [1, 2.00, "3"]) # => [ # => | 1, # => | 2.0, # => - Float+ "3" # => ] # => Where, - 1 missing, + 1 additional, | 2 match_matcher ``` When `actual` is matched against some custom logic, an object with an RSpec Matcher interface can be used. (NB. `#mathches?(actual)`, `#description` and `#failure_message` methods must be implemented.) So you can use one of the RSpec matchers or you can implement your own. ```ruby class BeAWeekend def matches?(day) @day = day %w{Sat Sun}.include?(day) end def description 'be a weekend day' end def failure_message "expected #{@day} to #{description}" end end be_a_weekend = BeAWeekend.new puts DiffMatcher::difference(be_a_weekend, 'Mon') # => - expected Mon to be a weekend day+ "Mon" # => Where, - 1 missing, + 1 additional all_be_weekends = DiffMatcher::AllMatcher.new(be_a_weekend) puts DiffMatcher::difference(all_be_weekends, ['Sat', 'Mon']) # => [ # => R "Sat" be a weekend day, # => - expected Mon to be a weekend day+ "Mon" # => ] # => Where, - 1 missing, + 1 additional, R 1 match_rspec ``` ### Matcher options Matcher options can be passed to `DiffMatcher::difference` or `DiffMatcher::Matcher#diff` or instances of `DiffMatcher::Matcher` First consider: ``` ruby expected = DiffMatcher::Matcher.new([1]) puts expected.diff([1, 2]) # => [ # => 1, # => + 2 # => ] ``` Using `:ignore_additional=>true` will now match even though `actual` has additional items. It can be used in the following ways: ``` ruby expected = DiffMatcher::Matcher.new([1]) puts expected.diff([1, 2], :ignore_additional=>true) # => nil ``` or ``` ruby expected = DiffMatcher::Matcher.new([1]) puts DiffMatcher::difference(expected, [1, 2], :ignore_additional=>true) # => nil ``` or ``` ruby expected = DiffMatcher::Matcher.new([1], :ignore_additional=>true) puts expected.diff([1, 2]) # => nil ``` Now consider: ``` ruby puts DiffMatcher::Matcher.new([Fixnum, 2]).diff([1]) # => [ # => : 1, # => - 2 # => ] ``` Using `:quiet=>true` will only show missing and additional items in the output ``` ruby puts DiffMatcher::Matcher.new([Fixnum, 2]).diff([1], :quiet=>true) # => [ # => - 2 # => ] ``` `:html_output=>true` will convert ansii escape colour codes to html spans ``` ruby puts DiffMatcher::difference(1, 2, :html_output=>true) # =>
# => - 1+ 2 # => Where, - 1 missing, + 1 additional # =>``` #### Prefixes A difference string is similar in appereance to the `.inspect` of plain ruby objects, however the matched elements it contains are prefixed in the following way: missing => "- " additional => "+ " match value => match regexp => "~ " match class => ": " match matcher => "| " match range => ". " match proc => "{ " match rspec => "R " #### Colours Colours (defined in colour schemes) can also appear in the difference. Using the `:default` colour scheme items shown in a difference are coloured as follows: missing => red additional => yellow match value => match regexp => green match class => blue match matcher => blue match range => cyan match proc => cyan match rspec => cyan Other colour schemes, eg. `:color_scheme=>:white_background` will use different colour mappings. Similar gems --- ### String differs *