pretty_diff 0.9.3 → 2.0.0

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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +41 -6
  4. data/Rakefile +0 -1
  5. data/VERSION +1 -1
  6. data/lib/pretty_diff.rb +5 -6
  7. data/lib/pretty_diff/abstract_generator.rb +51 -0
  8. data/lib/pretty_diff/basic_generator.rb +72 -0
  9. data/lib/pretty_diff/chunk.rb +24 -41
  10. data/lib/pretty_diff/diff.rb +75 -41
  11. data/lib/pretty_diff/encoding.rb +30 -0
  12. data/lib/pretty_diff/line.rb +30 -51
  13. data/lib/pretty_diff/line_numbers.rb +47 -67
  14. data/pretty_diff.gemspec +33 -18
  15. data/test/abstract_generator_test.rb +65 -0
  16. data/test/basic_generator_test.rb +29 -0
  17. data/test/chunk_test.rb +13 -30
  18. data/test/diff_test.rb +50 -19
  19. data/test/encoding_test.rb +35 -0
  20. data/test/fixtures/blank.diff +0 -0
  21. data/test/fixtures/blank.diff.html +1 -0
  22. data/test/fixtures/chinese-gbk-crlf +5 -0
  23. data/test/fixtures/cp1251.diff +13 -0
  24. data/test/fixtures/csharp.diff +12 -0
  25. data/test/fixtures/csharp.diff.html +19 -0
  26. data/test/{data → fixtures}/first.diff +0 -0
  27. data/test/fixtures/first.diff.html +85 -0
  28. data/test/{data → fixtures}/second.diff +0 -0
  29. data/test/fixtures/single_line.diff +7 -0
  30. data/test/fixtures/single_line.diff.html +4 -0
  31. data/test/fixtures/text.diff +8 -0
  32. data/test/fixtures/text.diff.html +13 -0
  33. data/test/fixtures/windows-cp1251-lf +6 -0
  34. data/test/helper.rb +10 -8
  35. data/test/line_numbers_test.rb +25 -0
  36. data/test/line_test.rb +26 -37
  37. metadata +81 -75
  38. data/lib/pretty_diff/html_generators/chunk_generator.rb +0 -41
  39. data/lib/pretty_diff/html_generators/diff_generator.rb +0 -13
  40. data/lib/pretty_diff/html_generators/line_generator.rb +0 -45
  41. data/lib/pretty_diff/html_generators/line_numbers_generator.rb +0 -36
  42. data/test/html_generator_test.rb +0 -39
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2b6469b15bc93eef7d745c13cb407cf7ed005b34
4
+ data.tar.gz: 30836b4814f14cf92ea0032be12daf987259b311
5
+ SHA512:
6
+ metadata.gz: 8d4df170603a0a29963cc321bd292110107374e0f37bd1ae89cb76254cadcf10d52f0f956cc20987d81eb83cf2687dcbc4761cd3ff16aed0bc6c43a93a071431
7
+ data.tar.gz: 92f7e156c4e5fbfcff6b23f749c656a4d9c29d03b4344d321df1addfc0a8e08239f7b71c56f0985a194d417667924bb1da6e603c916bf1b796aebe5cd944f455
data/Gemfile CHANGED
@@ -1,5 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
+ gem "charlock_holmes", "~> 0.6"
4
+
3
5
  group :development do
4
6
  gem "jeweler"
5
7
  gem "builder"
data/Gemfile.lock CHANGED
@@ -1,16 +1,50 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ addressable (2.3.5)
4
5
  builder (3.1.4)
5
- git (1.2.5)
6
- jeweler (1.8.4)
7
- bundler (~> 1.0)
6
+ charlock_holmes (0.6.9.1)
7
+ descendants_tracker (0.0.3)
8
+ faraday (0.9.0)
9
+ multipart-post (>= 1.2, < 3)
10
+ git (1.2.6)
11
+ github_api (0.11.2)
12
+ addressable (~> 2.3)
13
+ descendants_tracker (~> 0.0.1)
14
+ faraday (~> 0.8, < 0.10)
15
+ hashie (>= 1.2)
16
+ multi_json (>= 1.7.5, < 2.0)
17
+ nokogiri (~> 1.6.0)
18
+ oauth2
19
+ hashie (2.0.5)
20
+ highline (1.6.20)
21
+ jeweler (2.0.1)
22
+ builder
23
+ bundler (>= 1.0)
8
24
  git (>= 1.2.5)
