pretty_diff 0.9.3 → 2.0.0

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