Commit d2b7f117e224639e566f57beadd551c7e8e0adc5
1 parent
4c5fccb6
Exists in
master
and in
22 other branches
Add diffy lib
Showing
17 changed files
with
1352 additions
and
0 deletions
Show diff stats
| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | +allow_empty_diff is true by default | |
| 2 | + | |
| 3 | +== 2.1.0 == | |
| 4 | +Windows support | |
| 5 | + | |
| 6 | +== 2.0.10 == | |
| 7 | +Close tempfile after it's been written to to avoid too many open file handles | |
| 8 | + | |
| 9 | +== 2.0.9 == | |
| 10 | +Memoize calls to `which diff` which should result in a minor performance | |
| 11 | +improvement in high use environments. | |
| 12 | + | |
| 13 | +== 2.0.8 == | |
| 14 | +Handle non-UTF-8 byte sequences in Ruby 1.9. | |
| 15 | +Avoid non-deterministic deletion of temp files when GC runs | |
| 16 | + | |
| 17 | +== 2.0.7 == | |
| 18 | +Added :allow_empty_diff option | |
| 19 | + | |
| 20 | +== Oops, need to backfill changelog == | |
| 21 | + | |
| 22 | +== 1.0.1 == | |
| 23 | +* Compatibility with ruby 1.8.6 and 1.9 | |
| 24 | + | |
| 25 | +== 1.0.0 == | |
| 26 | +* HTML output and better documentation | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 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. | ... | ... |
| ... | ... | @@ -0,0 +1,260 @@ |
| 1 | +Diffy - Easy Diffing With Ruby [](http://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 `which` and `diff` on your machine and on | |
| 34 | + your search path. | |
| 35 | + | |
| 36 | + There are two options: | |
| 37 | + | |
| 38 | + 1. install unxutils <http://sourceforge.net/projects/unxutils> | |
| 39 | + | |
| 40 | + note that these tools contain diff 2.7 which has a different handling | |
| 41 | + of whitespace in the diff results. This makes Diffy spec tests | |
| 42 | + yielding one fail on Windows. | |
| 43 | + | |
| 44 | + 2. install these two individually from the gnuwin32 project | |
| 45 | + <http://gnuwin32.sourceforge.net/> | |
| 46 | + | |
| 47 | + note that this delivers diff 2.8 which makes Diffy spec pass | |
| 48 | + even on Windows. | |
| 49 | + | |
| 50 | + | |
| 51 | +2. install the gem by | |
| 52 | + | |
| 53 | + gem install diffy | |
| 54 | + | |
| 55 | + | |
| 56 | +Getting Started | |
| 57 | +--------------- | |
| 58 | + | |
| 59 | +Here's an example of using Diffy to diff two strings | |
| 60 | + | |
| 61 | + $ irb | |
| 62 | + >> string1 = <<-TXT | |
| 63 | + >" Hello how are you | |
| 64 | + >" I'm fine | |
| 65 | + >" That's great | |
| 66 | + >" TXT | |
| 67 | + => "Hello how are you\nI'm fine\nThat's great\n" | |
| 68 | + >> string2 = <<-TXT | |
| 69 | + >" Hello how are you? | |
| 70 | + >" I'm fine | |
| 71 | + >" That's swell | |
| 72 | + >" TXT | |
| 73 | + => "Hello how are you?\nI'm fine\nThat's swell\n" | |
| 74 | + >> puts Diffy::Diff.new(string1, string2) | |
| 75 | + -Hello how are you | |
| 76 | + +Hello how are you? | |
| 77 | + I'm fine | |
| 78 | + -That's great | |
| 79 | + +That's swell | |
| 80 | + | |
| 81 | +HTML Output | |
| 82 | +--------------- | |
| 83 | + | |
| 84 | +Outputing the diff as html is easy too. Here's an example using the | |
| 85 | +`:html_simple` formatter. | |
| 86 | + | |
| 87 | + >> puts Diffy::Diff.new(string1, string2).to_s(:html_simple) | |
| 88 | + <div class="diff"> | |
| 89 | + <ul> | |
| 90 | + <li class="del"><del>Hello how are you</del></li> | |
| 91 | + <li class="ins"><ins>Hello how are you?</ins></li> | |
| 92 | + <li class="unchanged"><span>I'm fine</span></li> | |
| 93 | + <li class="del"><del>That's great</del></li> | |
| 94 | + <li class="ins"><ins>That's swell</ins></li> | |
| 95 | + </ul> | |
| 96 | + </div> | |
| 97 | + | |
| 98 | +The `:html` formatter will give you inline highlighting a la github. | |
| 99 | + | |
| 100 | + >> puts Diffy::Diff.new("foo\n", "Foo\n").to_s(:html) | |
| 101 | + <div class="diff"> | |
| 102 | + <ul> | |
| 103 | + <li class="del"><del><strong>f</strong>oo</del></li> | |
| 104 | + <li class="ins"><ins><strong>F</strong>oo</ins></li> | |
| 105 | + </ul> | |
| 106 | + </div> | |
| 107 | + | |
| 108 | +There's some pretty nice css provided in `Diffy::CSS`. | |
| 109 | + | |
| 110 | + >> puts Diffy::CSS | |
| 111 | + .diff{overflow:auto;} | |
| 112 | + .diff ul{background:#fff;overflow:auto;font-size:13px;list-style:none;margin:0;padding:0;display:table;width:100%;} | |
| 113 | + .diff del, .diff ins{display:block;text-decoration:none;} | |
| 114 | + .diff li{padding:0; display:table-row;margin: 0;height:1em;} | |
| 115 | + .diff li.ins{background:#dfd; color:#080} | |
| 116 | + .diff li.del{background:#fee; color:#b00} | |
| 117 | + .diff li:hover{background:#ffc} | |
| 118 | + /* try 'whitespace:pre;' if you don't want lines to wrap */ | |
| 119 | + .diff del, .diff ins, .diff span{white-space:pre-wrap;font-family:courier;} | |
| 120 | + .diff del strong{font-weight:normal;background:#fcc;} | |
| 121 | + .diff ins strong{font-weight:normal;background:#9f9;} | |
| 122 | + .diff li.diff-comment { display: none; } | |
| 123 | + .diff li.diff-block-info { background: none repeat scroll 0 0 gray; } | |
| 124 | + | |
| 125 | +Other Diff Options | |
| 126 | +------------------ | |
| 127 | + | |
| 128 | +### Diffing files instead of strings | |
| 129 | + | |
| 130 | +You can diff files instead of strings by using the `:source` option. | |
| 131 | + | |
| 132 | + >> puts Diffy::Diff.new('/tmp/foo', '/tmp/bar', :source => 'files') | |
| 133 | + | |
| 134 | +### Full Diff Output | |
| 135 | + | |
| 136 | +By default Diffy removes the superfluous diff output. This is because its | |
| 137 | +default is to show the complete diff'ed file (`diff -U 10000` is the default). | |
| 138 | + | |
| 139 | +Diffy does support full output, just use the `:include_diff_info => true` | |
| 140 | +option when initializing: | |
| 141 | + | |
| 142 | + >> Diffy::Diff.new("foo\nbar\n", "foo\nbar\nbaz\n", :include_diff_info => true).to_s(:text) | |
| 143 | + =>--- /Users/chaffeqa/Projects/stiwiki/tmp/diffy20111116-82153-ie27ex 2011-11-16 20:16:41.000000000 -0500 | |
| 144 | + +++ /Users/chaffeqa/Projects/stiwiki/tmp/diffy20111116-82153-wzrhw5 2011-11-16 20:16:41.000000000 -0500 | |
| 145 | + @@ -1,2 +1,3 @@ | |
| 146 | + foo | |
| 147 | + bar | |
| 148 | + +baz | |
| 149 | + | |
| 150 | +And even deals a bit with the formatting! | |
| 151 | + | |
| 152 | +### Empty Diff Behavior | |
| 153 | + | |
| 154 | +By default Diffy will return empty string if there are no | |
| 155 | +differences in inputs. In previous versions the full text of its first input | |
| 156 | +was returned in this case. To restore this behaviour simply use the | |
| 157 | +`:allow_empty_diff => false` option when initializing. | |
| 158 | + | |
| 159 | +### Plus and Minus symbols in HTML output | |
| 160 | + | |
| 161 | +By default Diffy doesn't include the `+`, `-`, and ` ` at the beginning of line for | |
| 162 | +HTML output. | |
| 163 | + | |
| 164 | +You can use the `:include_plus_and_minus_in_html` option to include those | |
| 165 | +symbols in the output. | |
| 166 | + | |
| 167 | + >> puts Diffy::Diff.new(string1, string2, :include_plus_and_minus_in_html => true).to_s(:html_simple) | |
| 168 | + <div class="diff"> | |
| 169 | + <ul> | |
| 170 | + <li class="del"><del><span class="symbol">-</span>Hello how are you</del></li> | |
| 171 | + <li class="ins"><ins><span class="symbol">+</span>Hello how are you?</ins></li> | |
| 172 | + <li class="unchanged"><span class="symbol"> </span><span>I'm fine</span></li> | |
| 173 | + <li class="del"><del><span class="symbol">-</span>That's great</del></li> | |
| 174 | + <li class="ins"><ins><span class="symbol">+</span>That's swell</ins></li> | |
| 175 | + </ul> | |
| 176 | + </div> | |
| 177 | + | |
| 178 | +### Number of lines of context around changes | |
| 179 | + | |
| 180 | +You can use the `:context` option to override the number of lines of context | |
| 181 | +that are shown around each change (this defaults to 10000 to show the full | |
| 182 | +file). | |
| 183 | + | |
| 184 | + >> puts Diffy::Diff.new("foo\nfoo\nBAR\nbang\nbaz", "foo\nfoo\nbar\nbang\nbaz", :context => 1) | |
| 185 | + foo | |
| 186 | + -BAR | |
| 187 | + +bar | |
| 188 | + bang | |
| 189 | + | |
| 190 | + | |
| 191 | +### Overriding the command line options passed to diff. | |
| 192 | + | |
| 193 | +You can use the `:diff` option to override the command line options that are | |
| 194 | +passed to unix diff. They default to `-U 10000`. This option will noop if | |
| 195 | +combined with the `:context` option. | |
| 196 | + | |
| 197 | + >> puts Diffy::Diff.new(" foo\nbar\n", "foo\nbar\n", :diff => "-w") | |
| 198 | + foo | |
| 199 | + bar | |
| 200 | + | |
| 201 | +Default Diff Options | |
| 202 | +-------------------- | |
| 203 | + | |
| 204 | +You can set the default options for new `Diffy::Diff`s using the | |
| 205 | +`Diffy::Diff.default_options` and `Diffy::Diff.default_options=` methods. | |
| 206 | +Options passed to `Diffy::Diff.new` will be merged into the default options. | |
| 207 | + | |
| 208 | + >> Diffy::Diff.default_options | |
| 209 | + => {:diff=>"-U 10000", :source=>"strings", :include_diff_info=>false, :include_plus_and_minus_in_html=>false} | |
| 210 | + >> Diffy::Diff.default_options.merge!(:source => 'files') | |
| 211 | + => {:diff=>"-U 10000", :source=>"files", :include_diff_info=>false, :include_plus_and_minus_in_html=>false} | |
| 212 | + | |
| 213 | + | |
| 214 | +Custom Formats | |
| 215 | +-------------- | |
| 216 | + | |
| 217 | +Diffy tries to make generating your own custom formatted output easy. | |
| 218 | +`Diffy::Diff` provides an enumerable interface which lets you iterate over | |
| 219 | +lines in the diff. | |
| 220 | + | |
| 221 | + >> Diffy::Diff.new("foo\nbar\n", "foo\nbar\nbaz\n").each do |line| | |
| 222 | + >* case line | |
| 223 | + >> when /^\+/ then puts "line #{line.chomp} added" | |
| 224 | + >> when /^-/ then puts "line #{line.chomp} removed" | |
| 225 | + >> end | |
| 226 | + >> end | |
| 227 | + line +baz added | |
| 228 | + => [" foo\n", " bar\n", "+baz\n"] | |
| 229 | + | |
| 230 | +You can also use `Diffy::Diff#each_chunk` to iterate each grouping of additions, | |
| 231 | +deletions, and unchanged in a diff. | |
| 232 | + | |
| 233 | + >> Diffy::Diff.new("foo\nbar\nbang\nbaz\n", "foo\nbar\nbing\nbong\n").each_chunk.to_a | |
| 234 | + => [" foo\n bar\n", "-bang\n-baz\n", "+bing\n+bong\n"] | |
| 235 | + | |
| 236 | +Use `#map`, `#inject`, or any of Enumerable's methods. Go crazy. | |
| 237 | + | |
| 238 | + | |
| 239 | +Ruby Version Compatibility | |
| 240 | +------------------------- | |
| 241 | + | |
| 242 | +Support for Ruby 1.8.6 was dropped beginning at version 2.0 in order to support | |
| 243 | +the chainable enumerators available in 1.8.7 and 1.9. | |
| 244 | + | |
| 245 | +If you want to use Diffy and Ruby 1.8.6 then: | |
| 246 | + | |
| 247 | + $ gem install diffy -v1.1.0 | |
| 248 | + | |
| 249 | +Testing | |
| 250 | +------------ | |
| 251 | + | |
| 252 | +Diffy includes a full set of rspec tests. When contributing please include | |
| 253 | +tests for your changes. | |
| 254 | + | |
| 255 | +[](http://travis-ci.org/samg/diffy) | |
| 256 | + | |
| 257 | +--------------------------------------------------------------------- | |
| 258 | + | |
| 259 | +Report bugs or request features at http://github.com/samg/diffy/issues | |
| 260 | + | ... | ... |
| ... | ... | @@ -0,0 +1,23 @@ |
| 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", '~> 2.0' | |
| 23 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,12 @@ |
| 1 | +require 'tempfile' | |
| 2 | +require 'erb' | |
| 3 | +require 'rbconfig' | |
| 4 | + | |
| 5 | +module Diffy | |
| 6 | + WINDOWS = (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) | |
| 7 | +end | |
| 8 | +require 'open3' unless Diffy::WINDOWS | |
| 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', 'css') | ... | ... |
| ... | ... | @@ -0,0 +1,17 @@ |
| 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 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,161 @@ |
| 1 | +module Diffy | |
| 2 | + class Diff | |
| 3 | + class << self | |
| 4 | + attr_writer :default_format | |
| 5 | + def default_format | |
| 6 | + @default_format || :text | |
| 7 | + end | |
| 8 | + | |
| 9 | + attr_writer :default_options | |
| 10 | + # default options passed to new Diff objects | |
| 11 | + def default_options | |
| 12 | + @default_options ||= { | |
| 13 | + :diff => '-U 10000', | |
| 14 | + :source => 'strings', | |
| 15 | + :include_diff_info => false, | |
| 16 | + :include_plus_and_minus_in_html => false, | |
| 17 | + :context => nil, | |
| 18 | + :allow_empty_diff => true, | |
| 19 | + } | |
| 20 | + end | |
| 21 | + | |
| 22 | + end | |
| 23 | + include Enumerable | |
| 24 | + attr_reader :string1, :string2, :options, :diff | |
| 25 | + | |
| 26 | + # supported options | |
| 27 | + # +:diff+:: A cli options string passed to diff | |
| 28 | + # +:source+:: Either _strings_ or _files_. Determines whether string1 | |
| 29 | + # and string2 should be interpreted as strings or file paths. | |
| 30 | + # +:include_diff_info+:: Include diff header info | |
| 31 | + # +:include_plus_and_minus_in_html+:: Show the +, -, ' ' at the | |
| 32 | + # beginning of lines in html output. | |
| 33 | + def initialize(string1, string2, options = {}) | |
| 34 | + @options = self.class.default_options.merge(options) | |
| 35 | + if ! ['strings', 'files'].include?(@options[:source]) | |
| 36 | + raise ArgumentError, "Invalid :source option #{@options[:source].inspect}. Supported options are 'strings' and 'files'." | |
| 37 | + end | |
| 38 | + @string1, @string2 = string1, string2 | |
| 39 | + end | |
| 40 | + | |
| 41 | + def diff | |
| 42 | + @diff ||= begin | |
| 43 | + paths = case options[:source] | |
| 44 | + when 'strings' | |
| 45 | + [tempfile(string1), tempfile(string2)] | |
| 46 | + when 'files' | |
| 47 | + [string1, string2] | |
| 48 | + end | |
| 49 | + | |
| 50 | + if WINDOWS | |
| 51 | + # don't use open3 on windows | |
| 52 | + cmd = "\"#{diff_bin}\" #{diff_options.join(' ')} #{paths.map{|s| "\"#{s}\""}.join(' ')}" | |
| 53 | + diff = `#{cmd}` | |
| 54 | + else | |
| 55 | + diff = Open3.popen3(diff_bin, *(diff_options + paths)) { |i, o, e| o.read } | |
| 56 | + end | |
| 57 | + diff.force_encoding('ASCII-8BIT') if diff.respond_to?(:valid_encoding?) && !diff.valid_encoding? | |
| 58 | + if diff =~ /\A\s*\Z/ && !options[:allow_empty_diff] | |
| 59 | + diff = case options[:source] | |
| 60 | + when 'strings' then string1 | |
| 61 | + when 'files' then File.read(string1) | |
| 62 | + end.gsub(/^/, " ") | |
| 63 | + end | |
| 64 | + diff | |
| 65 | + end | |
| 66 | + ensure | |
| 67 | + # unlink the tempfiles explicitly now that the diff is generated | |
| 68 | + Array(@tempfiles).each do |t| | |
| 69 | + begin | |
| 70 | + # check that the path is not nil and file still exists. | |
| 71 | + # REE seems to be very agressive with when it magically removes | |
| 72 | + # tempfiles | |
| 73 | + t.unlink if t.path && File.exist?(t.path) | |
| 74 | + rescue => e | |
| 75 | + warn "#{e.class}: #{e}" | |
| 76 | + warn e.backtrace.join("\n") | |
| 77 | + end | |
| 78 | + end | |
| 79 | + end | |
| 80 | + | |
| 81 | + def each | |
| 82 | + lines = case @options[:include_diff_info] | |
| 83 | + when false then diff.split("\n").reject{|x| x =~ /^(---|\+\+\+|@@|\\\\)/ }.map {|line| line + "\n" } | |
| 84 | + when true then diff.split("\n").map {|line| line + "\n" } | |
| 85 | + end | |
| 86 | + if block_given? | |
| 87 | + lines.each{|line| yield line} | |
| 88 | + else | |
| 89 | + lines.to_enum | |
| 90 | + end | |
| 91 | + end | |
| 92 | + | |
| 93 | + def each_chunk | |
| 94 | + old_state = nil | |
| 95 | + chunks = inject([]) do |cc, line| | |
| 96 | + state = line.each_char.first | |
| 97 | + if state == old_state | |
| 98 | + cc.last << line | |
| 99 | + else | |
| 100 | + cc.push line.dup | |
| 101 | + end | |
| 102 | + old_state = state | |
| 103 | + cc | |
| 104 | + end | |
| 105 | + | |
| 106 | + if block_given? | |
| 107 | + chunks.each{|chunk| yield chunk } | |
| 108 | + else | |
| 109 | + chunks.to_enum | |
| 110 | + end | |
| 111 | + end | |
| 112 | + | |
| 113 | + def tempfile(string) | |
| 114 | + t = Tempfile.new('diffy') | |
| 115 | + # ensure tempfiles aren't unlinked when GC runs by maintaining a | |
| 116 | + # reference to them. | |
| 117 | + @tempfiles ||=[] | |
| 118 | + @tempfiles.push(t) | |
| 119 | + t.print(string) | |
| 120 | + t.flush | |
| 121 | + t.close | |
| 122 | + t.path | |
| 123 | + end | |
| 124 | + | |
| 125 | + def to_s(format = nil) | |
| 126 | + format ||= self.class.default_format | |
| 127 | + formats = Format.instance_methods(false).map{|x| x.to_s} | |
| 128 | + if formats.include? format.to_s | |
| 129 | + enum = self | |
| 130 | + enum.extend Format | |
| 131 | + enum.send format | |
| 132 | + else | |
| 133 | + raise ArgumentError, | |
| 134 | + "Format #{format.inspect} not found in #{formats.inspect}" | |
| 135 | + end | |
| 136 | + end | |
| 137 | + private | |
| 138 | + | |
| 139 | + @@bin = nil | |
| 140 | + def diff_bin | |
| 141 | + return @@bin if @@bin | |
| 142 | + | |
| 143 | + @@bin ||= "" | |
| 144 | + if WINDOWS | |
| 145 | + @@bin = `which diff.exe`.chomp if @@bin.empty? | |
| 146 | + end | |
| 147 | + @@bin = `which diff`.chomp if @@bin.empty? | |
| 148 | + | |
| 149 | + if @@bin.empty? | |
| 150 | + raise "Can't find a diff executable in PATH #{ENV['PATH']}" | |
| 151 | + end | |
| 152 | + @@bin | |
| 153 | + end | |
| 154 | + | |
| 155 | + # options pass to diff program | |
| 156 | + def diff_options | |
| 157 | + Array(options[:context] ? "-U #{options[:context]}" : options[:diff]) | |
| 158 | + end | |
| 159 | + | |
| 160 | + end | |
| 161 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,37 @@ |
| 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 | ... | ... |
| ... | ... | @@ -0,0 +1,133 @@ |
| 1 | +module Diffy | |
| 2 | + class HtmlFormatter | |
| 3 | + def initialize(diff, options = {}) | |
| 4 | + @diff = diff | |
| 5 | + @options = options | |
| 6 | + end | |
| 7 | + | |
| 8 | + def to_s | |
| 9 | + if @options[:highlight_words] | |
| 10 | + wrap_lines(highlighted_words) | |
| 11 | + else | |
| 12 | + wrap_lines(@diff.map{|line| wrap_line(ERB::Util.h(line))}) | |
| 13 | + end | |
| 14 | + end | |
| 15 | + | |
| 16 | + private | |
| 17 | + def wrap_line(line) | |
| 18 | + cleaned = clean_line(line) | |
| 19 | + case line | |
| 20 | + when /^(---|\+\+\+|\\\\)/ | |
| 21 | + ' <li class="diff-comment"><span>' + line.chomp + '</span></li>' | |
| 22 | + when /^\+/ | |
| 23 | + ' <li class="ins"><ins>' + cleaned + '</ins></li>' | |
| 24 | + when /^-/ | |
| 25 | + ' <li class="del"><del>' + cleaned + '</del></li>' | |
| 26 | + when /^ / | |
| 27 | + ' <li class="unchanged"><span>' + cleaned + '</span></li>' | |
| 28 | + when /^@@/ | |
| 29 | + ' <li class="diff-block-info"><span>' + line.chomp + '</span></li>' | |
| 30 | + end | |
| 31 | + end | |
| 32 | + | |
| 33 | + # remove +/- or wrap in html | |
| 34 | + def clean_line(line) | |
| 35 | + if @options[:include_plus_and_minus_in_html] | |
| 36 | + line.sub(/^(.)/, '<span class="symbol">\1</span>') | |
| 37 | + else | |
| 38 | + line.sub(/^./, '') | |
| 39 | + end.chomp | |
| 40 | + end | |
| 41 | + | |
| 42 | + def wrap_lines(lines) | |
| 43 | + if lines.empty? | |
| 44 | + %'<div class="diff"></div>' | |
| 45 | + else | |
| 46 | + %'<div class="diff">\n <ul>\n#{lines.join("\n")}\n </ul>\n</div>\n' | |
| 47 | + end | |
| 48 | + end | |
| 49 | + | |
| 50 | + def highlighted_words | |
| 51 | + chunks = @diff.each_chunk. | |
| 52 | + reject{|c| c == '\ No newline at end of file'"\n"} | |
| 53 | + | |
| 54 | + processed = [] | |
| 55 | + lines = chunks.each_with_index.map do |chunk1, index| | |
| 56 | + next if processed.include? index | |
| 57 | + processed << index | |
| 58 | + chunk1 = chunk1 | |
| 59 | + chunk2 = chunks[index + 1] | |
| 60 | + if not chunk2 | |
| 61 | + next ERB::Util.h(chunk1) | |
| 62 | + end | |
| 63 | + | |
| 64 | + dir1 = chunk1.each_char.first | |
| 65 | + dir2 = chunk2.each_char.first | |
| 66 | + case [dir1, dir2] | |
| 67 | + when ['-', '+'] | |
| 68 | + if chunk1.each_char.take(3).join("") =~ /^(---|\+\+\+|\\\\)/ and | |
| 69 | + chunk2.each_char.take(3).join("") =~ /^(---|\+\+\+|\\\\)/ | |
| 70 | + ERB::Util.h(chunk1) | |
| 71 | + else | |
| 72 | + line_diff = Diffy::Diff.new( | |
| 73 | + split_characters(chunk1), | |
| 74 | + split_characters(chunk2) | |
| 75 | + ) | |
| 76 | + hi1 = reconstruct_characters(line_diff, '-') | |
| 77 | + hi2 = reconstruct_characters(line_diff, '+') | |
| 78 | + processed << (index + 1) | |
| 79 | + [hi1, hi2] | |
| 80 | + end | |
| 81 | + else | |
| 82 | + ERB::Util.h(chunk1) | |
| 83 | + end | |
| 84 | + end.flatten | |
| 85 | + lines.map{|line| line.each_line.map(&:chomp).to_a if line }.flatten.compact. | |
| 86 | + map{|line|wrap_line(line) }.compact | |
| 87 | + end | |
| 88 | + | |
| 89 | + def split_characters(chunk) | |
| 90 | + chunk.gsub(/^./, '').each_line.map do |line| | |
| 91 | + chars = line.sub(/([\r\n]$)/, '').split('') | |
| 92 | + # add escaped newlines | |
| 93 | + chars += [($1 || "\n").gsub("\r", '\r').gsub("\n", '\n')] | |
| 94 | + chars.map{|chr| ERB::Util.h(chr) } | |
| 95 | + end.flatten.join("\n") + "\n" | |
| 96 | + end | |
| 97 | + | |
| 98 | + def reconstruct_characters(line_diff, type) | |
| 99 | + enum = line_diff.each_chunk | |
| 100 | + enum.each_with_index.map do |l, i| | |
| 101 | + re = /(^|\\n)#{Regexp.escape(type)}/ | |
| 102 | + case l | |
| 103 | + when re | |
| 104 | + highlight(l) | |
| 105 | + when /^ / | |
| 106 | + if i > 1 and enum.to_a[i+1] and l.each_line.to_a.size < 4 | |
| 107 | + highlight(l) | |
| 108 | + else | |
| 109 | + l.gsub(/^./, '').gsub("\n", ''). | |
| 110 | + gsub('\r', "\r").gsub('\n', "\n") | |
| 111 | + end | |
| 112 | + end | |
| 113 | + end.join('').split("\n").map do |l| | |
| 114 | + type + l.gsub('</strong><strong>' , '') | |
| 115 | + end | |
| 116 | + end | |
| 117 | + | |
| 118 | + def highlight(lines) | |
| 119 | + "<strong>" + | |
| 120 | + lines. | |
| 121 | + # strip diff tokens (e.g. +,-,etc.) | |
| 122 | + gsub(/(^|\\n)./, ''). | |
| 123 | + # mark line boundaries from higher level line diff | |
| 124 | + # html is all escaped so using brackets should make this safe. | |
| 125 | + gsub('\n', '<LINE_BOUNDARY>'). | |
| 126 | + # join characters back by stripping out newlines | |
| 127 | + gsub("\n", ''). | |
| 128 | + # close and reopen strong tags. we don't want inline elements | |
| 129 | + # spanning block elements which get added later. | |
| 130 | + gsub('<LINE_BOUNDARY>',"</strong>\n<strong>") + "</strong>" | |
| 131 | + end | |
| 132 | + end | |
| 133 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,46 @@ |
| 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 | + | ... | ... |
| ... | ... | @@ -0,0 +1,576 @@ |
| 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 | + Diffy::Diff.new(path1, path2, :source => 'files').to_s.should == <<-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 | + Diffy::Diff.new(path1, path2, :source => 'files').to_s.should == <<-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 | + Diffy::Diff.new(path1, path2, :source => 'files').to_s.should == <<-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 | + Diffy::Diff.new(@path1, @path2, :source => 'files', :allow_empty_diff => false). | |
| 69 | + to_s.should == <<-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 | + Diffy::Diff.new(@path1, @path2, :source => 'files', :allow_empty_diff => true).to_s.should == '' | |
| 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 | + Diffy::Diff.new(@path1, @path2, :source => 'files').to_s.should == <<-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 | + Diffy::Diff.new(@path1, @path2, :source => 'files').to_s.should == 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 | + d = ::Diffy::Diff.new("a", "b").to_s | |
| 119 | + after_tmpfiles = Dir.entries(Dir.tmpdir) | |
| 120 | + before_tmpfiles.should =~ after_tmpfiles | |
| 121 | + end | |
| 122 | + | |
| 123 | + it "should still be able to generate multiple diffs" do | |
| 124 | + d = ::Diffy::Diff.new("a", "b") | |
| 125 | + d.to_s.should be_a String | |
| 126 | + d.to_s(:html).should 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 | + diff.to_s.should == <<-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 | + @diffy.options[:include_plus_and_minus_in_html].should == 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 | + @diffy.options[:include_plus_and_minus_in_html].should == 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 | + output.should == <<-HTML | |
| 158 | +<div class="diff"> | |
| 159 | + <ul> | |
| 160 | + <li class="unchanged"><span><span class="symbol"> </span>foo</span></li> | |
| 161 | + <li class="del"><del><span class="symbol">-</span>bar</del></li> | |
| 162 | + <li class="unchanged"><span><span class="symbol"> </span>bang</span></li> | |
| 163 | + </ul> | |
| 164 | +</div> | |
| 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 | + output.should == <<-HTML | |
| 172 | +<div class="diff"> | |
| 173 | + <ul> | |
| 174 | + <li class="unchanged"><span><span class="symbol"> </span>foo</span></li> | |
| 175 | + <li class="del"><del><span class="symbol">-</span>ba<strong>r</strong></del></li> | |
| 176 | + <li class="ins"><ins><span class="symbol">+</span><strong>a</strong>ba</ins></li> | |
| 177 | + <li class="unchanged"><span><span class="symbol"> </span>bang</span></li> | |
| 178 | + </ul> | |
| 179 | +</div> | |
| 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 | + @diffy.options[:include_diff_info].should == 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 | + @diffy.options[:include_diff_info].should == 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 | + output.to_s.should match( /@@/) | |
| 201 | + output.should match( /---/) | |
| 202 | + output.should 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 | + output.should match( /\e\[0m\n\e\[36m\@\@/ ) | |
| 209 | + output.to_s.should match( /\e\[90m---/) | |
| 210 | + output.to_s.should 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 | + output.split("\n").should include( " <li class=\"diff-block-info\"><span>@@ -1,3 +1,2 @@</span></li>" ) | |
| 216 | + output.should include( "<li class=\"diff-comment\"><span>---") | |
| 217 | + output.should include( "<li class=\"diff-comment\"><span>+++") | |
| 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 | + @diff.to_s.should == <<-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 | + @diff.to_s.should == <<-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 | + Diffy::Diff.new(@string1, @string2, :allow_empty_diff => false).to_s.should == <<-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 | + Diffy::Diff.new(@string1, @string2).to_s.should == <<-DIFF | |
| 264 | + foo | |
| 265 | +-bar | |
| 266 | + bang | |
| 267 | + DIFF | |
| 268 | + end | |
| 269 | + | |
| 270 | + it "to_s should accept a format key" do | |
| 271 | + Diffy::Diff.new(@string1, @string2).to_s(:color). | |
| 272 | + should == " 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 | + Diffy::Diff.new(@string1, @string2).to_s. | |
| 279 | + should == " 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 | + Diffy::Diff.new(@string1, @string2).to_s. | |
| 287 | + should include('@@ -1,3 +1,2 @@') | |
| 288 | + Diffy::Diff.default_options = old_options | |
| 289 | + end | |
| 290 | + | |
| 291 | + it "should show one line added" do | |
| 292 | + Diffy::Diff.new(@string2, @string1).to_s. | |
| 293 | + should == <<-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 | + Diffy::Diff.new(@string1, @string2).to_s.should == <<-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 | + Diffy::Diff.new(@string1, @string2).to_s.should == <<-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 | + @diff.to_s.should == <<-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 | + @diff.to_s(:html_simple).should == <<-HTML | |
| 354 | +<div class="diff"> | |
| 355 | + <ul> | |
| 356 | + <li class="del"><del>foo</del></li> | |
| 357 | + <li class="ins"><ins>one</ins></li> | |
| 358 | + <li class="ins"><ins>two</ins></li> | |
| 359 | + <li class="ins"><ins>three</ins></li> | |
| 360 | + <li class="unchanged"><span>bar</span></li> | |
| 361 | + <li class="unchanged"><span>bang</span></li> | |
| 362 | + <li class="del"><del>woot</del></li> | |
| 363 | + <li class="ins"><ins>baz</ins></li> | |
| 364 | + </ul> | |
| 365 | +</div> | |
| 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 | + @diff.to_s.should == <<-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<script>\n" | |
| 387 | + @string2 = "hahaha\nfruit flies like a banana\nbang baz\n<script>\n" | |
| 388 | + @diff = Diffy::Diff.new(@string1, @string2) | |
| 389 | + html = <<-HTML | |
| 390 | +<div class="diff"> | |
| 391 | + <ul> | |
| 392 | + <li class="unchanged"><span>hahaha</span></li> | |
| 393 | + <li class="del"><del><strong>time</strong> flies like a<strong>n arrow</strong></del></li> | |
| 394 | + <li class="del"><del><strong>foo bar</strong></del></li> | |
| 395 | + <li class="ins"><ins><strong>fruit</strong> flies like a<strong> banana</strong></ins></li> | |
| 396 | + <li class="unchanged"><span>bang baz</span></li> | |
| 397 | + <li class="unchanged"><span><script></span></li> | |
| 398 | + </ul> | |
| 399 | +</div> | |
| 400 | + HTML | |
| 401 | + @diff.to_s(:html).should == html | |
| 402 | + end | |
| 403 | + | |
| 404 | + it "should highlight the changes within the line" do | |
| 405 | + @string1 = "hahaha\ntime flies like an arrow\nfoo bar\nbang baz\n" | |
| 406 | + @string2 = "hahaha\nfruit flies like a banana\nbang baz\n" | |
| 407 | + @diff = Diffy::Diff.new(@string1, @string2) | |
| 408 | + html = <<-HTML | |
| 409 | +<div class="diff"> | |
| 410 | + <ul> | |
| 411 | + <li class="unchanged"><span>hahaha</span></li> | |
| 412 | + <li class="del"><del><strong>time</strong> flies like a<strong>n arrow</strong></del></li> | |
| 413 | + <li class="del"><del><strong>foo bar</strong></del></li> | |
| 414 | + <li class="ins"><ins><strong>fruit</strong> flies like a<strong> banana</strong></ins></li> | |
| 415 | + <li class="unchanged"><span>bang baz</span></li> | |
| 416 | + </ul> | |
| 417 | +</div> | |
| 418 | + HTML | |
| 419 | + @diff.to_s(:html).should == html | |
| 420 | + end | |
| 421 | + | |
| 422 | + it "should not duplicate some lines" do | |
| 423 | + @string1 = "hahaha\ntime flies like an arrow\n" | |
| 424 | + @string2 = "hahaha\nfruit flies like a banana\nbang baz" | |
| 425 | + @diff = Diffy::Diff.new(@string1, @string2) | |
| 426 | + html = <<-HTML | |
| 427 | +<div class="diff"> | |
| 428 | + <ul> | |
| 429 | + <li class="unchanged"><span>hahaha</span></li> | |
| 430 | + <li class="del"><del><strong>time</strong> flies like a<strong>n arrow</strong></del></li> | |
| 431 | + <li class="ins"><ins><strong>fruit</strong> flies like a<strong> banana</strong></ins></li> | |
| 432 | + <li class="ins"><ins><strong>bang baz</strong></ins></li> | |
| 433 | + </ul> | |
| 434 | +</div> | |
| 435 | + HTML | |
| 436 | + @diff.to_s(:html).should == html | |
| 437 | + end | |
| 438 | + | |
| 439 | + it "should escape html" do | |
| 440 | + @string1 = "ha<br>haha\ntime flies like an arrow\n" | |
| 441 | + @string2 = "ha<br>haha\nfruit flies like a banana\nbang baz" | |
| 442 | + @diff = Diffy::Diff.new(@string1, @string2) | |
| 443 | + html = <<-HTML | |
| 444 | +<div class="diff"> | |
| 445 | + <ul> | |
| 446 | + <li class="unchanged"><span>ha<br>haha</span></li> | |
| 447 | + <li class="del"><del><strong>time</strong> flies like a<strong>n arrow</strong></del></li> | |
| 448 | + <li class="ins"><ins><strong>fruit</strong> flies like a<strong> banana</strong></ins></li> | |
| 449 | + <li class="ins"><ins><strong>bang baz</strong></ins></li> | |
| 450 | + </ul> | |
| 451 | +</div> | |
| 452 | + HTML | |
| 453 | + @diff.to_s(:html).should == html | |
| 454 | + end | |
| 455 | + | |
| 456 | + it "should not double escape html in wierd edge cases" do | |
| 457 | + @string1 = "preface = (! title .)+ title &{YYACCEPT}\n" | |
| 458 | + @string2 = "preface = << (! title .)+ title >> &{YYACCEPT}\n" | |
| 459 | + @diff = Diffy::Diff.new @string1, @string2 | |
| 460 | + html = <<-HTML | |
| 461 | +<div class="diff"> | |
| 462 | + <ul> | |
| 463 | + <li class="del"><del>preface = (! title .)+ title &{YYACCEPT}</del></li> | |
| 464 | + <li class="ins"><ins>preface = <strong><< </strong>(! title .)+ title <strong>>> </strong>&{YYACCEPT}</ins></li> | |
| 465 | + </ul> | |
| 466 | +</div> | |
| 467 | + HTML | |
| 468 | + @diff.to_s(:html).should == html | |
| 469 | + end | |
| 470 | + | |
| 471 | + it "should highlight the changes within the line with windows style line breaks" do | |
| 472 | + @string1 = "hahaha\r\ntime flies like an arrow\r\nfoo bar\r\nbang baz\n" | |
| 473 | + @string2 = "hahaha\r\nfruit flies like a banana\r\nbang baz\n" | |
| 474 | + @diff = Diffy::Diff.new(@string1, @string2) | |
| 475 | + html = <<-HTML | |
| 476 | +<div class="diff"> | |
| 477 | + <ul> | |
| 478 | + <li class="unchanged"><span>hahaha</span></li> | |
| 479 | + <li class="del"><del><strong>time</strong> flies like a<strong>n arrow</strong></del></li> | |
| 480 | + <li class="del"><del><strong>foo bar</strong></del></li> | |
| 481 | + <li class="ins"><ins><strong>fruit</strong> flies like a<strong> banana</strong></ins></li> | |
| 482 | + <li class="unchanged"><span>bang baz</span></li> | |
| 483 | + </ul> | |
| 484 | +</div> | |
| 485 | + HTML | |
| 486 | + @diff.to_s(:html).should == html | |
| 487 | + end | |
| 488 | + | |
| 489 | + it "should treat unix vs windows newlines as differences" do | |
| 490 | + @diff = Diffy::Diff.new("one\ntwo\nthree\n", "one\r\ntwo\r\nthree\r\n") | |
| 491 | + html = <<-HTML | |
| 492 | +<div class="diff"> | |
| 493 | + <ul> | |
| 494 | + <li class="del"><del>one</del></li> | |
| 495 | + <li class="del"><del>two</del></li> | |
| 496 | + <li class="del"><del>three</del></li> | |
| 497 | + <li class="ins"><ins>one<strong></strong></ins></li> | |
| 498 | + <li class="ins"><ins>two<strong></strong></ins></li> | |
| 499 | + <li class="ins"><ins>three<strong></strong></ins></li> | |
| 500 | + </ul> | |
| 501 | +</div> | |
| 502 | + HTML | |
| 503 | + @diff.to_s(:html).should == html | |
| 504 | + end | |
| 505 | + | |
| 506 | + describe 'with lines that include \n' do | |
| 507 | + before do | |
| 508 | + string1 = 'a\nb'"\n" | |
| 509 | + | |
| 510 | + string2 = 'acb'"\n" | |
| 511 | + @string1, @string2 = string1, string2 | |
| 512 | + end | |
| 513 | + | |
| 514 | + it "should not leave lines out" do | |
| 515 | + Diffy::Diff.new(@string1, @string2 ).to_s(:html).should == <<-DIFF | |
| 516 | +<div class="diff"> | |
| 517 | + <ul> | |
| 518 | + <li class="del"><del>a<strong>\\n</strong>b</del></li> | |
| 519 | + <li class="ins"><ins>a<strong>c</strong>b</ins></li> | |
| 520 | + </ul> | |
| 521 | +</div> | |
| 522 | + DIFF | |
| 523 | + end | |
| 524 | + end | |
| 525 | + | |
| 526 | + it "should do highlighting on the last line when there's no trailing newlines" do | |
| 527 | + s1 = "foo\nbar\nbang" | |
| 528 | + s2 = "foo\nbar\nbangleize" | |
| 529 | + Diffy::Diff.new(s1,s2).to_s(:html).should == <<-DIFF | |
| 530 | +<div class="diff"> | |
| 531 | + <ul> | |
| 532 | + <li class="unchanged"><span>foo</span></li> | |
| 533 | + <li class="unchanged"><span>bar</span></li> | |
| 534 | + <li class="del"><del>bang</del></li> | |
| 535 | + <li class="ins"><ins>bang<strong>leize</strong></ins></li> | |
| 536 | + </ul> | |
| 537 | +</div> | |
| 538 | + DIFF | |
| 539 | + end | |
| 540 | + end | |
| 541 | + | |
| 542 | + it "should escape diffed html in html output" do | |
| 543 | + diff = Diffy::Diff.new("<script>alert('bar')</script>", "<script>alert('foo')</script>").to_s(:html) | |
| 544 | + diff.should include('<script>') | |
| 545 | + diff.should_not include('<script>') | |
| 546 | + end | |
| 547 | + | |
| 548 | + it "should be easy to generate custom format" do | |
| 549 | + Diffy::Diff.new("foo\nbar\n", "foo\nbar\nbaz\n").map do |line| | |
| 550 | + case line | |
| 551 | + when /^\+/ then "line #{line.chomp} added" | |
| 552 | + when /^-/ then "line #{line.chomp} removed" | |
| 553 | + end | |
| 554 | + end.compact.join.should == "line +baz added" | |
| 555 | + end | |
| 556 | + | |
| 557 | + it "should let you iterate over chunks instead of lines" do | |
| 558 | + Diffy::Diff.new("foo\nbar\n", "foo\nbar\nbaz\n").each_chunk.map do |chunk| | |
| 559 | + chunk | |
| 560 | + end.should == [" foo\n bar\n", "+baz\n"] | |
| 561 | + end | |
| 562 | + | |
| 563 | + it "should allow chaining enumerable methods" do | |
| 564 | + Diffy::Diff.new("foo\nbar\n", "foo\nbar\nbaz\n").each.map do |line| | |
| 565 | + line | |
| 566 | + end.should == [" foo\n", " bar\n", "+baz\n"] | |
| 567 | + end | |
| 568 | + end | |
| 569 | +end | |
| 570 | + | |
| 571 | +describe 'Diffy::CSS' do | |
| 572 | + it "should be some css" do | |
| 573 | + Diffy::CSS.should include 'diff{overflow:auto;}' | |
| 574 | + end | |
| 575 | +end | |
| 576 | + | ... | ... |