mail_diff 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ilya Sabanin
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.
data/README.rdoc ADDED
@@ -0,0 +1,27 @@
1
+ = Mail Diff
2
+
3
+ Similar to PrettyDiff (http://github.com/iSabanin/pretty_diff) but for email messages. Contains a lot of hours of markup tuning to make diffs viewable in almost all known browsers.
4
+
5
+ Heavily used in Beanstalk (http://beanstalkapp.com) application.
6
+
7
+ == Installation
8
+
9
+ gem install mail_diff
10
+
11
+ == Example
12
+
13
+ A quick example will tell it all:
14
+
15
+ udiff = File.read("awesome.diff")
16
+ mail_diff = MailDiff::Diff.new(udiff)
17
+ mail_diff.to_html
18
+
19
+ Wrap it with HTML, add some styles and you will get something like this:
20
+
21
+ http://ilya.sabanin.ru/projects/pretty_diff_example.html
22
+
23
+ == Features
24
+
25
+ Same as in PrettyDiff (http://github.com/iSabanin/pretty_diff).
26
+
27
+ Copyright (c) 2011 Ilya Sabanin and Eugene Fedorenko, Wildbit; see LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "mail_diff"
8
+ gem.summary = %Q{Unified Diff to HTML converter for emails}
9
+ gem.description = %Q{Version of PrettyDiff that generate markup that works in most email clients}
10
+ gem.email = "ilya.sabanin@gmail.com"
11
+ gem.homepage = "http://github.com/iSabanin/mail_diff"
12
+ gem.authors = ["Ilya Sabanin"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "mail_diff #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
Binary file
@@ -0,0 +1,51 @@
1
+ #
2
+ # Represent a single piece of a diff.
3
+ #
4
+ class MailDiff::Chunk #:nodoc:
5
+ attr_reader :diff, :meta_info, :content, :lines
6
+
7
+ def initialize(diff, meta_info, content)
8
+ @diff = diff
9
+ @meta_info = meta_info
10
+ @content = content
11
+ end
12
+
13
+ # Generate HTML presentation for a Chunk. Return a string.
14
+ def to_html
15
+ # We have to find lines before we can call line numbers methods.
16
+ find_lines!
17
+ generator.generate
18
+ end
19
+
20
+ # Return LineNumbers object that represents two columns of numbers
21
+ # that will be displayed on the left of the HTML presentation.
22
+ #
23
+ # IMPORTANT! Before calling this method it's essential to call "find_lines!" first,
24
+ # otherwise the array will be empty.
25
+ def line_numbers
26
+ @_line_numbers ||= MailDiff::LineNumbers.new(diff, meta_info)
27
+ end
28
+
29
+ private
30
+
31
+ def generator
32
+ @_generator ||= MailDiff::ChunkGenerator.new(self)
33
+ end
34
+
35
+ # Parse the content searching for lines. Initialize Line object for every line.
36
+ # Return an array of Line objects.
37
+ def find_lines!
38
+ @lines = []
39
+ @lines.tap do
40
+ content.split(/\r?\n/).each do |line_str|
41
+ line = MailDiff::Line.new(diff, line_str)
42
+ next if line.ignore?
43
+ @lines << line
44
+ line_numbers.act_on_line(line)
45
+ line.left_number = line_numbers.left_column.last
46
+ line.right_number = line_numbers.right_column.last
47
+ end
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,60 @@
1
+ require 'cgi'
2
+
3
+ #
4
+ # Main class to interact with. In fact this is the only class you should interact with
5
+ # when using the library.
6
+ #
7
+ # === Usage example
8
+ # mail_diff = MailDiff::Diff.new(udiff)
9
+ # mail_diff.to_html
10
+ #
11
+ # Keep in mind that Diff will automatically escape all HTML tags from the intput string
12
+ # so that it doesn't interfere with the output.
13
+ #
14
+ class MailDiff::Diff
15
+ CHUNK_REGEXP = /@@ .+ @@\n/
16
+
17
+ attr_reader :input, :options
18
+
19
+ # Create new Diff object.
20
+ # Accept a String in unified diff format and options hash.
21
+ def initialize(unified_diff, options={})
22
+ @input = escape_html(unified_diff)
23
+ @options = options
24
+ end
25
+
26
+ # Generate HTML presentation. Return a string.
27
+ def to_html
28
+ generator.generate
29
+ end
30
+
31
+ # Return an array of Chunk objects that Diff found in the input.
32
+ def chunks
33
+ @_chunks ||= find_chunks(input)
34
+ end
35
+
36
+ private
37
+
38
+ def generator
39
+ @_generator ||= MailDiff::DiffGenerator.new(self)
40
+ end
41
+
42
+ # Parse the input for diff chunks and initialize a Chunk object for each of them.
43
+ # Return an array of Chunks.
44
+ def find_chunks(text)
45
+ meta_info = text.scan(CHUNK_REGEXP)
46
+ chunks = []
47
+ chunks.tap do
48
+ split = text.split(CHUNK_REGEXP)
49
+ split.shift
50
+ split.each_with_index do |lines, idx|
51
+ chunks << MailDiff::Chunk.new(self, meta_info[idx], lines)
52
+ end
53
+ end
54
+ end
55
+
56
+ def escape_html(input_text)
57
+ CGI.escapeHTML(input_text)
58
+ end
59
+
60
+ end
@@ -0,0 +1,19 @@
1
+ class MailDiff::ChunkGenerator
2
+
3
+ attr_reader :chunk
4
+
5
+ def initialize(chunk)
6
+ @chunk = chunk
7
+ end
8
+
9
+ def generate
10
+ content
11
+ end
12
+
13
+ private
14
+
15
+ def content
16
+ chunk.lines.map{|l| l.to_html }.join("\n")
17
+ end
18
+
19
+ end
@@ -0,0 +1,41 @@
1
+ class MailDiff::DiffGenerator
2
+
3
+ attr_reader :diff
4
+
5
+ def initialize(diff)
6
+ @diff = diff
7
+ end
8
+
9
+ def generate
10
+ chunks_html = diff.chunks.map{|c| c.to_html}.join(chunk_separator_html).to_s
11
+ intro_html + chunks_html + outro_html
12
+ end
13
+
14
+ private
15
+
16
+ def intro_html
17
+ %Q[<div style="padding: 0 0 1em; overflow:auto; overflow-y: hidden;">
18
+ <table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#F9F9F9" style="margin: 0; border: 1px solid #E5E5E5; border-collapse: collapse; padding: 0; font: 11px Monaco, 'Lucida Console', Consolas, 'Courier New', monospace;">]
19
+ end
20
+
21
+ def chunk_separator_html
22
+ %Q[<tr>
23
+ <td bgcolor="#F3F3F3" style="border-right: 1px solid #E5E5E5; padding: 0 .75em;">&nbsp;</td>
24
+ <td bgcolor="#F3F3F3" style="border-right: 1px solid #E5E5E5; padding: 0 .75em;">&nbsp;</td>
25
+ <td style="padding: 0 1em;">&nbsp;</td>
26
+ </tr>
27
+ <tr>
28
+ <td colspan="3" height="2" bgcolor="#E5E5E5"></td>
29
+ </tr>
30
+ <tr>
31
+ <td bgcolor="#F3F3F3" style="border-right: 1px solid #E5E5E5; padding: 0 .75em;">&nbsp;</td>
32
+ <td bgcolor="#F3F3F3" style="border-right: 1px solid #E5E5E5; padding: 0 .75em;">&nbsp;</td>
33
+ <td style="padding: 0 1em;">&nbsp;</td>
34
+ </tr>]
35
+ end
36
+
37
+ def outro_html
38
+ %Q[</table></div>]
39
+ end
40
+
41
+ end
@@ -0,0 +1,69 @@
1
+ class MailDiff::LineGenerator
2
+
3
+ attr_reader :line
4
+
5
+ def initialize(line)
6
+ @line = line
7
+ end
8
+
9
+ def generate
10
+ if line.added?
11
+ added_html
12
+ elsif line.deleted?
13
+ deleted_html
14
+ else
15
+ not_modified_html
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def wrapper(&block)
22
+ line_start +
23
+ line_numbers +
24
+ yield +
25
+ line_end
26
+ end
27
+
28
+ def line_start
29
+ %Q[<tr>]
30
+ end
31
+
32
+ def line_end
33
+ %Q[</tr>]
34
+ end
35
+
36
+ def line_numbers
37
+ %Q[<td width="4%" align="right" valign="top" bgcolor="#F3F3F3" style="border-right: 1px solid #E5E5E5; padding: .3em .75em; color: #BBB;">
38
+ <pre style="width:auto; margin: 0; border: 0; padding: 0; font: 11px Monaco, 'Lucida Console', Consolas, 'Courier New', monospace;">#{ line.left_number }</pre>
39
+ </td>
40
+ <td width="4%" align="right" valign="top" bgcolor="#F3F3F3" style="border-right: 1px solid #E5E5E5; padding: .3em .75em; color: #BBB;">
41
+ <pre style="width:auto; margin: 0; border: 0; padding: 0; font: 11px Monaco, 'Lucida Console', Consolas, 'Courier New', monospace;">#{ line.right_number }</pre>
42
+ </td>]
43
+ end
44
+
45
+ def added_html
46
+ wrapper do
47
+ %Q[<td valign="top" style="padding: .3em 1em;">
48
+ <pre style="width:auto; margin: 0; border: 0; padding: 0; white-space: pre-wrap; font: 11px Monaco, 'Lucida Console', Consolas, 'Courier New', monospace;"><span class="gi">#{ line.format }</span></pre>
49
+ </td>]
50
+ end
51
+ end
52
+
53
+ def deleted_html
54
+ wrapper do
55
+ %Q[<td valign="top" style="padding: .3em 1em;">
56
+ <pre style="width:auto; margin: 0; border: 0; padding: 0; white-space: pre-wrap; font: 11px Monaco, 'Lucida Console', Consolas, 'Courier New', monospace;"><span class="gd">#{ line.format }</span></pre>
57
+ </td>]
58
+ end
59
+ end
60
+
61
+ def not_modified_html
62
+ wrapper do
63
+ %Q[<td valign="top" style="padding: .3em 1em;">
64
+ <pre style="width:auto; margin: 0; border: 0; padding: 0; white-space: pre-wrap; font: 11px Monaco, 'Lucida Console', Consolas, 'Courier New', monospace;">#{ line.format }</pre>
65
+ </td>]
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,63 @@
1
+ #
2
+ # Represent a single line of the diff.
3
+ #
4
+ class MailDiff::Line #:nodoc:
5
+
6
+ attr_reader :diff, :content
7
+ attr_accessor :left_number, :right_number
8
+
9
+ def initialize(diff, content)
10
+ @diff = diff
11
+ @content = content
12
+ end
13
+
14
+ # Generate HTML presentation for a Line. Return a string.
15
+ def to_html
16
+ generator.generate
17
+ end
18
+
19
+ # Prepare Line contents for injection into HTML structure.
20
+ # Currently used for replacing Tab symbols with spaces.
21
+ # Return a string.
22
+ def format
23
+ content.gsub("\t", ' ')
24
+ end
25
+
26
+ # Unified Diff sometimes emit a special line at the end of the file
27
+ # that we should not display in the output.
28
+ # Return true or false.
29
+ def ignore?
30
+ content =~ /\/
31
+ end
32
+
33
+ # Return status of the Line. Can be :added, :deleted or :not_modified.
34
+ def status
35
+ case content
36
+ when /^\+/
37
+ :added
38
+ when /^\-/
39
+ :deleted
40
+ else
41
+ :not_modified
42
+ end
43
+ end
44
+
45
+ def added?
46
+ status == :added
47
+ end
48
+
49
+ def deleted?
50
+ status == :deleted
51
+ end
52
+
53
+ def not_modified?
54
+ status == :not_modified
55
+ end
56
+
57
+ private
58
+
59
+ def generator
60
+ @_generator ||= MailDiff::LineGenerator.new(self)
61
+ end
62
+
63
+ end
@@ -0,0 +1,75 @@
1
+ #
2
+ # Represent 2 columns of numbers that will be displayed
3
+ # on the left of the HTML presentation.
4
+ #
5
+ class MailDiff::LineNumbers #:nodoc:
6
+
7
+ attr_reader :diff, :meta_info
8
+
9
+ def initialize(diff, meta)
10
+ @diff = diff
11
+ @meta_info = meta
12
+ end
13
+
14
+ # Increase either left column of numbers, right or both of them; depending on the Line status.
15
+ def act_on_line(line)
16
+ if line.added?
17
+ increase_right
18
+ elsif line.deleted?
19
+ increase_left
20
+ else
21
+ increase_both
22
+ end
23
+ end
24
+
25
+ def left_column
26
+ @left_column ||= []
27
+ end
28
+
29
+ def right_column
30
+ @right_column ||= []
31
+ end
32
+
33
+ private
34
+
35
+ # Search for information about line numbers changes provided by unified diff format.
36
+ def scan_meta(target)
37
+ meta_info.scan(target).flatten.first
38
+ end
39
+
40
+ # Return starting number for the left column according to unified diff information.
41
+ def left_starts_at
42
+ scan_meta(/^@@ -(\d+),/).to_i
43
+ end
44
+
45
+ # Return starting number for the right column according to unified diff information.
46
+ def right_starts_at
47
+ scan_meta(/\+(\d+),\d+ @@$/).to_i
48
+ end
49
+
50
+ # Increase left column line number by one.
51
+ def increase_left
52
+ left_column << increase_or_start(:left)
53
+ right_column << nil
54
+ end
55
+
56
+ # Increase right column line number by one.
57
+ def increase_right
58
+ left_column << nil
59
+ right_column << increase_or_start(:right)
60
+ end
61
+
62
+ # Increase both columns line numbers by one.
63
+ def increase_both
64
+ left_column << increase_or_start(:left)
65
+ right_column << increase_or_start(:right)
66
+ end
67
+
68
+ # Either increasing existing line number by one or using the initial number provided by
69
+ # unified diff format.
70
+ def increase_or_start(which)
71
+ previous = send("#{which}_column").reverse.find{|e| !e.nil?}
72
+ if previous then previous + 1 else send("#{which}_starts_at") end
73
+ end
74
+
75
+ end
data/lib/mail_diff.rb ADDED
@@ -0,0 +1,15 @@
1
+ module MailDiff #:nodoc:
2
+ end
3
+
4
+ def require_local(suffix)
5
+ require(File.expand_path(File.join(File.dirname(__FILE__), suffix)))
6
+ end
7
+
8
+ require_local 'mail_diff/diff'
9
+ require_local 'mail_diff/chunk'
10
+ require_local 'mail_diff/line_numbers'
11
+ require_local 'mail_diff/line'
12
+
13
+ require_local 'mail_diff/html_generators/diff_generator'
14
+ require_local 'mail_diff/html_generators/chunk_generator'
15
+ require_local 'mail_diff/html_generators/line_generator'
data/mail_diff.gemspec ADDED
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{mail_diff}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ilya Sabanin"]
12
+ s.date = %q{2011-04-18}
13
+ s.description = %q{Version of PrettyDiff that generate markup that works in most email clients}
14
+ s.email = %q{ilya.sabanin@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "lib/mail_diff.rb",
25
+ "lib/mail_diff/.DS_Store",
26
+ "lib/mail_diff/chunk.rb",
27
+ "lib/mail_diff/diff.rb",
28
+ "lib/mail_diff/html_generators/chunk_generator.rb",
29
+ "lib/mail_diff/html_generators/diff_generator.rb",
30
+ "lib/mail_diff/html_generators/line_generator.rb",
31
+ "lib/mail_diff/line.rb",
32
+ "lib/mail_diff/line_numbers.rb",
33
+ "mail_diff.gemspec",
34
+ "test/dummy_mail_diff_test.rb",
35
+ "test/example.udiff",
36
+ "test/stylesheet.css"
37
+ ]
38
+ s.homepage = %q{http://github.com/iSabanin/mail_diff}
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.7}
41
+ s.summary = %q{Unified Diff to HTML converter for emails}
42
+ s.test_files = [
43
+ "test/dummy_mail_diff_test.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
52
+ else
53
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
57
+ end
58
+ end
59
+
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'mail_diff')
2
+
3
+ file = File.read("example.udiff")
4
+ diff = MailDiff::Diff.new(file)
5
+
6
+ File.open("test_result.html", 'w') do |f|
7
+ f << File.read("stylesheet.css")
8
+ f << diff.to_html
9
+ end
10
+
11
+ `open test_result.html`
@@ -0,0 +1,212 @@
1
+ --- Revision 12
2
+ +++ Revision 17
3
+ @@ -4,7 +4,7 @@
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>Team Schedules</title>
7
+ - <style type="text/css"> article,aside,dialog,figure,footer,header,hgroup,nav,section{display:block}html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,hr,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,dialog,figure,footer,header,hgroup,menu,nav,section,time,mark,audio,video{margin:0;border:0;padding:0;outline:0;font-size:1em;vertical-align:baseline;background-color:transparent}html,body{height:100%}body{background-color:#FFF;color:#333;font:normal .8em/1.4 'Helvetica Neue',Arial,Helvetica,sans-serif}select{width:160px}table{width:100%;height:100%;border-collapse:collapse;border-spacing:0}table th,table td{border-bottom:1px solid rgba(0,0,0,.02);vertical-align:middle}table thead th{width:10%;padding:1em 0}table tbody th{padding-right:1em;border-color:#DDD;background-color:#EEE;color:#AAA;font-size:.85em;font-weight:normal;text-align:right}table th.now{background-color:#FFFFD5;color:#908E00}table td.working{background-color:#D5F4FF} </style>
8
+ + <style type="text/css"> article,aside,dialog,figure,footer,header,hgroup,nav,section{display:block}html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,hr,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,dialog,figure,footer,header,hgroup,menu,nav,section,time,mark,audio,video{margin:0;border:0;padding:0;outline:0;font-size:1em;vertical-align:baseline;background-color:transparent}html,body{height:100%}body{background-color:#FFF;color:#333;font:normal .8em/1.4 'Helvetica Neue',Arial,Helvetica,sans-serif}select{width:160px}table{width:100%;height:100%;border-collapse:collapse;border-spacing:0}table th,table td{border-bottom:1px solid rgba(0,0,0,.02);vertical-align:middle}table thead th{width:10%;padding:1em 0}table thead th span{color:#AAA;font-size:.85em;font-weight:normal;vertical-align:top;}table tbody th{padding-right:1em;border-color:#DDD;background-color:#EEE;color:#AAA;font-size:.85em;font-weight:normal;text-align:right}table th.now{background-color:#FFFFD5;color:#908E00}table td.working{background-color:#D5F4FF} </style>
9
+ </head>
10
+
11
+ <body>
12
+ @@ -31,7 +31,7 @@
13
+ <td></td> <!-- Eugene -->
14
+ <td></td> <!-- Gilbert -->
15
+ <td></td> <!-- Ilya -->
16
+ - <td></td> <!-- Dima -->
17
+ + <td>*</td> <!-- Dima -->
18
+ <td></td> <!-- Hristo -->
19
+ <td></td> <!-- Igor -->
20
+ </tr>
21
+ @@ -42,7 +42,7 @@
22
+ <td></td> <!-- Eugene -->
23
+ <td></td> <!-- Gilbert -->
24
+ <td>*</td> <!-- Ilya -->
25
+ - <td></td> <!-- Dima -->
26
+ + <td>*</td> <!-- Dima -->
27
+ <td></td> <!-- Hristo -->
28
+ <td></td> <!-- Igor -->
29
+ </tr>
30
+ @@ -53,7 +53,7 @@
31
+ <td>*</td> <!-- Eugene -->
32
+ <td>*</td> <!-- Gilbert -->
33
+ <td>*</td> <!-- Ilya -->
34
+ - <td></td> <!-- Dima -->
35
+ + <td>*</td> <!-- Dima -->
36
+ <td>*</td> <!-- Hristo -->
37
+ <td>*</td> <!-- Igor -->
38
+ </tr>
39
+ @@ -64,7 +64,7 @@
40
+ <td>*</td> <!-- Eugene -->
41
+ <td>*</td> <!-- Gilbert -->
42
+ <td>*</td> <!-- Ilya -->
43
+ - <td></td> <!-- Dima -->
44
+ + <td>*</td> <!-- Dima -->
45
+ <td>*</td> <!-- Hristo -->
46
+ <td>*</td> <!-- Igor -->
47
+ </tr>
48
+ @@ -125,8 +125,8 @@
49
+ </tr>
50
+ <tr>
51
+ <th>10 AM</th>
52
+ - <td></td> <!-- Chris -->
53
+ - <td></td> <!-- Natalie -->
54
+ + <td>*</td> <!-- Chris -->
55
+ + <td>*</td> <!-- Natalie -->
56
+ <td>*</td> <!-- Eugene -->
57
+ <td>*</td> <!-- Gilbert -->
58
+ <td>*</td> <!-- Ilya -->
59
+ @@ -136,8 +136,8 @@
60
+ </tr>
61
+ <tr>
62
+ <th>11 AM</th>
63
+ - <td></td> <!-- Chris -->
64
+ - <td></td> <!-- Natalie -->
65
+ + <td>*</td> <!-- Chris -->
66
+ + <td>*</td> <!-- Natalie -->
67
+ <td>*</td> <!-- Eugene -->
68
+ <td>*</td> <!-- Gilbert -->
69
+ <td>*</td> <!-- Ilya -->
70
+ @@ -147,10 +147,10 @@
71
+ </tr>
72
+ <tr>
73
+ <th>Noon</th>
74
+ - <td></td> <!-- Chris -->
75
+ - <td></td> <!-- Natalie -->
76
+ + <td>*</td> <!-- Chris -->
77
+ + <td>*</td> <!-- Natalie -->
78
+ <td>*</td> <!-- Eugene -->
79
+ - <td>*</td> <!-- Gilbert -->
80
+ + <td></td> <!-- Gilbert -->
81
+ <td></td> <!-- Ilya -->
82
+ <td></td> <!-- Dima -->
83
+ <td>*</td> <!-- Hristo -->
84
+ @@ -158,20 +158,20 @@
85
+ </tr>
86
+ <tr>
87
+ <th>1 PM</th>
88
+ - <td></td> <!-- Chris -->
89
+ - <td></td> <!-- Natalie -->
90
+ - <td>*</td> <!-- Eugene -->
91
+ - <td>*</td> <!-- Gilbert -->
92
+ + <td>*</td> <!-- Chris -->
93
+ + <td>*</td> <!-- Natalie -->
94
+ + <td></td> <!-- Eugene -->
95
+ + <td></td> <!-- Gilbert -->
96
+ <td></td> <!-- Ilya -->
97
+ <td></td> <!-- Dima -->
98
+ - <td>*</td> <!-- Hristo -->
99
+ - <td>*</td> <!-- Igor -->
100
+ + <td></td> <!-- Hristo -->
101
+ + <td></td> <!-- Igor -->
102
+ </tr>
103
+ <tr>
104
+ <th>2 PM</th>
105
+ - <td></td> <!-- Chris -->
106
+ - <td></td> <!-- Natalie -->
107
+ - <td>*</td> <!-- Eugene -->
108
+ + <td>*</td> <!-- Chris -->
109
+ + <td>*</td> <!-- Natalie -->
110
+ + <td></td> <!-- Eugene -->
111
+ <td></td> <!-- Gilbert -->
112
+ <td></td> <!-- Ilya -->
113
+ <td></td> <!-- Dima -->
114
+ @@ -180,8 +180,8 @@
115
+ </tr>
116
+ <tr>
117
+ <th>3 PM</th>
118
+ - <td></td> <!-- Chris -->
119
+ - <td></td> <!-- Natalie -->
120
+ + <td>*</td> <!-- Chris -->
121
+ + <td>*</td> <!-- Natalie -->
122
+ <td></td> <!-- Eugene -->
123
+ <td></td> <!-- Gilbert -->
124
+ <td></td> <!-- Ilya -->
125
+ @@ -191,8 +191,8 @@
126
+ </tr>
127
+ <tr>
128
+ <th>4 PM</th>
129
+ - <td></td> <!-- Chris -->
130
+ - <td></td> <!-- Natalie -->
131
+ + <td>*</td> <!-- Chris -->
132
+ + <td>*</td> <!-- Natalie -->
133
+ <td></td> <!-- Eugene -->
134
+ <td></td> <!-- Gilbert -->
135
+ <td></td> <!-- Ilya -->
136
+ @@ -202,8 +202,8 @@
137
+ </tr>
138
+ <tr>
139
+ <th>5 PM</th>
140
+ - <td></td> <!-- Chris -->
141
+ - <td></td> <!-- Natalie -->
142
+ + <td>*</td> <!-- Chris -->
143
+ + <td>*</td> <!-- Natalie -->
144
+ <td></td> <!-- Eugene -->
145
+ <td></td> <!-- Gilbert -->
146
+ <td></td> <!-- Ilya -->
147
+ @@ -240,7 +240,7 @@
148
+ <td></td> <!-- Eugene -->
149
+ <td></td> <!-- Gilbert -->
150
+ <td></td> <!-- Ilya -->
151
+ - <td></td> <!-- Dima -->
152
+ + <td>*</td> <!-- Dima -->
153
+ <td></td> <!-- Hristo -->
154
+ <td></td> <!-- Igor -->
155
+ </tr>
156
+ @@ -251,7 +251,7 @@
157
+ <td></td> <!-- Eugene -->
158
+ <td></td> <!-- Gilbert -->
159
+ <td></td> <!-- Ilya -->
160
+ - <td></td> <!-- Dima -->
161
+ + <td>*</td> <!-- Dima -->
162
+ <td></td> <!-- Hristo -->
163
+ <td></td> <!-- Igor -->
164
+ </tr>
165
+ @@ -262,7 +262,7 @@
166
+ <td></td> <!-- Eugene -->
167
+ <td></td> <!-- Gilbert -->
168
+ <td>*</td> <!-- Ilya -->
169
+ - <td></td> <!-- Dima -->
170
+ + <td>*</td> <!-- Dima -->
171
+ <td></td> <!-- Hristo -->
172
+ <td></td> <!-- Igor -->
173
+ </tr>
174
+ @@ -273,7 +273,7 @@
175
+ <td></td> <!-- Eugene -->
176
+ <td></td> <!-- Gilbert -->
177
+ <td>*</td> <!-- Ilya -->
178
+ - <td></td> <!-- Dima -->
179
+ + <td>*</td> <!-- Dima -->
180
+ <td></td> <!-- Hristo -->
181
+ <td></td> <!-- Igor -->
182
+ </tr>
183
+ @@ -284,7 +284,7 @@
184
+ <td></td> <!-- Eugene -->
185
+ <td></td> <!-- Gilbert -->
186
+ <td></td> <!-- Ilya -->
187
+ - <td></td> <!-- Dima -->
188
+ + <td>*</td> <!-- Dima -->
189
+ <td></td> <!-- Hristo -->
190
+ <td></td> <!-- Igor -->
191
+ </tr>
192
+ @@ -299,8 +299,19 @@
193
+ var now = new Date();
194
+
195
+ $("td:contains('*')").addClass('working').text('');
196
+ - $('table tbody tr:eq(' + (now.getHours() - 7) + ') th').addClass('now')
197
+
198
+ + $('table tbody tr:eq(' + (now.getHours() - 7) + ') th').addClass('now');
199
+ +
200
+ + $('table thead th:gt(0)').each(function(i){
201
+ + var hours = 0;
202
+ +
203
+ + $('table tbody tr').find('td:eq(' + i + ')').each(function(){
204
+ + if ($(this).hasClass('working')) hours++;
205
+ + });
206
+ +
207
+ + $(this).append(' <span>' + hours + '</span>');
208
+ + });
209
+ +
210
+ });
211
+
212
+ </script>
@@ -0,0 +1,65 @@
1
+ <style type="text/css">
2
+
3
+ /* Syntax Coloring */
4
+
5
+ pre span.lineno { background-color: #F0F0F0; }
6
+ pre .hll { background-color: #FFC; }
7
+ pre .c { color: #AAA; } /* Comment */
8
+ pre .err { color: #F00000; background-color: #F0A0A0; } /* Error */
9
+ pre .k { color: #00A; } /* Keyword */
10
+ pre .cm { color: #AAA; } /* Comment.Multiline */
11
+ pre .cp { color: #4C8317; } /* Comment.Preproc */
12
+ pre .c1 { color: #AAA; } /* Comment.Single */
13
+ pre .cs { color: #00A; } /* Comment.Special */
14
+ pre .gd { background-color: #FDD; color: #300; } /* Generic.Deleted */
15
+ pre .ge { } /* Generic.Emph */
16
+ pre .gr { color: #A00 } /* Generic.Error */
17
+ pre .gh { color: #000080; } /* Generic.Heading */
18
+ pre .gi { background-color: #E0F2CE; color: #030; } /* Generic.Inserted */
19
+ pre .go { color: #888 } /* Generic.Output */
20
+ pre .gp { color: #555 } /* Generic.Prompt */
21
+ pre .gs { } /* Generic.Strong */
22
+ pre .gu { color: #800080; } /* Generic.Subheading */
23
+ pre .gt { color: #A00 } /* Generic.Traceback */
24
+ pre .kc { color: #00A } /* Keyword.Constant */
25
+ pre .kd { color: #00A } /* Keyword.Declaration */
26
+ pre .kn { color: #00A } /* Keyword.Namespace */
27
+ pre .kp { color: #00A } /* Keyword.Pseudo */
28
+ pre .kr { color: #00A } /* Keyword.Reserved */
29
+ pre .kt { color: #0AA } /* Keyword.Type */
30
+ pre .m { color: #099 } /* Literal.Number */
31
+ pre .s { color: #A50 } /* Literal.String */
32
+ pre .na { color: #1E90FF } /* Name.Attribute */
33
+ pre .nb { color: #0AA } /* Name.Builtin */
34
+ pre .nc { color: #0A0; text-decoration: underline } /* Name.Class */
35
+ pre .no { color: #A00 } /* Name.Constant */
36
+ pre .nd { color: #888 } /* Name.Decorator */
37
+ pre .ni { color: #800000; } /* Name.Entity */
38
+ pre .nf { color: #0A0 } /* Name.Function */
39
+ pre .nn { color: #0AA; text-decoration: underline } /* Name.Namespace */
40
+ pre .nt { color: #1E90FF; } /* Name.Tag */
41
+ pre .nv { color: #A00 } /* Name.Variable */
42
+ pre .ow { color: #00A } /* Operator.Word */
43
+ pre .w { color: #BBB } /* Text.Whitespace */
44
+ pre .mf { color: #099 } /* Literal.Number.Float */
45
+ pre .mh { color: #099 } /* Literal.Number.Hex */
46
+ pre .mi { color: #099 } /* Literal.Number.Integer */
47
+ pre .mo { color: #099 } /* Literal.Number.Oct */
48
+ pre .sb { color: #A50 } /* Literal.String.Backtick */
49
+ pre .sc { color: #A50 } /* Literal.String.Char */
50
+ pre .sd { color: #A50 } /* Literal.String.Doc */
51
+ pre .s2 { color: #A50 } /* Literal.String.Double */
52
+ pre .se { color: #A50 } /* Literal.String.Escape */
53
+ pre .sh { color: #A50 } /* Literal.String.Heredoc */
54
+ pre .si { color: #A50 } /* Literal.String.Interpol */
55
+ pre .sx { color: #A50 } /* Literal.String.Other */
56
+ pre .sr { color: #099 } /* Literal.String.Regex */
57
+ pre .s1 { color: #A50 } /* Literal.String.Single */
58
+ pre .ss { color: #00A } /* Literal.String.Symbol */
59
+ pre .bp { color: #0AA } /* Name.Builtin.Pseudo */
60
+ pre .vc { color: #A00 } /* Name.Variable.Class */
61
+ pre .vg { color: #A00 } /* Name.Variable.Global */
62
+ pre .vi { color: #A00 } /* Name.Variable.Instance */
63
+ pre .il { color: #099 } /* Literal.Number.Integer.Long */
64
+
65
+ </style>
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mail_diff
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Ilya Sabanin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-18 00:00:00 +08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: thoughtbot-shoulda
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ description: Version of PrettyDiff that generate markup that works in most email clients
36
+ email: ilya.sabanin@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - LICENSE
46
+ - README.rdoc
47
+ - Rakefile
48
+ - VERSION
49
+ - lib/mail_diff.rb
50
+ - lib/mail_diff/.DS_Store
51
+ - lib/mail_diff/chunk.rb
52
+ - lib/mail_diff/diff.rb
53
+ - lib/mail_diff/html_generators/chunk_generator.rb
54
+ - lib/mail_diff/html_generators/diff_generator.rb
55
+ - lib/mail_diff/html_generators/line_generator.rb
56
+ - lib/mail_diff/line.rb
57
+ - lib/mail_diff/line_numbers.rb
58
+ - mail_diff.gemspec
59
+ - test/dummy_mail_diff_test.rb
60
+ - test/example.udiff
61
+ - test/stylesheet.css
62
+ has_rdoc: true
63
+ homepage: http://github.com/iSabanin/mail_diff
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options: []
68
+
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.3.7
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Unified Diff to HTML converter for emails
96
+ test_files:
97
+ - test/dummy_mail_diff_test.rb