monkeyhelper-diffrenderer 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ nbproject
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Rattle http://www.rattlecentral.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ = DiffRenderer
2
+
3
+ DiffRenderer creates pretty HTML diffs for text and bits of html documents.
4
+
5
+ == Example Usage
6
+
7
+ <pre>
8
+ #!/usr/bin/ruby
9
+
10
+ require 'rubygems'
11
+ require 'diffrenderer'
12
+
13
+ old_text = ['This was some text']
14
+ new_text = ['This is some text']
15
+
16
+ puts DiffRenderer.new(old_text, new_text).to_html
17
+ </pre>
18
+
19
+ <pre>
20
+ <p>
21
+ This <span class="removed">was</span> <span class="added">is</span> some text
22
+ </p>
23
+ </pre>
24
+
25
+ See the examples directory for more details.
26
+
27
+ == Copyright
28
+
29
+ Copyright (c) 2009 Rattle http://www.rattlecentral.com. See LICENSE for details.
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "diffrenderer"
8
+ gem.summary = %Q{TODO}
9
+ gem.email = "robl@monkeyhelper.com"
10
+ gem.homepage = "http://github.com/monkeyhelper/diffrenderer"
11
+ gem.authors = ["robl"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+
40
+ task :default => :test
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "diffrenderer #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
56
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{diffrenderer}
5
+ s.version = "0.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["robl"]
9
+ s.date = %q{2009-06-03}
10
+ s.email = %q{robl@monkeyhelper.com}
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".document",
17
+ ".gitignore",
18
+ "LICENSE",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "diffrenderer.gemspec",
23
+ "examples/diff-html.rb",
24
+ "examples/diff-text.rb",
25
+ "lib/diffrenderer.rb",
26
+ "lib/diffrenderer/paragraph_block.rb",
27
+ "lib/word_run_diff_lcs.rb",
28
+ "test/diffrenderer_test.rb",
29
+ "test/test_helper.rb"
30
+ ]
31
+ s.has_rdoc = true
32
+ s.homepage = %q{http://github.com/monkeyhelper/diffrenderer}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.3.1}
36
+ s.summary = %q{TODO}
37
+ s.test_files = [
38
+ "test/diffrenderer_test.rb",
39
+ "test/test_helper.rb",
40
+ "examples/diff-text.rb",
41
+ "examples/diff-html.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 2
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ else
50
+ end
51
+ else
52
+ end
53
+ end
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'diffrenderer'
5
+
6
+ old_html= <<OLDHTML
7
+ <h2 class="title">Hello, we're Rattle</h2>
8
+ <p class="first">We're not your average digital agency. We don't do marketing, we don't do SEO, and we don't do flim flam. We just research and develop social web stuff that works.</p>
9
+
10
+ <p>Our expert knowledge allows us to develop the right social web applications for your business and your audience. Clients include the <a href="http://www.rattlecentral.com/case-studies/followme.html">BBC</a>, the <a href="http://www.rattlecentral.com/case-studies/talk-science.html">Science Museum</a>, <a href="http://www.rattlecentral.com/case-studies/a-randomers-guide-to-teenagers.html">Channel 4</a> and <a href="http://www.rattlecentral.com/case-studies/ghd.html">ghd</a>. We've also produced <a href="http://www.rattlecentral.com/case-studies/folksy.html">Folksy</a>, a website for designers and makers.</p>
11
+ OLDHTML
12
+
13
+ new_html= <<NEWHTML
14
+ <h2 class="title">Hello, we're Rattle</h2>
15
+ <p class="first">We're not your average digital agency. We don't do marketing, we don't do SEO, and we don't do flim flam. We just research and develop social web stuff that works.</p>
16
+
17
+ <p>Our expert knowledge allows us to develop the right social web applications for your business and your audience. Clients include the <a href="http://www.rattlecentral.com/case-studies/followme.html">BBC</a>, the <a href="http://www.rattlecentral.com/case-studies/talk-science.html">Science Museum</a>, <a href="http://www.rattlecentral.com/case-studies/a-randomers-guide-to-teenagers.html">Channel 4</a> and <a href="http://www.rattlecentral.com/case-studies/ghd.html">ghd</a>. We've also produced <a href="http://www.rattlecentral.com/case-studies/folksy.html">Folksy</a>, a website for designers and makers and we give away code too !</p>
18
+ NEWHTML
19
+
20
+
21
+ diffed = DiffRenderer.new(DiffRenderer.process_html(old_html), DiffRenderer.process_html(new_html)).to_html
22
+
23
+ out = <<HTML
24
+ <html>
25
+ <head>
26
+ <title>DiffRenderer Example</title>
27
+
28
+ <style type="text/css">
29
+ <!--
30
+ #content {
31
+ float: left;
32
+ width: 560px;
33
+ background: #FFF;
34
+ margin-bottom: 10px;
35
+ margin-left: 10px;
36
+ padding: 1em 20px 0px;
37
+ }
38
+ #content p.added {
39
+ background: #66ff66;
40
+ margin-top: 0;
41
+ margin-left: -0.5em;
42
+ margin-right: -0.5em;
43
+ padding: 0.5em;
44
+ }
45
+ #content p.removed {
46
+ background: #ff6666;
47
+ text-decoration: line-through;
48
+ padding: 0.5em;
49
+ margin-top: 0;
50
+ margin-left: -0.5em;
51
+ margin-right: -0.5em;
52
+ }
53
+ #content p.replaced {
54
+ margin-top: 0;
55
+ margin-bottom: 0;
56
+ margin-left: -0.5em;
57
+ margin-right: -0.5em;
58
+ }
59
+ #content span.added {
60
+ background: #66ff66;
61
+ padding: 2px 2px;
62
+ }
63
+ #content span.removed {
64
+ background: #ff6666;
65
+ padding: 2px 2px;
66
+ text-decoration: line-through;
67
+ }
68
+ -->
69
+ </style>
70
+
71
+ </head>
72
+
73
+ <body>
74
+ <div id="content">
75
+ #{diffed}
76
+ </div>
77
+ </body>
78
+ </html>
79
+
80
+ HTML
81
+
82
+ puts out
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'diffrenderer'
5
+
6
+ old_text = ['This was some text']
7
+ new_text = ['This is some text']
8
+
9
+ diffed = DiffRenderer.new(old_text, new_text).to_html
10
+
11
+ out = <<HTML
12
+ <html>
13
+ <head>
14
+ <title>DiffRenderer Example</title>
15
+
16
+ <style type="text/css">
17
+ <!--
18
+ #content {
19
+ float: left;
20
+ width: 560px;
21
+ background: #FFF;
22
+ margin-bottom: 10px;
23
+ margin-left: 10px;
24
+ padding: 1em 20px 0px;
25
+ }
26
+ #content p.added {
27
+ background: #66ff66;
28
+ margin-top: 0;
29
+ margin-left: -0.5em;
30
+ margin-right: -0.5em;
31
+ padding: 0.5em;
32
+ }
33
+ #content p.removed {
34
+ background: #ff6666;
35
+ text-decoration: line-through;
36
+ padding: 0.5em;
37
+ margin-top: 0;
38
+ margin-left: -0.5em;
39
+ margin-right: -0.5em;
40
+ }
41
+ #content p.replaced {
42
+ margin-top: 0;
43
+ margin-bottom: 0;
44
+ margin-left: -0.5em;
45
+ margin-right: -0.5em;
46
+ }
47
+ #content span.added {
48
+ background: #66ff66;
49
+ padding: 2px 2px;
50
+ }
51
+ #content span.removed {
52
+ background: #ff6666;
53
+ padding: 2px 2px;
54
+ text-decoration: line-through;
55
+ }
56
+ -->
57
+ </style>
58
+
59
+ </head>
60
+
61
+ <body>
62
+ <div id="content">
63
+ #{diffed}
64
+ </div>
65
+ </body>
66
+ </html>
67
+
68
+ HTML
69
+
70
+ puts out
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ gem 'diff-lcs', '~> 1.1', '>= 1.1.2'
3
+ require 'diff/lcs'
4
+ require 'hpricot'
5
+ require 'pp'
6
+
7
+ require 'word_run_diff_lcs'
8
+
9
+ %w(paragraph_block).each do |file|
10
+ require File.join(File.dirname(__FILE__), 'diffrenderer', file)
11
+ end
12
+
13
+ class DiffRenderer
14
+
15
+ UNWANTED_TAGS = %w[ a ul ol ].freeze
16
+
17
+ def initialize(paragraph_list_a, paragraph_list_b)
18
+ @paragraph_list_a, @paragraph_list_b = paragraph_list_a, paragraph_list_b
19
+ end
20
+
21
+ def to_html
22
+ diff = Diff::LCS.sdiff(@paragraph_list_a,@paragraph_list_b)
23
+ diff.map { |context_change| ParagraphBlock.new(context_change) }.join
24
+ end
25
+
26
+ def self.process_html(h)
27
+ doc = Hpricot(h)
28
+
29
+ UNWANTED_TAGS.each { |tag| (doc / tag).remove }
30
+ (doc / "p").map { |elem|
31
+ # Silence "nested" <p> tags as Hpricot does not implicitly close <p> tags
32
+ (elem / "p").each do |e|
33
+ class << e; def inner_text; nil; end; end
34
+ end
35
+
36
+ text = elem.inner_text.strip
37
+
38
+ (elem / "p").each do |e|
39
+ class << e; remove_method :inner_text; end
40
+ end
41
+ text
42
+ }.reject { |text|
43
+ text == ""
44
+ }
45
+ end
46
+
47
+ end
48
+
49
+
@@ -0,0 +1,168 @@
1
+ class DiffRenderer
2
+
3
+ class ParagraphBlock
4
+ def self.new(context_change)
5
+ case context_change.action
6
+ when "=" then PlainParagraph
7
+ when "-" then RemovedParagraph
8
+ when "+" then AddedParagraph
9
+ when "!" then ChangedParagraph
10
+ end.new(context_change)
11
+ end
12
+
13
+ class HtmlParagraph
14
+ def initialize(content, class_string = "")
15
+ @content = content
16
+ @class_string = class_string
17
+ end
18
+
19
+ def to_s
20
+ str = "<p#{@class_string}>\n"
21
+ str << " " << @content
22
+ str << "\n</p>\n"
23
+ end
24
+ end
25
+
26
+ class BaseParagraph
27
+ def initialize(context_change)
28
+ @context_change = context_change
29
+ end
30
+
31
+ def to_s
32
+ HtmlParagraph.new(content, html_class_string).to_s
33
+ end
34
+ end
35
+ class PlainParagraph < BaseParagraph
36
+ def html_class_string
37
+ ""
38
+ end
39
+
40
+ def content
41
+ @context_change.old_element
42
+ end
43
+ end
44
+
45
+ class ClassfulParagraph < BaseParagraph
46
+ def html_class_string
47
+ %Q{ class="#{classes.join(" ")}"}
48
+ end
49
+ end
50
+
51
+ class DeletedParagraph < ClassfulParagraph
52
+ def content
53
+ @context_change.old_element
54
+ end
55
+ end
56
+
57
+ class RemovedParagraph < DeletedParagraph
58
+ def classes
59
+ %w[ removed ]
60
+ end
61
+ end
62
+
63
+ class ReplacedParagraph < DeletedParagraph
64
+ def classes
65
+ %w[ removed replaced ]
66
+ end
67
+ end
68
+
69
+ class AddedParagraph < ClassfulParagraph
70
+ def classes
71
+ %w[ added ]
72
+ end
73
+ def content
74
+ @context_change.new_element
75
+ end
76
+ end
77
+
78
+ class ChangedParagraph
79
+ include WordRunDiffLCS
80
+
81
+ class HtmlText
82
+ class HtmlSpan
83
+ def initialize(content, class_string)
84
+ @content = content
85
+ @class_string = class_string
86
+ end
87
+
88
+ def to_s
89
+ %Q{<span class="#@class_string">#@content</span>}
90
+ end
91
+ end
92
+
93
+ def initialize(context_change)
94
+ @context_change = context_change
95
+ end
96
+
97
+ def to_s
98
+ case @context_change.action
99
+ when "=" then old_text
100
+ when "-" then removed_span
101
+ when "+" then added_span
102
+ when "!" then "#{removed_span} #{added_span}"
103
+ end.to_s
104
+ end
105
+
106
+ private
107
+
108
+ def old_text
109
+ @context_change.old_element
110
+ end
111
+ def removed_span
112
+ HtmlSpan.new(@context_change.old_element, "removed")
113
+ end
114
+
115
+ def added_span
116
+ HtmlSpan.new(@context_change.new_element, "added")
117
+ end
118
+ end
119
+
120
+ def initialize(context_change)
121
+ @context_change = context_change
122
+ end
123
+
124
+ def to_s
125
+ if significant_change? && !all_additions_or_removals?
126
+ removed_paragraph = ReplacedParagraph.new(@context_change)
127
+ added_paragraph = AddedParagraph.new(@context_change)
128
+ removed_paragraph.to_s + added_paragraph.to_s
129
+ else
130
+ words = word_run_level_sdiff.map { |context_change|
131
+ HtmlText.new(context_change)
132
+ }
133
+ HtmlParagraph.new(words.join(" ")).to_s
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ WORD_CHANGE_THRESHOLD = 0.25
140
+
141
+ def significant_change?
142
+ count = word_level_sdiff.count { |context_change| context_change.action != "=" }
143
+ count.to_f / word_level_sdiff.length > WORD_CHANGE_THRESHOLD
144
+ end
145
+
146
+ def all_additions_or_removals?
147
+ word_level_sdiff.all? { |context_change| context_change.action =~ /^[+=]$/ } ||
148
+ word_level_sdiff.all? { |context_change| context_change.action =~ /^[-=]$/ }
149
+ end
150
+
151
+ def word_run_level_sdiff
152
+ word_run_sdiff = WordRunContextChangeSequence.new
153
+
154
+ word_level_sdiff.each do |context_change|
155
+ word_run_sdiff << context_change
156
+ end
157
+
158
+ word_run_sdiff
159
+ end
160
+
161
+ def word_level_sdiff
162
+ Diff::LCS.sdiff(@context_change.old_element.split, @context_change.new_element.split)
163
+ end
164
+ end
165
+ end
166
+
167
+ end
168
+
@@ -0,0 +1,57 @@
1
+ module WordRunDiffLCS
2
+
3
+ class WordRunContextChange
4
+ attr_reader :action, :old_element, :new_element
5
+
6
+ def initialize(change_context)
7
+ @action = change_context.action
8
+ @old_element = change_context.old_element
9
+ @new_element = change_context.new_element
10
+ end
11
+
12
+ def merge!(change_context)
13
+ return false unless merge_possible_with?(change_context)
14
+ @old_element << " #{change_context.old_element}" if change_context.old_element
15
+ @new_element << " #{change_context.new_element}" if change_context.new_element
16
+ true
17
+ end
18
+
19
+ private
20
+
21
+ def merge_possible_with?(other)
22
+ return true if action == other.action
23
+ return true if action == "!" && other.action != "="
24
+ false
25
+ end
26
+ end
27
+
28
+ class WordRunContextChangeSequence
29
+ def initialize
30
+ @word_run_context_changes = [ ]
31
+ end
32
+
33
+ def <<(diff_lcs_context_change)
34
+ word_run_context_change = WordRunContextChange.new(diff_lcs_context_change)
35
+
36
+ if empty?
37
+ @word_run_context_changes << word_run_context_change
38
+ else
39
+ @word_run_context_changes << word_run_context_change unless last.merge!(word_run_context_change)
40
+ end
41
+ end
42
+ def map(&block)
43
+ @word_run_context_changes.map(&block)
44
+ end
45
+
46
+ private
47
+
48
+ def empty?
49
+ @word_run_context_changes.empty?
50
+ end
51
+
52
+ def last
53
+ @word_run_context_changes.last
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class DiffRendererTest < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'diffrenderer'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: monkeyhelper-diffrenderer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - robl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-03 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: robl@monkeyhelper.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .document
27
+ - .gitignore
28
+ - LICENSE
29
+ - README.rdoc
30
+ - Rakefile
31
+ - VERSION
32
+ - diffrenderer.gemspec
33
+ - examples/diff-html.rb
34
+ - examples/diff-text.rb
35
+ - lib/diffrenderer.rb
36
+ - lib/diffrenderer/paragraph_block.rb
37
+ - lib/word_run_diff_lcs.rb
38
+ - test/diffrenderer_test.rb
39
+ - test/test_helper.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/monkeyhelper/diffrenderer
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --charset=UTF-8
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.2.0
63
+ signing_key:
64
+ specification_version: 2
65
+ summary: TODO
66
+ test_files:
67
+ - test/diffrenderer_test.rb
68
+ - test/test_helper.rb
69
+ - examples/diff-text.rb
70
+ - examples/diff-html.rb