25
+ github_api
26
+ highline (>= 1.6.15)
27
+ nokogiri (>= 1.5.10)
9
28
  rake
10
29
  rdoc
11
- json (1.7.7)
12
- rake (10.0.4)
13
- rdoc (4.0.1)
30
+ json (1.8.1)
31
+ jwt (0.1.11)
32
+ multi_json (>= 1.5)
33
+ mini_portile (0.5.2)
34
+ multi_json (1.8.4)
35
+ multi_xml (0.5.5)
36
+ multipart-post (2.0.0)
37
+ nokogiri (1.6.1)
38
+ mini_portile (~> 0.5.0)
39
+ oauth2 (0.9.3)
40
+ faraday (>= 0.8, < 0.10)
41
+ jwt (~> 0.1.8)
42
+ multi_json (~> 1.3)
43
+ multi_xml (~> 0.5)
44
+ rack (~> 1.2)
45
+ rack (1.5.2)
46
+ rake (10.1.1)
47
+ rdoc (4.1.1)
14
48
  json (~> 1.4)
15
49
 
16
50
  PLATFORMS
@@ -18,4 +52,5 @@ PLATFORMS
18
52
 
19
53
  DEPENDENCIES
20
54
  builder
55
+ charlock_holmes (~> 0.6)
21
56
  jeweler
data/Rakefile CHANGED
@@ -29,5 +29,4 @@ Rake::TestTask.new(:test) do |test|
29
29
  test.verbose = true
30
30
  end
31
31
 
32
- task :test => :check_dependencies
33
32
  task :default => :test
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.3
1
+ 2.0.0
data/lib/pretty_diff.rb CHANGED
@@ -1,16 +1,15 @@
1
- module PrettyDiff #:nodoc:
1
+ module PrettyDiff
2
+ class InvalidGenerator < Exception; end
2
3
  end
3
4
 
4
5
  def require_local(suffix)
5
6
  require(File.expand_path(File.join(File.dirname(__FILE__), suffix)))
6
7
  end
7
8
 
9
+ require_local 'pretty_diff/encoding'
8
10
  require_local 'pretty_diff/diff'
9
11
  require_local 'pretty_diff/chunk'
10
12
  require_local 'pretty_diff/line_numbers'
11
13
  require_local 'pretty_diff/line'
