Commit d2b7f117e224639e566f57beadd551c7e8e0adc5
1 parent
4c5fccb6
Exists in
master
and in
29 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 | + | ... | ... |