You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

diff.rb 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. # frozen_string_literal: true
  2. module RedmineDiff
  3. class Diff
  4. VERSION = 0.3
  5. def Diff.lcs(a, b)
  6. astart = 0
  7. bstart = 0
  8. afinish = a.length-1
  9. bfinish = b.length-1
  10. mvector = []
  11. # First we prune off any common elements at the beginning
  12. while (astart <= afinish) && (bstart <= afinish) && (a[astart] == b[bstart])
  13. mvector[astart] = bstart
  14. astart += 1
  15. bstart += 1
  16. end
  17. # now the end
  18. while (astart <= afinish) && (bstart <= bfinish) && (a[afinish] == b[bfinish])
  19. mvector[afinish] = bfinish
  20. afinish -= 1
  21. bfinish -= 1
  22. end
  23. bmatches = b.reverse_hash(bstart..bfinish)
  24. thresh = []
  25. links = []
  26. (astart..afinish).each { |aindex|
  27. aelem = a[aindex]
  28. next unless bmatches.has_key? aelem
  29. k = nil
  30. bmatches[aelem].reverse_each { |bindex|
  31. if k && (thresh[k] > bindex) && (thresh[k-1] < bindex)
  32. thresh[k] = bindex
  33. else
  34. k = thresh.replacenextlarger(bindex, k)
  35. end
  36. links[k] = [ (k==0) ? nil : links[k-1], aindex, bindex ] if k
  37. }
  38. }
  39. if !thresh.empty?
  40. link = links[thresh.length-1]
  41. while link
  42. mvector[link[1]] = link[2]
  43. link = link[0]
  44. end
  45. end
  46. return mvector
  47. end
  48. def makediff(a, b)
  49. mvector = Diff.lcs(a, b)
  50. ai = bi = 0
  51. while ai < mvector.length
  52. bline = mvector[ai]
  53. if bline
  54. while bi < bline
  55. discardb(bi, b[bi])
  56. bi += 1
  57. end
  58. match(ai, bi)
  59. bi += 1
  60. else
  61. discarda(ai, a[ai])
  62. end
  63. ai += 1
  64. end
  65. while ai < a.length
  66. discarda(ai, a[ai])
  67. ai += 1
  68. end
  69. while bi < b.length
  70. discardb(bi, b[bi])
  71. bi += 1
  72. end
  73. match(ai, bi)
  74. 1
  75. end
  76. def compactdiffs
  77. diffs = []
  78. @diffs.each { |df|
  79. i = 0
  80. curdiff = []
  81. while i < df.length
  82. whot = df[i][0]
  83. s = @isstring ? df[i][2].chr : [df[i][2]]
  84. p = df[i][1]
  85. last = df[i][1]
  86. i += 1
  87. while df[i] && df[i][0] == whot && df[i][1] == last+1
  88. s << df[i][2]
  89. last = df[i][1]
  90. i += 1
  91. end
  92. curdiff.push [whot, p, s]
  93. end
  94. diffs.push curdiff
  95. }
  96. return diffs
  97. end
  98. attr_reader :diffs, :difftype
  99. def initialize(diffs_or_a, b = nil, isstring = nil)
  100. if b.nil?
  101. @diffs = diffs_or_a
  102. @isstring = isstring
  103. else
  104. @diffs = []
  105. @curdiffs = []
  106. makediff(diffs_or_a, b)
  107. @difftype = diffs_or_a.class
  108. end
  109. end
  110. def match(ai, bi)
  111. @diffs.push @curdiffs unless @curdiffs.empty?
  112. @curdiffs = []
  113. end
  114. def discarda(i, elem)
  115. @curdiffs.push ['-', i, elem]
  116. end
  117. def discardb(i, elem)
  118. @curdiffs.push ['+', i, elem]
  119. end
  120. def compact
  121. return Diff.new(compactdiffs)
  122. end
  123. def compact!
  124. @diffs = compactdiffs
  125. end
  126. def inspect
  127. @diffs.inspect
  128. end
  129. end
  130. end
  131. module Diffable
  132. def diff(b)
  133. RedmineDiff::Diff.new(self, b)
  134. end
  135. # Create a hash that maps elements of the array to arrays of indices
  136. # where the elements are found.
  137. def reverse_hash(range = (0...self.length))
  138. revmap = {}
  139. range.each { |i|
  140. elem = self[i]
  141. if revmap.has_key? elem
  142. revmap[elem].push i
  143. else
  144. revmap[elem] = [i]
  145. end
  146. }
  147. return revmap
  148. end
  149. def replacenextlarger(value, high = nil)
  150. high ||= self.length
  151. if self.empty? || value > self[-1]
  152. push value
  153. return high
  154. end
  155. # binary search for replacement point
  156. low = 0
  157. while low < high
  158. index = (high+low)/2
  159. found = self[index]
  160. return nil if value == found
  161. if value > found
  162. low = index + 1
  163. else
  164. high = index
  165. end
  166. end
  167. self[low] = value
  168. # $stderr << "replace #{value} : 0/#{low}/#{init_high} (#{steps} steps) (#{init_high-low} off )\n"
  169. # $stderr.puts self.inspect
  170. # gets
  171. # p length - low
  172. return low
  173. end
  174. def patch(diff)
  175. newary = nil
  176. if diff.difftype == String
  177. newary = diff.difftype.new('')
  178. else
  179. newary = diff.difftype.new
  180. end
  181. ai = 0
  182. bi = 0
  183. diff.diffs.each { |d|
  184. d.each { |mod|
  185. case mod[0]
  186. when '-'
  187. while ai < mod[1]
  188. newary << self[ai]
  189. ai += 1
  190. bi += 1
  191. end
  192. ai += 1
  193. when '+'
  194. while bi < mod[1]
  195. newary << self[ai]
  196. ai += 1
  197. bi += 1
  198. end
  199. newary << mod[2]
  200. bi += 1
  201. else
  202. raise "Unknown diff action"
  203. end
  204. }
  205. }
  206. while ai < self.length
  207. newary << self[ai]
  208. ai += 1
  209. bi += 1
  210. end
  211. return newary
  212. end
  213. end
  214. class Array
  215. include Diffable
  216. end
  217. class String
  218. include Diffable
  219. end
  220. =begin
  221. = Diff
  222. (({diff.rb})) - computes the differences between two arrays or
  223. strings. Copyright (C) 2001 Lars Christensen
  224. == Synopsis
  225. diff = Diff.new(a, b)
  226. b = a.patch(diff)
  227. == Class Diff
  228. === Class Methods
  229. --- Diff.new(a, b)
  230. --- a.diff(b)
  231. Creates a Diff object which represent the differences between
  232. ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays
  233. of any objects, strings, or object of any class that include
  234. module ((|Diffable|))
  235. == Module Diffable
  236. The module ((|Diffable|)) is intended to be included in any class for
  237. which differences are to be computed. Diffable is included into String
  238. and Array when (({diff.rb})) is (({require}))'d.
  239. Classes including Diffable should implement (({[]})) to get element at
  240. integer indices, (({<<})) to append elements to the object and
  241. (({ClassName#new})) should accept 0 arguments to create a new empty
  242. object.
  243. === Instance Methods
  244. --- Diffable#patch(diff)
  245. Applies the differences from ((|diff|)) to the object ((|obj|))
  246. and return the result. ((|obj|)) is not changed. ((|obj|)) and
  247. can be either an array or a string, but must match the object
  248. from which the ((|diff|)) was created.
  249. =end