12
-
13
- require_local 'pretty_diff/html_generators/diff_generator'
14
- require_local 'pretty_diff/html_generators/chunk_generator'
15
- require_local 'pretty_diff/html_generators/line_generator'
16
- require_local 'pretty_diff/html_generators/line_numbers_generator'
14
+ require_local 'pretty_diff/abstract_generator'
15
+ require_local 'pretty_diff/basic_generator'
@@ -0,0 +1,51 @@
1
+ require 'cgi'
2
+
3
+ module PrettyDiff
4
+ class AbstractGenerator
5
+
6
+ def self.[](tgt)
7
+ new(tgt)
8
+ end
9
+
10
+ def initialize(tgt)
11
+ target_name = class_to_target_name(tgt.class)
12
+ instance_variable_set("@#{target_name}", tgt)
13
+ self.class.class_eval do
14
+ attr_accessor(target_name)
15
+ end
16
+ end
17
+
18
+ def generate
19
+ raise 'Not implemented!'
20
+ end
21
+
22
+ protected
23
+
24
+ def tag(name, options=nil, open=false)
25
+ "<#{name}#{tag_options(options) if options}#{open ? ">" : " />"}"
26
+ end
27
+
28
+ def content_tag(name, options=nil, &block)
29
+ "<#{name}#{tag_options(options) if options}>#{block.call}</#{name}>"
30
+ end
31
+
32
+ def tag_options(options)
33
+ return if options.empty?
34
+ attrs = []
35
+ options.each_pair do |key, value|
36
+ unless value.nil?
37
+ attrs << %(#{key}="#{h(value)}")
38
+ end
39
+ end
40
+ " #{attrs.sort * ' '}" unless attrs.empty?
41
+ end
42
+
43
+ def h(t)
44
+ CGI.escapeHTML(t)
45
+ end
46
+
47
+ def class_to_target_name(c)
48
+ c.to_s.split('::').last.gsub(/Gen(erator)?\z/, '').gsub(/(.)([A-Z])/,'\1_\2').downcase
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,72 @@
1
+ module PrettyDiff
2
+ class BasicGenerator < AbstractGenerator
3
+
4
+ def generate
5
+ content_tag :div, :class => 'diff' do
6
+ diff.chunks.map{|c| ChunkGen[c].generate}.join('')
7
+ end
8
+ end
9
+
10
+ class ChunkGen < PrettyDiff::AbstractGenerator
11
+ def generate
12
+ content_tag :div, :class => 'diff-chunk' do
13
+ numbers + code
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def numbers
20
+ LineNumbersGen[chunk.line_numbers].generate
21
+ end
22
+
23
+ def code
24
+ content_tag :div, :class => 'diff-code' do
25
+ content_tag :pre do
26
+ chunk.lines.map{|l| LineGen[l].generate }.join("\n")
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ class LineNumbersGen < PrettyDiff::AbstractGenerator
33
+ def generate
34
+ column(line_numbers.left_column) + column(line_numbers.right_column)
35
+ end
36
+
37
+ private
38
+
39
+ def column(clmn)
40
+ content_tag :div, :class => 'diff-line-nums' do
41
+ content_tag :pre do
42
+ fill_whitespace(clmn).join("\n")
43
+ end
44
+ end
45
+ end
46
+
47
+ def fill_whitespace(numbers)
48
+ [].tap do |filled|
49
+ numbers.each do |v|
50
+ filled << (v.nil? ? '&nbsp;' : v)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ class LineGen < PrettyDiff::AbstractGenerator
57
+ def generate
58
+ if line.added?
59
+ content_tag(:span, :class => 'diff-line-add') do
60
+ h(line.contents)
61
+ end
62
+ elsif line.deleted?
63
+ content_tag(:span, :class => 'diff-line-del') do
64
+ h(line.contents)
65
+ end
66
+ else
67
+ h(line.contents)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,49 +1,32 @@
1
- #
2
- # Represent a single piece of a diff.
3
- #
4
- class PrettyDiff::Chunk #:nodoc:
5
- attr_reader :diff, :meta_info, :input, :lines
1
+ module PrettyDiff
2
+ class Chunk
3
+ attr_reader :diff, :meta_info, :lines, :contents
6
4
 
7
- def initialize(diff, meta_info, input)
8
- @diff = diff
9
- @meta_info = meta_info
10
- @input = input
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 ||= PrettyDiff::LineNumbers.new(diff, meta_info)
27
- end
5
+ def initialize(diff, meta_info, contents)
6
+ @diff = diff
7
+ @meta_info = meta_info
8
+ @contents = contents
9
+ @lines = find_lines
10
+ end
28
11
 
29
- private
12
+ def line_numbers
13
+ @_line_numbers ||= LineNumbers.new(diff, meta_info)
14
+ end
30
15
 
31
- def generator
32
- @_generator ||= PrettyDiff::ChunkGenerator.new(self)
33
- end
16
+ private
34
17
 
35
- # Parse the input 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
- input.split(/\r?\n/).each do |line_str|
41
- line = PrettyDiff::Line.new(diff, line_str)
42
- next if line.ignore?
43
- @lines << line
44
- line_numbers.act_on_line(line)
18
+ def find_lines
19
+ [].tap do |lines|
20
+ contents.split(/\r?\n|\r/).each do |line_str|
21
+ line = Line.new(self, line_str)
22
+ next if line.ignored?
23
+ lines << line
24
+ line_numbers.act_on_line(line)
25
+ line.left_number = line_numbers.left_column.last
26
+ line.right_number = line_numbers.right_column.last
27
+ end
45
28
  end
46
29
  end
47
- end
48
30
 
31
+ end
49
32
  end
@@ -1,5 +1,3 @@
1
- require 'cgi'
2
-
3
1
  #
4
2
  # Main class to interact with. In fact this is the only class you should interact with
5
3
  # when using the library.
@@ -11,52 +9,88 @@ require 'cgi'
11
9
  # Keep in mind that Diff will automatically escape all HTML tags from the intput string
12
10
  # so that it doesn't interfere with the output.
13
11
  #
14
- class PrettyDiff::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
- # Currrent options:
22
- # * wrap_lines -- wrap each line in code block with <div> element.
23
- def initialize(unified_diff, options={})
24
- @input = escape_html(unified_diff)
25
- @options = options
26
- end
12
+ module PrettyDiff
13
+ class Diff
14
+ CHUNK_REGEXP = /^@@ .+? @@.*\n?$/
27
15
 
28
- # Generate HTML presentation. Return a string.
29
- def to_html
30
- generator.generate
31
- end
16
+ attr_reader :unified_diff, :generator, :out_encoding
32
17
 
33
- # Return an array of Chunk objects that Diff found in the input.
34
- def chunks
35
- @_chunks ||= find_chunks(input)
36
- end
18
+ #
19
+ # Create new Diff object.
20
+ # Accept a String in unified diff format and options hash.
21
+ # Currrent options:
22
+ # * generator -- your own custom implementation of HTML generator. Will use BasicGenerator by default.
23
+ # * out_encoding -- convert encoding of diffs to the specififed encoding. utf-8 by default.
24
+ #
25
+ def initialize(unified_diff, options={})
26
+ @unified_diff = unified_diff
27
+ @options = options
28
+ @out_encoding =
29
+ @generator = validate_generator(options[:generator]) || BasicGenerator
30
+ @out_encoding = options[:out_encoding] || 'utf-8'
31
+ end
37
32
 
38
- private
33
+ def metadata
34
+ @_metadata ||= begin
35
+ ''.tap do |result|
36
+ unified_diff.each_line do |l|
37
+ result << l
38
+ break if l =~ /^\+\+\+ /
39
+ end
40
+ end
41
+ end
42
+ end
39
43
 
40
- def generator
41
- @_generator ||= PrettyDiff::DiffGenerator.new(self)
42
- end
44
+ def contents
45
+ # We have to strip metadata from the rest of the diff
46
+ # to enforce encoding. It's not uncommon for metadata to be in Unicode
47
+ # while the diff itself is in some other encoding.
48
+ @_contents ||= enforce_encoding(unified_diff.lines[metadata.lines.size..-1].join(''))
49
+ end
50
+
51
+ def to_html
52
+ generator.new(self).generate
53
+ end
54
+
55
+ def chunks
56
+ @_chunks ||= find_chunks
57
+ end
43
58
 
44
- # Parse the input for diff chunks and initialize a Chunk object for each of them.
45
- # Return an array of Chunks.
46
- def find_chunks(text)
47
- meta_info = text.scan(CHUNK_REGEXP)
48
- chunks = []
49
- chunks.tap do
50
- split = text.split(CHUNK_REGEXP)
51
- split.shift
52
- split.each_with_index do |lines, idx|
53
- chunks << PrettyDiff::Chunk.new(self, meta_info[idx], lines)
59
+ private
60
+
61
+ def find_chunks
62
+ chunks_meta = contents.scan(CHUNK_REGEXP)
63
+ [].tap do |chunks|
64
+ split = contents.split(CHUNK_REGEXP)
65
+ split.shift
66
+ split.each_with_index do |lines, index|
67
+ chunks << Chunk.new(self, chunks_meta[index], cleanup(lines))
68
+ end
54
69
  end
55
70
  end
56
- end
57
71
 
58
- def escape_html(input_text)
59
- CGI.escapeHTML(input_text)
60
- end
72
+ def validate_generator(gen)
73
+ if valid_generator?(gen)
74
+ gen
75
+ else
76
+ raise InvalidGenerator, "#{gen.inspect} is not a valid PrettyDiff generator"
77
+ end
78
+ end
79
+
80
+ def valid_generator?(generator)
81
+ !generator.nil? &&
82
+ generator.kind_of?(Class) &&
83
+ generator.ancestors.include?(PrettyDiff::AbstractGenerator) &&
84
+ generator.instance_methods.include?(:generate)
85
+ end
86
+
87
+ def enforce_encoding(text)
88
+ Encoding.enforce(out_encoding, text)
89
+ end
61
90
 
91
+ def cleanup(line)
92
+ line[1..-1]
93
+ end
94
+
95
+ end
62
96
  end