Class: Diffy::Diff

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/diffy/diff.rb

Constant Summary collapse

ORIGINAL_DEFAULT_OPTIONS =
{
  :diff => '-U10000',
  :source => 'strings',
  :include_diff_info => false,
  :include_plus_and_minus_in_html => false,
  :context => nil,
  :allow_empty_diff => true,
}

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(string1, string2, options = {}) ⇒ Diff

supported options

:diff

A cli options string passed to diff

:source

Either strings or files. Determines whether string1 and string2 should be interpreted as strings or file paths.

:include_diff_info

Include diff header info

:include_plus_and_minus_in_html

Show the +, -, ‘ ’ at the beginning of lines in html output.



35
36
37
38
39
40
41
# File 'lib/diffy/diff.rb', line 35

def initialize(string1, string2, options = {})
  @options = self.class.default_options.merge(options)
  if ! ['strings', 'files'].include?(@options[:source])
    raise ArgumentError, "Invalid :source option #{@options[:source].inspect}. Supported options are 'strings' and 'files'."
  end
  @string1, @string2 = string1, string2
end

Class Attribute Details

.default_formatObject



14
15
16
# File 'lib/diffy/diff.rb', line 14

def default_format
  @default_format ||= :text
end

.default_optionsObject



20
21
22
# File 'lib/diffy/diff.rb', line 20

def default_options
  @default_options ||= ORIGINAL_DEFAULT_OPTIONS.dup
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



26
27
28
# File 'lib/diffy/diff.rb', line 26

def options
  @options
end

#string1Object (readonly)

Returns the value of attribute string1.



26
27
28
# File 'lib/diffy/diff.rb', line 26

def string1
  @string1
end

#string2Object (readonly)

Returns the value of attribute string2.



26
27
28
# File 'lib/diffy/diff.rb', line 26

def string2
  @string2
end

Instance Method Details

#diffObject



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/diffy/diff.rb', line 43

def diff
  @diff ||= begin
    @paths = case options[:source]
      when 'strings'
        [tempfile(string1), tempfile(string2)]
      when 'files'
        [string1, string2]
      end

    diff, _stderr, _process_status = Open3.capture3(diff_bin, *(diff_options + @paths))
    diff.force_encoding('ASCII-8BIT') if diff.respond_to?(:valid_encoding?) && !diff.valid_encoding?
    if diff =~ /\A\s*\Z/ && !options[:allow_empty_diff]
      diff = case options[:source]
      when 'strings' then string1
      when 'files' then File.read(string1)
      end.gsub(/^/, " ")
    end
    diff
  end
ensure
  # unlink the tempfiles explicitly now that the diff is generated
  if defined? @tempfiles # to avoid Ruby warnings about undefined ins var.
    Array(@tempfiles).each do |t|
      begin
        # check that the path is not nil and file still exists.
        # REE seems to be very agressive with when it magically removes
        # tempfiles
        t.unlink if t.path && File.exist?(t.path)
      rescue => e
        warn "#{e.class}: #{e}"
        warn e.backtrace.join("\n")
      end
    end
  end
end

#eachObject



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/diffy/diff.rb', line 79

def each
  lines = case @options[:include_diff_info]
  when false
    # this "primes" the diff and sets up the paths we'll reference below.
    diff

    # caching this regexp improves the performance of the loop by a
    # considerable amount.
    regexp = /^(--- "?#{@paths[0]}"?|\+\+\+ "?#{@paths[1]}"?|@@|\\\\)/

    diff.split("\n").reject{|x| x =~ regexp }.map {|line| line + "\n" }

  when true
    diff.split("\n").map {|line| line + "\n" }
  end

  if block_given?
    lines.each{|line| yield line}
  else
    lines.to_enum
  end
end

#each_chunkObject



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/diffy/diff.rb', line 102

def each_chunk
  old_state = nil
  chunks = inject([]) do |cc, line|
    state = line.each_char.first
    if state == old_state
      cc.last << line
    else
      cc.push line.dup
    end
    old_state = state
    cc
  end

  if block_given?
    chunks.each{|chunk| yield chunk }
  else
    chunks.to_enum
  end
end

#tempfile(string) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/diffy/diff.rb', line 122

def tempfile(string)
  t = Tempfile.new('diffy')
  # ensure tempfiles aren't unlinked when GC runs by maintaining a
  # reference to them.
  @tempfiles ||=[]
  @tempfiles.push(t)
  t.print(string)
  t.flush
  t.close
  t.path
end

#to_s(format = nil) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/diffy/diff.rb', line 134

def to_s(format = nil)
  format ||= self.class.default_format
  formats = Format.instance_methods(false).map{|x| x.to_s}
  if formats.include? format.to_s
    enum = self
    enum.extend Format
    enum.send format
  else
    raise ArgumentError,
      "Format #{format.inspect} not found in #{formats.inspect}"
  end
end