mail_diff 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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