├── .gitignore ├── .rspec ├── .travis.yml ├── CHANGELOG ├── CONTRIBUTORS ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── diffy.gemspec ├── lib ├── diffy.rb └── diffy │ ├── css.rb │ ├── diff.rb │ ├── format.rb │ ├── html_formatter.rb │ ├── split_diff.rb │ └── version.rb └── spec ├── demo_app.rb └── diffy_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.swp 3 | Gemfile.lock 4 | tags 5 | .bundle 6 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --profile 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | cache: bundler 4 | rvm: 5 | - 1.8.7 6 | - 1.9.3-p551 7 | - 2.0.0-p648 8 | - 2.1.9 9 | - 2.2.6 10 | - 2.3.3 11 | - 2.4.0 12 | - 3.0 13 | - jruby-9.1.12.0 14 | matrix: 15 | allow_failures: 16 | - rvm: 1.8.7 17 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | == 3.4.3 == 2 | Require open3 unconditionally on Windows. Thanks @avdv! 3 | 4 | == 3.4.2 == 5 | Silence warning from unused variable when using `ruby -w`. Thanks 6 | @sambostock! 7 | 8 | == 3.4.1 == 9 | Prevent remote code execution from user controlled diff file paths. This 10 | issue was only present in Windows platforms. Thanks @tehryanx for reporting 11 | and testing the fix! 12 | 13 | == 3.4.0 == 14 | Remove space between U diff option and context number. Thanks @tomas! 15 | Add option to ignore CRLF diffs in HTML comparisons. Thanks @ptyagi16! 16 | 17 | == 3.3.0 == 18 | Fix diff lines that begin with -- or ++. Thanks @dark-panda! 19 | 20 | == 3.2.1 == 21 | Fix default options on alpine linux. Thanks @evgen! 22 | 23 | == 3.1.0 == 24 | Side by side diffs. Thanks Runar Skaare Tveiten! 25 | 26 | == 3.0.5 == 27 | Improve performance when generating html output (with inline highlighting) on 28 | long lines. Thanks Jason Barnabe! 29 | 30 | == 3.0.4 == 31 | handle windows vs. unix line breaks consistently in html output 32 | 33 | == 3.0.3 == 34 | explicitly unlink tempfiles to avoid occasional file handle leaks 35 | 36 | == 3.0.0 == 37 | allow_empty_diff is true by default 38 | 39 | == 2.1.0 == 40 | Windows support 41 | 42 | == 2.0.10 == 43 | Close tempfile after it's been written to to avoid too many open file handles 44 | 45 | == 2.0.9 == 46 | Memoize calls to `which diff` which should result in a minor performance 47 | improvement in high use environments. 48 | 49 | == 2.0.8 == 50 | Handle non-UTF-8 byte sequences in Ruby 1.9. 51 | Avoid non-deterministic deletion of temp files when GC runs 52 | 53 | == 2.0.7 == 54 | Added :allow_empty_diff option 55 | 56 | == Oops, need to backfill changelog == 57 | 58 | == 1.0.1 == 59 | * Compatibility with ruby 1.8.6 and 1.9 60 | 61 | == 1.0.0 == 62 | * HTML output and better documentation 63 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | * Sam Goldstein 2 | * joelash 3 | * chaffeqa 4 | * Lincoln Ritter 5 | * Bernhard Weichel 6 | * Yuichi Tateno 7 | * Nigel Thorne 8 | * Richard Stiller 9 | * printercu 10 | * Bryan Ricker 11 | * JasonBarnabe 12 | * Skye Shaw 13 | * Abinoam P. Marques Jr. 14 | * evgen 15 | * J Smith @dark-panda 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | platforms :rbx do 4 | gem 'rubysl', '~> 2.0' 5 | end 6 | 7 | gemspec 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Sam Goldstein 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Diffy - Easy Diffing With Ruby [![Build Status](https://travis-ci.org/samg/diffy.svg?branch=master)](https://travis-ci.org/samg/diffy) 2 | ============================ 3 | 4 | Need diffs in your ruby app? Diffy has you covered. It provides a convenient 5 | way to generate a diff from two strings or files. Instead of reimplementing 6 | the LCS diff algorithm Diffy uses battle tested Unix diff to generate diffs, 7 | and focuses on providing a convenient interface, and getting out of your way. 8 | 9 | Supported Formats 10 | ----------------- 11 | 12 | It provides several built in format options which can be passed to 13 | `Diffy::Diff#to_s`. 14 | 15 | * `:text` - Plain text output 16 | * `:color` - ANSI colorized text suitable for use in a terminal 17 | * `:html` - HTML output. Since version 2.0 this format does inline highlighting of the character changes between lines. 18 | * `:html_simple` - HTML output without inline highlighting. This may be useful in situations where high performance is required or simpler output is desired. 19 | 20 | A default format can be set like so: 21 | 22 | Diffy::Diff.default_format = :html 23 | 24 | Installation 25 | ------------ 26 | 27 | ### on Unix 28 | 29 | gem install diffy 30 | 31 | ### on Windows: 32 | 33 | 1. Ensure that you have a working `diff` on your machine and in your search path. 34 | 35 | There are several options: 36 | 37 | 1. Install [Diff::LCS](https://github.com/halostatue/diff-lcs), which includes `ldiff`. [RSpec](https://www.relishapp.com/rspec/docs/gettingstarted) 38 | depends on Diff::LCS so you may already have it installed. 39 | 40 | 1. If you're using [RubyInstaller](http://rubyinstaller.org), install the [devkit](http://rubyinstaller.org/add-ons/devkit). 41 | 42 | 1. Install unxutils 43 | 44 | note that these tools contain diff 2.7 which has a different handling 45 | of whitespace in the diff results. This makes Diffy spec tests 46 | yielding one fail on Windows. 47 | 48 | 1. Install these two individually from the gnuwin32 project 49 | 50 | 51 | note that this delivers diff 2.8 which makes Diffy spec pass 52 | even on Windows. 53 | 54 | 55 | 2. Install the gem by 56 | 57 | gem install diffy 58 | 59 | 60 | Getting Started 61 | --------------- 62 | 63 | Here's an example of using Diffy to diff two strings 64 | 65 | $ irb 66 | >> string1 = <<-TXT 67 | >" Hello how are you 68 | >" I'm fine 69 | >" That's great 70 | >" TXT 71 | => "Hello how are you\nI'm fine\nThat's great\n" 72 | >> string2 = <<-TXT 73 | >" Hello how are you? 74 | >" I'm fine 75 | >" That's swell 76 | >" TXT 77 | => "Hello how are you?\nI'm fine\nThat's swell\n" 78 | >> puts Diffy::Diff.new(string1, string2) 79 | -Hello how are you 80 | +Hello how are you? 81 | I'm fine 82 | -That's great 83 | +That's swell 84 | 85 | HTML Output 86 | --------------- 87 | 88 | Outputing the diff as html is easy too. Here's an example using the 89 | `:html_simple` formatter. 90 | 91 | >> puts Diffy::Diff.new(string1, string2).to_s(:html_simple) 92 |
93 |
    94 |
  • Hello how are you
  • 95 |
  • Hello how are you?
  • 96 |
  • I'm fine
  • 97 |
  • That's great
  • 98 |
  • That's swell
  • 99 |
100 |
101 | 102 | The `:html` formatter will give you inline highlighting a la github. 103 | 104 | >> puts Diffy::Diff.new("foo\n", "Foo\n").to_s(:html) 105 |
106 |
    107 |
  • foo
  • 108 |
  • Foo
  • 109 |
110 |
111 | 112 | There's some pretty nice css provided in `Diffy::CSS`. 113 | 114 | >> puts Diffy::CSS 115 | .diff{overflow:auto;} 116 | .diff ul{background:#fff;overflow:auto;font-size:13px;list-style:none;margin:0;padding:0;display:table;width:100%;} 117 | .diff del, .diff ins{display:block;text-decoration:none;} 118 | .diff li{padding:0; display:table-row;margin: 0;height:1em;} 119 | .diff li.ins{background:#dfd; color:#080} 120 | .diff li.del{background:#fee; color:#b00} 121 | .diff li:hover{background:#ffc} 122 | /* try 'whitespace:pre;' if you don't want lines to wrap */ 123 | .diff del, .diff ins, .diff span{white-space:pre-wrap;font-family:courier;} 124 | .diff del strong{font-weight:normal;background:#fcc;} 125 | .diff ins strong{font-weight:normal;background:#9f9;} 126 | .diff li.diff-comment { display: none; } 127 | .diff li.diff-block-info { background: none repeat scroll 0 0 gray; } 128 | 129 | 130 | There's also a colorblind-safe version of the pallete provided in `Diffy::CSS_COLORBLIND_1`. 131 | 132 | 133 | Side-by-side comparisons 134 | ------------------------ 135 | 136 | Side-by-side comparisons, or split views as called by some, are supported by 137 | using the `Diffy::SplitDiff` class. This class takes a diff returned from 138 | `Diffy::Diff` and splits it in two parts (or two sides): left and right. The 139 | left side represents deletions while the right side represents insertions. 140 | 141 | The class is used as follows: 142 | 143 | ``` 144 | Diffy::SplitDiff.new(string1, string2, options = {}) 145 | ``` 146 | 147 | The optional options hash is passed along to the main `Diff::Diff` class, so 148 | all default options such as full diff output are supported. The output format 149 | may be changed by passing the format with the options hash (see below), and all 150 | default formats are supported. 151 | 152 | Unlike `Diffy::Diff`, `Diffy::SplitDiff` does not use `#to_s` to output 153 | the resulting diff. Instead, two self-explanatory methods are used to output 154 | the diff: `#left` and `#right`. Using the earlier example, this is what they 155 | look like in action: 156 | 157 | ``` 158 | >> puts Diffy::SplitDiff.new(string1, string2).left 159 | -Hello how are you 160 | I'm fine 161 | -That's great 162 | ``` 163 | 164 | ``` 165 | >> puts Diffy::SplitDiff.new(string1, string2).right 166 | +Hello how are you? 167 | I'm fine 168 | +That's swell 169 | ``` 170 | 171 | ### Changing the split view output format 172 | 173 | The output format may be changed by passing the format with the options hash: 174 | 175 | ``` 176 | Diffy::SplitDiff.new(string1, string2, :format => :html) 177 | ``` 178 | 179 | This will result in the following: 180 | 181 | ``` 182 | >> puts Diffy::SplitDiff.new(string1, string2, :format => :html).left 183 |
184 |
    185 |
  • Hello how are you
  • 186 |
  • I'm fine
  • 187 |
  • That's great
  • 188 |
189 |
190 | ``` 191 | 192 | ``` 193 | >> puts Diffy::SplitDiff.new(string1, string2, :format => :html).right 194 |
195 |
    196 |
  • Hello how are you?
  • 197 |
  • I'm fine
  • 198 |
  • That's swell
  • 199 |
200 |
201 | ``` 202 | 203 | 204 | Other Diff Options 205 | ------------------ 206 | 207 | ### Diffing files instead of strings 208 | 209 | You can diff files instead of strings by using the `:source` option. 210 | 211 | >> puts Diffy::Diff.new('/tmp/foo', '/tmp/bar', :source => 'files') 212 | 213 | ### Full Diff Output 214 | 215 | By default Diffy removes the superfluous diff output. This is because its 216 | default is to show the complete diff'ed file (`diff -U10000` is the default). 217 | 218 | Diffy does support full output, just use the `:include_diff_info => true` 219 | option when initializing: 220 | 221 | >> Diffy::Diff.new("foo\nbar\n", "foo\nbar\nbaz\n", :include_diff_info => true).to_s(:text) 222 | =>--- /Users/chaffeqa/Projects/stiwiki/tmp/diffy20111116-82153-ie27ex 2011-11-16 20:16:41.000000000 -0500 223 | +++ /Users/chaffeqa/Projects/stiwiki/tmp/diffy20111116-82153-wzrhw5 2011-11-16 20:16:41.000000000 -0500 224 | @@ -1,2 +1,3 @@ 225 | foo 226 | bar 227 | +baz 228 | 229 | And even deals a bit with the formatting! 230 | 231 | ### Empty Diff Behavior 232 | 233 | By default Diffy will return empty string if there are no 234 | differences in inputs. In previous versions the full text of its first input 235 | was returned in this case. To restore this behaviour simply use the 236 | `:allow_empty_diff => false` option when initializing. 237 | 238 | ### Plus and Minus symbols in HTML output 239 | 240 | By default Diffy doesn't include the `+`, `-`, and ` ` at the beginning of line for 241 | HTML output. 242 | 243 | You can use the `:include_plus_and_minus_in_html` option to include those 244 | symbols in the output. 245 | 246 | >> puts Diffy::Diff.new(string1, string2, :include_plus_and_minus_in_html => true).to_s(:html_simple) 247 |
248 |
    249 |
  • -Hello how are you
  • 250 |
  • +Hello how are you?
  • 251 |
  • I'm fine
  • 252 |
  • -That's great
  • 253 |
  • +That's swell
  • 254 |
255 |
256 | 257 | ### Number of lines of context around changes 258 | 259 | You can use the `:context` option to override the number of lines of context 260 | that are shown around each change (this defaults to 10000 to show the full 261 | file). 262 | 263 | >> puts Diffy::Diff.new("foo\nfoo\nBAR\nbang\nbaz", "foo\nfoo\nbar\nbang\nbaz", :context => 1) 264 | foo 265 | -BAR 266 | +bar 267 | bang 268 | 269 | 270 | ### Overriding the command line options passed to diff. 271 | 272 | You can use the `:diff` option to override the command line options that are 273 | passed to unix diff. They default to `-U10000`. This option will noop if 274 | combined with the `:context` option. 275 | 276 | >> puts Diffy::Diff.new(" foo\nbar\n", "foo\nbar\n", :diff => "-w") 277 | foo 278 | bar 279 | 280 | ### `:ignore_crlf` when doing HTML compares 281 | 282 | You can make the HTML output ignore the CRLF by passing the `:ignore_crlf` option a truthy value. 283 | 284 | >> puts Diffy::Diff.new(" foo\nbar\n", "foo\r\nbar\r\n", ignore_crlf: true).to_s(:html) 285 | "
" 286 | 287 | 288 | 289 | Default Diff Options 290 | -------------------- 291 | 292 | You can set the default options for new `Diffy::Diff`s using the 293 | `Diffy::Diff.default_options` and `Diffy::Diff.default_options=` methods. 294 | Options passed to `Diffy::Diff.new` will be merged into the default options. 295 | 296 | >> Diffy::Diff.default_options 297 | => {:diff=>"-U10000", :source=>"strings", :include_diff_info=>false, :include_plus_and_minus_in_html=>false} 298 | >> Diffy::Diff.default_options.merge!(:source => 'files') 299 | => {:diff=>"-U10000", :source=>"files", :include_diff_info=>false, :include_plus_and_minus_in_html=>false} 300 | 301 | 302 | Custom Formats 303 | -------------- 304 | 305 | Diffy tries to make generating your own custom formatted output easy. 306 | `Diffy::Diff` provides an enumerable interface which lets you iterate over 307 | lines in the diff. 308 | 309 | >> Diffy::Diff.new("foo\nbar\n", "foo\nbar\nbaz\n").each do |line| 310 | >* case line 311 | >> when /^\+/ then puts "line #{line.chomp} added" 312 | >> when /^-/ then puts "line #{line.chomp} removed" 313 | >> end 314 | >> end 315 | line +baz added 316 | => [" foo\n", " bar\n", "+baz\n"] 317 | 318 | You can also use `Diffy::Diff#each_chunk` to iterate each grouping of additions, 319 | deletions, and unchanged in a diff. 320 | 321 | >> Diffy::Diff.new("foo\nbar\nbang\nbaz\n", "foo\nbar\nbing\nbong\n").each_chunk.to_a 322 | => [" foo\n bar\n", "-bang\n-baz\n", "+bing\n+bong\n"] 323 | 324 | Use `#map`, `#inject`, or any of Enumerable's methods. Go crazy. 325 | 326 | 327 | Testing 328 | ------------ 329 | 330 | Diffy includes a full set of rspec tests. When contributing please include 331 | tests for your changes. 332 | 333 | [![Build Status](https://secure.travis-ci.org/samg/diffy.png)](http://travis-ci.org/samg/diffy) 334 | 335 | --------------------------------------------------------------------- 336 | 337 | Report bugs or request features at http://github.com/samg/diffy/issues 338 | 339 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | task :default => :spec 4 | 5 | desc "Run all specs in spec directory" 6 | 7 | RSpec::Core::RakeTask.new(:spec) do |t| 8 | t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default. 9 | t.ruby_opts = "-w" 10 | end 11 | -------------------------------------------------------------------------------- /diffy.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'diffy/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "diffy" 8 | spec.version = Diffy::VERSION 9 | spec.authors = ["Sam Goldstein"] 10 | spec.email = ["sgrock@gmail.org"] 11 | spec.description = "Convenient diffing in ruby" 12 | spec.summary = "A convenient way to diff string in ruby" 13 | spec.homepage = "http://github.com/samg/diffy" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "rake" 22 | spec.add_development_dependency "rspec" 23 | end 24 | -------------------------------------------------------------------------------- /lib/diffy.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'erb' 3 | require 'rbconfig' 4 | require 'open3' 5 | 6 | module Diffy 7 | WINDOWS = (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) 8 | end 9 | require File.join(File.dirname(__FILE__), 'diffy', 'format') 10 | require File.join(File.dirname(__FILE__), 'diffy', 'html_formatter') 11 | require File.join(File.dirname(__FILE__), 'diffy', 'diff') 12 | require File.join(File.dirname(__FILE__), 'diffy', 'split_diff') 13 | require File.join(File.dirname(__FILE__), 'diffy', 'css') 14 | -------------------------------------------------------------------------------- /lib/diffy/css.rb: -------------------------------------------------------------------------------- 1 | module Diffy 2 | CSS = <<-STYLE 3 | .diff{overflow:auto;} 4 | .diff ul{background:#fff;overflow:auto;font-size:13px;list-style:none;margin:0;padding:0;display:table;width:100%;} 5 | .diff del, .diff ins{display:block;text-decoration:none;} 6 | .diff li{padding:0; display:table-row;margin: 0;height:1em;} 7 | .diff li.ins{background:#dfd; color:#080} 8 | .diff li.del{background:#fee; color:#b00} 9 | .diff li:hover{background:#ffc} 10 | /* try 'whitespace:pre;' if you don't want lines to wrap */ 11 | .diff del, .diff ins, .diff span{white-space:pre-wrap;font-family:courier;} 12 | .diff del strong{font-weight:normal;background:#fcc;} 13 | .diff ins strong{font-weight:normal;background:#9f9;} 14 | .diff li.diff-comment { display: none; } 15 | .diff li.diff-block-info { background: none repeat scroll 0 0 gray; } 16 | STYLE 17 | 18 | CSS_COLORBLIND_1 = <<-STYLE 19 | .diff{overflow:auto;} 20 | .diff ul{background:#fff;overflow:auto;font-size:13px;list-style:none;margin:0;padding:0;display:table;width:100%;} 21 | .diff del, .diff ins{display:block;text-decoration:none;} 22 | .diff li{padding:0; display:table-row;margin: 0;height:1em;} 23 | .diff li.ins{background:#ddf; color:#008} 24 | .diff li.del{background:#fee; color:#b00} 25 | .diff li:hover{background:#ffc} 26 | /* try 'whitespace:pre;' if you don't want lines to wrap */ 27 | .diff del, .diff ins, .diff span{white-space:pre-wrap;font-family:courier;} 28 | .diff del strong{font-weight:normal;background:#fcc;} 29 | .diff ins strong{font-weight:normal;background:#99f;} 30 | .diff li.diff-comment { display: none; } 31 | .diff li.diff-block-info { background: none repeat scroll 0 0 gray; } 32 | STYLE 33 | 34 | end 35 | -------------------------------------------------------------------------------- /lib/diffy/diff.rb: -------------------------------------------------------------------------------- 1 | module Diffy 2 | class Diff 3 | ORIGINAL_DEFAULT_OPTIONS = { 4 | :diff => '-U10000', 5 | :source => 'strings', 6 | :include_diff_info => false, 7 | :include_plus_and_minus_in_html => false, 8 | :context => nil, 9 | :allow_empty_diff => true, 10 | } 11 | 12 | class << self 13 | attr_writer :default_format 14 | def default_format 15 | @default_format ||= :text 16 | end 17 | 18 | # default options passed to new Diff objects 19 | attr_writer :default_options 20 | def default_options 21 | @default_options ||= ORIGINAL_DEFAULT_OPTIONS.dup 22 | end 23 | 24 | end 25 | include Enumerable 26 | attr_reader :string1, :string2, :options 27 | 28 | # supported options 29 | # +:diff+:: A cli options string passed to diff 30 | # +:source+:: Either _strings_ or _files_. Determines whether string1 31 | # and string2 should be interpreted as strings or file paths. 32 | # +:include_diff_info+:: Include diff header info 33 | # +:include_plus_and_minus_in_html+:: Show the +, -, ' ' at the 34 | # beginning of lines in html output. 35 | def initialize(string1, string2, options = {}) 36 | @options = self.class.default_options.merge(options) 37 | if ! ['strings', 'files'].include?(@options[:source]) 38 | raise ArgumentError, "Invalid :source option #{@options[:source].inspect}. Supported options are 'strings' and 'files'." 39 | end 40 | @string1, @string2 = string1, string2 41 | end 42 | 43 | def diff 44 | @diff ||= begin 45 | @paths = case options[:source] 46 | when 'strings' 47 | [tempfile(string1), tempfile(string2)] 48 | when 'files' 49 | [string1, string2] 50 | end 51 | 52 | diff, _stderr, _process_status = Open3.capture3(diff_bin, *(diff_options + @paths)) 53 | diff.force_encoding('ASCII-8BIT') if diff.respond_to?(:valid_encoding?) && !diff.valid_encoding? 54 | if diff =~ /\A\s*\Z/ && !options[:allow_empty_diff] 55 | diff = case options[:source] 56 | when 'strings' then string1 57 | when 'files' then File.read(string1) 58 | end.gsub(/^/, " ") 59 | end 60 | diff 61 | end 62 | ensure 63 | # unlink the tempfiles explicitly now that the diff is generated 64 | if defined? @tempfiles # to avoid Ruby warnings about undefined ins var. 65 | Array(@tempfiles).each do |t| 66 | begin 67 | # check that the path is not nil and file still exists. 68 | # REE seems to be very agressive with when it magically removes 69 | # tempfiles 70 | t.unlink if t.path && File.exist?(t.path) 71 | rescue => e 72 | warn "#{e.class}: #{e}" 73 | warn e.backtrace.join("\n") 74 | end 75 | end 76 | end 77 | end 78 | 79 | def each 80 | lines = case @options[:include_diff_info] 81 | when false 82 | # this "primes" the diff and sets up the paths we'll reference below. 83 | diff 84 | 85 | # caching this regexp improves the performance of the loop by a 86 | # considerable amount. 87 | regexp = /^(--- "?#{@paths[0]}"?|\+\+\+ "?#{@paths[1]}"?|@@|\\\\)/ 88 | 89 | diff.split("\n").reject{|x| x =~ regexp }.map {|line| line + "\n" } 90 | 91 | when true 92 | diff.split("\n").map {|line| line + "\n" } 93 | end 94 | 95 | if block_given? 96 | lines.each{|line| yield line} 97 | else 98 | lines.to_enum 99 | end 100 | end 101 | 102 | def each_chunk 103 | old_state = nil 104 | chunks = inject([]) do |cc, line| 105 | state = line.each_char.first 106 | if state == old_state 107 | cc.last << line 108 | else 109 | cc.push line.dup 110 | end 111 | old_state = state 112 | cc 113 | end 114 | 115 | if block_given? 116 | chunks.each{|chunk| yield chunk } 117 | else 118 | chunks.to_enum 119 | end 120 | end 121 | 122 | def tempfile(string) 123 | t = Tempfile.new('diffy') 124 | # ensure tempfiles aren't unlinked when GC runs by maintaining a 125 | # reference to them. 126 | @tempfiles ||=[] 127 | @tempfiles.push(t) 128 | t.print(string) 129 | t.flush 130 | t.close 131 | t.path 132 | end 133 | 134 | def to_s(format = nil) 135 | format ||= self.class.default_format 136 | formats = Format.instance_methods(false).map{|x| x.to_s} 137 | if formats.include? format.to_s 138 | enum = self 139 | enum.extend Format 140 | enum.send format 141 | else 142 | raise ArgumentError, 143 | "Format #{format.inspect} not found in #{formats.inspect}" 144 | end 145 | end 146 | private 147 | 148 | @@bin = nil 149 | def diff_bin 150 | return @@bin if @@bin 151 | 152 | if @@bin = ENV['DIFFY_DIFF'] 153 | # system() trick from Minitest 154 | raise "Can't execute diff program '#@@bin'" unless system(@@bin, __FILE__, __FILE__) 155 | return @@bin 156 | end 157 | 158 | diffs = ['diff', 'ldiff'] 159 | diffs.first << '.exe' if WINDOWS # ldiff does not have exe extension 160 | @@bin = diffs.find { |name| system(name, __FILE__, __FILE__) } 161 | 162 | if @@bin.nil? 163 | raise "Can't find a diff executable in PATH #{ENV['PATH']}" 164 | end 165 | 166 | @@bin 167 | end 168 | 169 | # options pass to diff program 170 | def diff_options 171 | Array(options[:context] ? "-U#{options[:context]}" : options[:diff]) 172 | end 173 | 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /lib/diffy/format.rb: -------------------------------------------------------------------------------- 1 | module Diffy 2 | module Format 3 | # ANSI color output suitable for terminal output 4 | def color 5 | map do |line| 6 | case line 7 | when /^(---|\+\+\+|\\\\)/ 8 | "\033[90m#{line.chomp}\033[0m" 9 | when /^\+/ 10 | "\033[32m#{line.chomp}\033[0m" 11 | when /^-/ 12 | "\033[31m#{line.chomp}\033[0m" 13 | when /^@@/ 14 | "\033[36m#{line.chomp}\033[0m" 15 | else 16 | line.chomp 17 | end 18 | end.join("\n") + "\n" 19 | end 20 | 21 | # Basic text output 22 | def text 23 | to_a.join 24 | end 25 | 26 | # Basic html output which does not attempt to highlight the changes 27 | # between lines, and is more performant. 28 | def html_simple 29 | HtmlFormatter.new(self, options).to_s 30 | end 31 | 32 | # Html output which does inline highlighting of changes between two lines. 33 | def html 34 | HtmlFormatter.new(self, options.merge(:highlight_words => true)).to_s 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/diffy/html_formatter.rb: -------------------------------------------------------------------------------- 1 | module Diffy 2 | class HtmlFormatter 3 | 4 | def initialize(diff, options = {}) 5 | @diff = diff 6 | @options = options 7 | end 8 | 9 | def to_s 10 | if @options[:highlight_words] 11 | wrap_lines(highlighted_words) 12 | else 13 | wrap_lines(@diff.map{|line| wrap_line(ERB::Util.h(line))}) 14 | end 15 | end 16 | 17 | private 18 | def wrap_line(line) 19 | cleaned = clean_line(line) 20 | case line 21 | when /^(---|\+\+\+|\\\\)/ 22 | '
  • ' + line.chomp + '
  • ' 23 | when /^\+/ 24 | '
  • ' + cleaned + '
  • ' 25 | when /^-/ 26 | '
  • ' + cleaned + '
  • ' 27 | when /^ / 28 | '
  • ' + cleaned + '
  • ' 29 | when /^@@/ 30 | '
  • ' + line.chomp + '
  • ' 31 | end 32 | end 33 | 34 | # remove +/- or wrap in html 35 | def clean_line(line) 36 | if @options[:include_plus_and_minus_in_html] 37 | line.sub(/^(.)/, '\1') 38 | else 39 | line.sub(/^./, '') 40 | end.chomp 41 | end 42 | 43 | def wrap_lines(lines) 44 | if lines.empty? 45 | %'
    ' 46 | else 47 | %'
    \n
      \n#{lines.join("\n")}\n
    \n
    \n' 48 | end 49 | end 50 | 51 | def highlighted_words 52 | chunks = @diff.each_chunk. 53 | reject{|c| c == '\ No newline at end of file'"\n"} 54 | 55 | processed = [] 56 | lines = chunks.each_with_index.map do |chunk1, index| 57 | next if processed.include? index 58 | processed << index 59 | chunk1 = chunk1 60 | chunk2 = chunks[index + 1] 61 | if not chunk2 62 | next ERB::Util.h(chunk1) 63 | end 64 | 65 | dir1 = chunk1.each_char.first 66 | dir2 = chunk2.each_char.first 67 | case [dir1, dir2] 68 | when ['-', '+'] 69 | if chunk1.each_char.take(3).join("") =~ /^(---|\+\+\+|\\\\)/ and 70 | chunk2.each_char.take(3).join("") =~ /^(---|\+\+\+|\\\\)/ 71 | ERB::Util.h(chunk1) 72 | else 73 | line_diff = Diffy::Diff.new( 74 | split_characters(chunk1), 75 | split_characters(chunk2), 76 | Diffy::Diff::ORIGINAL_DEFAULT_OPTIONS 77 | ) 78 | hi1 = reconstruct_characters(line_diff, '-') 79 | hi2 = reconstruct_characters(line_diff, '+') 80 | processed << (index + 1) 81 | [hi1, hi2] 82 | end 83 | else 84 | ERB::Util.h(chunk1) 85 | end 86 | end.flatten 87 | lines.map{|line| line.each_line.map(&:chomp).to_a if line }.flatten.compact. 88 | map{|line|wrap_line(line) }.compact 89 | end 90 | 91 | def split_characters(chunk) 92 | chunk.gsub(/^./, '').each_line.map do |line| 93 | if @options[:ignore_crlf] 94 | line.chomp.split('').map{|chr| ERB::Util.h(chr) } + [''] 95 | else 96 | chars = line.sub(/([\r\n]$)/, '').split('') 97 | chars.map{|chr| ERB::Util.h(chr) } + [''] 98 | end 99 | end.flatten.join("\n") + "\n" 100 | end 101 | 102 | def reconstruct_characters(line_diff, type) 103 | enum = line_diff.each_chunk.to_a 104 | enum.each_with_index.map do |l, i| 105 | re = /(^|)#{Regexp.escape(type)}/ 106 | case l 107 | when re 108 | highlight(l) 109 | when /^ / 110 | if i > 1 and enum[i+1] and l.each_line.to_a.size < 4 111 | highlight(l) 112 | else 113 | l.gsub(/^./, '').gsub("\n", ''). 114 | gsub('\r', "\r").gsub('', "\n") 115 | end 116 | end 117 | end.join('').split("\n").map do |l| 118 | type + l.gsub('' , '') 119 | end 120 | end 121 | 122 | def highlight(lines) 123 | "" + 124 | lines. 125 | # strip diff tokens (e.g. +,-,etc.) 126 | gsub(/(^|)./, ''). 127 | # join characters back by stripping out newlines 128 | gsub("\n", ''). 129 | # close and reopen strong tags. we don't want inline elements 130 | # spanning block elements which get added later. 131 | gsub('',"\n") + "" 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /lib/diffy/split_diff.rb: -------------------------------------------------------------------------------- 1 | module Diffy 2 | class SplitDiff 3 | def initialize(left, right, options = {}) 4 | @format = options[:format] || Diffy::Diff.default_format 5 | 6 | formats = Format.instance_methods(false).map { |x| x.to_s } 7 | unless formats.include?(@format.to_s) 8 | fail ArgumentError, "Format #{format.inspect} is not a valid format" 9 | end 10 | 11 | @diff = Diffy::Diff.new(left, right, options).to_s(@format) 12 | @left_diff, @right_diff = split 13 | end 14 | 15 | %w(left right).each do |direction| 16 | define_method direction do 17 | instance_variable_get("@#{direction}_diff") 18 | end 19 | end 20 | 21 | private 22 | 23 | def split 24 | [split_left, split_right] 25 | end 26 | 27 | def split_left 28 | case @format 29 | when :color 30 | @diff.gsub(/\033\[32m\+(.*)\033\[0m\n/, '') 31 | when :html, :html_simple 32 | @diff.gsub(%r{\s+
  • (.*)
  • }, '') 33 | when :text 34 | @diff.gsub(/^\+(.*)\n/, '') 35 | end 36 | end 37 | 38 | def split_right 39 | case @format 40 | when :color 41 | @diff.gsub(/\033\[31m\-(.*)\033\[0m\n/, '') 42 | when :html, :html_simple 43 | @diff.gsub(%r{\s+
  • (.*)
  • }, '') 44 | when :text 45 | @diff.gsub(/^-(.*)\n/, '') 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/diffy/version.rb: -------------------------------------------------------------------------------- 1 | module Diffy 2 | VERSION = '3.4.3' 3 | end 4 | -------------------------------------------------------------------------------- /spec/demo_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'json' 4 | require File.dirname(__FILE__) + '/../lib/diffy' 5 | 6 | blk = proc do 7 | Diffy::Diff.default_options.merge! JSON.parse(params[:options]) rescue {} 8 | haml "- d = Diffy::Diff.new(params[:one].to_s, params[:two].to_s)\n%div= d.to_s(:html)\n%pre= d.to_s" 9 | end 10 | post '/', &blk 11 | get '/', &blk 12 | __END__ 13 | 14 | @@ layout 15 | %html 16 | %head 17 | :css 18 | .diff{overflow:auto;} 19 | .diff ul{background:#fff;overflow:auto;font-size:13px;list-style:none;margin:0;padding:0;display:table;width:100%;} 20 | .diff del, .diff ins{display:block;text-decoration:none;} 21 | .diff li{padding:0; display:table-row;margin: 0;height:1em;} 22 | .diff li.ins{background:#dfd; color:#080} 23 | .diff li.del{background:#fee; color:#b00} 24 | .diff li:hover{background:#ffc} 25 | .diff del, .diff ins, .diff span{white-space:pre-wrap;font-family:courier;} 26 | .diff del strong{font-weight:normal;background:#fcc;} 27 | .diff ins strong{font-weight:normal;background:#9f9;} 28 | .diff li.diff-comment { display: none; } 29 | .diff li.diff-block-info { background: none repeat scroll 0 0 gray; } 30 | %body 31 | = yield 32 | %form{:action => '', :method => 'post'} 33 | %label JSON diff options 34 | %textarea{:name => 'options', :style => 'width:100%;height:250px;'}= params[:options] 35 | %label One 36 | %textarea{:name => 'one', :style => 'width:100%;height:250px;'}= params[:one] 37 | %br/ 38 | %label Two 39 | %textarea{:name => 'two', :style => 'width:100%;height:250px;'}= params[:two] 40 | %br/ 41 | %input{:type => 'submit'} 42 | %br/ 43 | 44 | @@ index 45 | %div.title Hello world!!!!! 46 | 47 | -------------------------------------------------------------------------------- /spec/diffy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'diffy')) 3 | 4 | describe Diffy::Diff do 5 | 6 | describe "diffing two files" do 7 | def tempfile(string, fn = 'diffy-spec') 8 | t = Tempfile.new(fn) 9 | # ensure tempfiles aren't unlinked when GC runs by maintaining a 10 | # reference to them. 11 | @tempfiles ||=[] 12 | @tempfiles.push(t) 13 | t.print(string) 14 | t.flush 15 | t.close 16 | t.path 17 | end 18 | 19 | it "should accept file paths as arguments" do 20 | string1 = "foo\nbar\nbang\n" 21 | string2 = "foo\nbang\n" 22 | path1, path2 = tempfile(string1), tempfile(string2) 23 | expect(Diffy::Diff.new(path1, path2, :source => 'files').to_s).to eq <<-DIFF 24 | foo 25 | -bar 26 | bang 27 | DIFF 28 | end 29 | 30 | it "should accept file paths with spaces as arguments" do 31 | string1 = "foo\nbar\nbang\n" 32 | string2 = "foo\nbang\n" 33 | path1, path2 = tempfile(string1, 'path with spaces'), tempfile(string2, 'path with spaces') 34 | expect(Diffy::Diff.new(path1, path2, :source => 'files').to_s).to eq <<-DIFF 35 | foo 36 | -bar 37 | bang 38 | DIFF 39 | end 40 | 41 | it "should accept file paths with spaces as arguments on windows" do 42 | begin 43 | 44 | orig_verbose, $VERBOSE = $VERBOSE, nil #silence redefine constant warnings 45 | orig_windows, Diffy::WINDOWS = Diffy::WINDOWS, true 46 | string1 = "foo\nbar\nbang\n" 47 | string2 = "foo\nbang\n" 48 | path1, path2 = tempfile(string1, 'path with spaces'), tempfile(string2, 'path with spaces') 49 | expect(Diffy::Diff.new(path1, path2, :source => 'files').to_s).to eq <<-DIFF 50 | foo 51 | -bar 52 | bang 53 | DIFF 54 | ensure 55 | Diffy::WINDOWS, $VERBOSE = orig_windows, orig_verbose 56 | end 57 | 58 | end 59 | 60 | describe "with no line different" do 61 | before do 62 | string1 = "foo\nbar\nbang\n" 63 | string2 = "foo\nbar\nbang\n" 64 | @path1, @path2 = tempfile(string1), tempfile(string2) 65 | end 66 | 67 | it "should show everything" do 68 | expect(Diffy::Diff.new(@path1, @path2, :source => 'files', :allow_empty_diff => false). 69 | to_s).to eq <<-DIFF 70 | foo 71 | bar 72 | bang 73 | DIFF 74 | end 75 | 76 | it "should not show everything if the :allow_empty_diff option is set" do 77 | expect(Diffy::Diff.new(@path1, @path2, :source => 'files', :allow_empty_diff => true).to_s).to eq('') 78 | end 79 | end 80 | describe "with lines that start with backslashes" do 81 | before do 82 | string1 = "foo\n\\\\bag\nbang\n" 83 | string2 = "foo\n\\\\bar\nbang\n" 84 | @path1, @path2 = tempfile(string1), tempfile(string2) 85 | end 86 | 87 | it "should not leave lines out" do 88 | expect(Diffy::Diff.new(@path1, @path2, :source => 'files').to_s).to eq <<-DIFF 89 | foo 90 | -\\\\bag 91 | +\\\\bar 92 | bang 93 | DIFF 94 | end 95 | end 96 | 97 | describe "with non valid UTF bytes" do 98 | before do 99 | string1 = "Foo ICS95095010000000000083320000BS01030000004100+\xFF00000000000000000\n" 100 | string2 = "Bar ICS95095010000000000083320000BS01030000004100+\xFF00000000000000000\n" 101 | @path1, @path2 = tempfile(string1), tempfile(string2) 102 | end 103 | it "should not raise invalid encoding issues" do 104 | desired = <<-DIFF 105 | -Foo ICS95095010000000000083320000BS01030000004100+\xFF00000000000000000 106 | +Bar ICS95095010000000000083320000BS01030000004100+\xFF00000000000000000 107 | DIFF 108 | desired.force_encoding("ASCII-8BIT") if desired.respond_to?(:force_encoding) 109 | expect(Diffy::Diff.new(@path1, @path2, :source => 'files').to_s).to eq(desired) 110 | end 111 | end 112 | 113 | end 114 | 115 | describe "handling temp files" do 116 | it "should unlink tempfiles after generating the diff" do 117 | before_tmpfiles = Dir.entries(Dir.tmpdir) 118 | ::Diffy::Diff.new("a", "b").to_s 119 | after_tmpfiles = Dir.entries(Dir.tmpdir) 120 | expect(before_tmpfiles).to match_array(after_tmpfiles) 121 | end 122 | 123 | it "should still be able to generate multiple diffs" do 124 | d = ::Diffy::Diff.new("a", "b") 125 | expect(d.to_s).to be_a String 126 | expect(d.to_s(:html)).to be_a String 127 | end 128 | end 129 | 130 | describe "options[:context]" do 131 | it "should limit context lines to 1" do 132 | diff = Diffy::Diff.new("foo\nfoo\nBAR\nbang\nbaz", "foo\nfoo\nbar\nbang\nbaz", :context => 1) 133 | expect(diff.to_s).to eq <<-DIFF 134 | foo 135 | -BAR 136 | +bar 137 | bang 138 | DIFF 139 | end 140 | end 141 | 142 | describe "options[:include_plus_and_minus_in_html]" do 143 | it "defaults to false" do 144 | @diffy = Diffy::Diff.new(" foo\nbar\n", "foo\nbar\n") 145 | expect(@diffy.options[:include_plus_and_minus_in_html]).to eq(false) 146 | end 147 | 148 | it "can be set to true" do 149 | @diffy = Diffy::Diff.new(" foo\nbar\n", "foo\nbar\n", :include_plus_and_minus_in_html=> true ) 150 | expect(@diffy.options[:include_plus_and_minus_in_html]).to eq(true) 151 | end 152 | 153 | describe "formats" do 154 | it "includes symbols in html_simple" do 155 | output = Diffy::Diff.new("foo\nbar\nbang\n", "foo\nbang\n", :include_plus_and_minus_in_html => true ). 156 | to_s(:html_simple) 157 | expect(output).to eq <<-HTML 158 |
    159 |
      160 |
    • foo
    • 161 |
    • -bar
    • 162 |
    • bang
    • 163 |
    164 |
    165 | HTML 166 | end 167 | 168 | it "includes symbols in html" do 169 | output = Diffy::Diff.new("foo\nbar\nbang\n", "foo\naba\nbang\n", :include_plus_and_minus_in_html => true ). 170 | to_s(:html) 171 | expect(output).to eq <<-HTML 172 |
    173 |
      174 |
    • foo
    • 175 |
    • -bar
    • 176 |
    • +aba
    • 177 |
    • bang
    • 178 |
    179 |
    180 | HTML 181 | end 182 | 183 | end 184 | 185 | end 186 | 187 | describe "options[:include_diff_info]" do 188 | it "defaults to false" do 189 | @diffy = Diffy::Diff.new(" foo\nbar\n", "foo\nbar\n") 190 | expect(@diffy.options[:include_diff_info]).to eq(false) 191 | end 192 | 193 | it "can be set to true" do 194 | @diffy = Diffy::Diff.new(" foo\nbar\n", "foo\nbar\n", :include_diff_info => true ) 195 | expect(@diffy.options[:include_diff_info]).to eq(true) 196 | end 197 | 198 | it "includes all diff output" do 199 | output = Diffy::Diff.new("foo\nbar\nbang\n", "foo\nbang\n", :include_diff_info => true ).to_s 200 | expect(output.to_s).to match( /@@/) 201 | expect(output).to match( /---/) 202 | expect(output).to match( /\+\+\+/) 203 | end 204 | 205 | describe "formats" do 206 | it "works for :color" do 207 | output = Diffy::Diff.new("foo\nbar\nbang\n", "foo\nbang\n", :include_diff_info => true ).to_s(:color) 208 | expect(output).to match( /\e\[0m\n\e\[36m\@\@/ ) 209 | expect(output.to_s).to match( /\e\[90m---/) 210 | expect(output.to_s).to match( /\e\[0m\n\e\[90m\+\+\+/) 211 | end 212 | 213 | it "works for :html_simple" do 214 | output = Diffy::Diff.new("foo\nbar\nbang\n", "foo\nbang\n", :include_diff_info => true ).to_s(:html_simple) 215 | expect(output.split("\n")).to include( "
  • @@ -1,3 +1,2 @@
  • " ) 216 | expect(output).to include( "
  • ---") 217 | expect(output).to include( "
  • +++") 218 | end 219 | end 220 | end 221 | 222 | describe "options[:diff]" do 223 | it "should accept an option to diff" do 224 | @diff = Diffy::Diff.new(" foo\nbar\n", "foo\nbar\n", :diff => "-w", :allow_empty_diff => false) 225 | expect(@diff.to_s).to eq <<-DIFF 226 | foo 227 | bar 228 | DIFF 229 | end 230 | 231 | it "should accept multiple arguments to diff" do 232 | @diff = Diffy::Diff.new(" foo\nbar\n", "foo\nbaz\n", :diff => ["-w", "-U 3"]) 233 | expect(@diff.to_s).to eq <<-DIFF 234 | foo 235 | -bar 236 | +baz 237 | DIFF 238 | end 239 | end 240 | 241 | describe "#to_s" do 242 | describe "with no line different" do 243 | before do 244 | @string1 = "foo\nbar\nbang\n" 245 | @string2 = "foo\nbar\nbang\n" 246 | end 247 | 248 | it "should show everything" do 249 | expect(Diffy::Diff.new(@string1, @string2, :allow_empty_diff => false).to_s).to eq <<-DIFF 250 | foo 251 | bar 252 | bang 253 | DIFF 254 | end 255 | end 256 | describe "with one line different" do 257 | before do 258 | @string1 = "foo\nbar\nbang\n" 259 | @string2 = "foo\nbang\n" 260 | end 261 | 262 | it "should show one line removed" do 263 | expect(Diffy::Diff.new(@string1, @string2).to_s).to eq <<-DIFF 264 | foo 265 | -bar 266 | bang 267 | DIFF 268 | end 269 | 270 | it "to_s should accept a format key" do 271 | expect(Diffy::Diff.new(@string1, @string2).to_s(:color)). 272 | to eq(" foo\n\e[31m-bar\e[0m\n bang\n") 273 | end 274 | 275 | it "should accept a default format option" do 276 | old_format = Diffy::Diff.default_format 277 | Diffy::Diff.default_format = :color 278 | expect(Diffy::Diff.new(@string1, @string2).to_s). 279 | to eq(" foo\n\e[31m-bar\e[0m\n bang\n") 280 | Diffy::Diff.default_format = old_format 281 | end 282 | 283 | it "should accept a default options" do 284 | old_options = Diffy::Diff.default_options 285 | Diffy::Diff.default_options = old_options.merge(:include_diff_info => true) 286 | expect(Diffy::Diff.new(@string1, @string2).to_s). 287 | to include('@@ -1,3 +1,2 @@') 288 | Diffy::Diff.default_options = old_options 289 | end 290 | 291 | it "should show one line added" do 292 | expect(Diffy::Diff.new(@string2, @string1).to_s). 293 | to eq <<-DIFF 294 | foo 295 | +bar 296 | bang 297 | DIFF 298 | end 299 | end 300 | 301 | describe "with one line changed" do 302 | before do 303 | @string1 = "foo\nbar\nbang\n" 304 | @string2 = "foo\nbong\nbang\n" 305 | end 306 | it "should show one line added and one removed" do 307 | expect(Diffy::Diff.new(@string1, @string2).to_s).to eq <<-DIFF 308 | foo 309 | -bar 310 | +bong 311 | bang 312 | DIFF 313 | end 314 | end 315 | 316 | describe "with totally different strings" do 317 | before do 318 | @string1 = "foo\nbar\nbang\n" 319 | @string2 = "one\ntwo\nthree\n" 320 | end 321 | it "should show one line added and one removed" do 322 | expect(Diffy::Diff.new(@string1, @string2).to_s).to eq <<-DIFF 323 | -foo 324 | -bar 325 | -bang 326 | +one 327 | +two 328 | +three 329 | DIFF 330 | end 331 | end 332 | 333 | describe "with a somewhat complicated diff" do 334 | before do 335 | @string1 = "foo\nbar\nbang\nwoot\n" 336 | @string2 = "one\ntwo\nthree\nbar\nbang\nbaz\n" 337 | @diff = Diffy::Diff.new(@string1, @string2) 338 | end 339 | it "should show one line added and one removed" do 340 | expect(@diff.to_s).to eq <<-DIFF 341 | -foo 342 | +one 343 | +two 344 | +three 345 | bar 346 | bang 347 | -woot 348 | +baz 349 | DIFF 350 | end 351 | 352 | it "should make an awesome simple html diff" do 353 | expect(@diff.to_s(:html_simple)).to eq <<-HTML 354 |
    355 |
      356 |
    • foo
    • 357 |
    • one
    • 358 |
    • two
    • 359 |
    • three
    • 360 |
    • bar
    • 361 |
    • bang
    • 362 |
    • woot
    • 363 |
    • baz
    • 364 |
    365 |
    366 | HTML 367 | end 368 | 369 | it "should accept overrides to diff's options" do 370 | @diff = Diffy::Diff.new(@string1, @string2, :diff => "--rcs") 371 | expect(@diff.to_s).to eq <<-DIFF 372 | d1 1 373 | a1 3 374 | one 375 | two 376 | three 377 | d4 1 378 | a4 1 379 | baz 380 | DIFF 381 | end 382 | end 383 | 384 | describe "html" do 385 | it "should not allow html injection on the last line" do 386 | @string1 = "hahaha\ntime flies like an arrow\nfoo bar\nbang baz\n", "").to_s(:html) 589 | expect(diff).to include('<script>') 590 | expect(diff).not_to include('