web-dev-qa-db-fra.com

diff a Ruby chaîne ou tableau

Comment puis-je faire un diff de deux chaînes ou tableaux dans Ruby?

54
Josh Moore
20
Brian Mitchell

Pour les tableaux, utilisez l'opérateur moins. Par exemple:

>> foo = [1, 2, 3]
=> [1, 2, 3]
>> goo = [2, 3, 4]
=> [2, 3, 4]
>> foo - goo
=> [1]

Ici, la dernière ligne supprime tout de foo qui est également en goo, ne laissant que l'élément 1. Je ne sais pas comment faire cela pour deux chaînes, mais jusqu'à ce que quelqu'un qui en sache des messages, vous pouvez simplement convertir chaque chaîne en un tableau, utilisez l'opérateur moins, puis reconvertissez le résultat.

32
Chris Bunch

J'ai été frustré par l'absence d'une bonne bibliothèque pour cela dans Ruby, alors j'ai écrit http://github.com/samg/diffy . Il utilise diff sous les couvertures, et se concentre sur être pratique et fournir de jolies options de sortie.

24
samg

Pour les chaînes, je voudrais d'abord essayer le Ruby Gem que @ sam-saffron mentionné ci-dessous. Il est plus facile à installer: http://github.com/pvande/differ/tree/master

gem install differ

irb
require 'differ'

one = "one two three"
two = "one two 3"

Differ.format = :color
puts Differ.diff_by_Word(one, two).to_s

Differ.format = :html
puts Differ.diff_by_Word(one, two).to_s
19
da01

Le HTMLDiff que @ da01 mentionne ci-dessus a fonctionné pour moi.

script/plugin install git://github.com/myobie/htmldiff.git

# bottom of environment.rb
require 'htmldiff'

# in model
class Page < ActiveRecord::Base
  extend HTMLDiff
end

# in view
<h1>Revisions for <%= @page.name %></h1>
<ul>
<% @page.revisions.each do |revision| %>
  <li>
    <b>Revised <%= distance_of_time_in_words_to_now revision.created_at %> ago</b><BR>
      <%= Page.diff(
        revision.changes['description'][0],
        revision.changes['description'][1]
      ) %>
      <BR><BR>
  </li>
<% end %>

# in style.css
ins.diffmod, ins.diffins { background: #d4fdd5; text-decoration: none; }
del.diffmod, del.diffdel { color: #ff9999; }

Ça a l'air bien. Au fait, je l'ai utilisé avec le acts_as_audited brancher.

5
Brian Armstrong

Il y a aussi diff-lcs qui est disponible comme gemme. Il n'a pas été mis à jour depuis 2004 mais nous l'avons utilisé sans aucun problème.

Edit: Une nouvelle version est sortie en 2011. On dirait qu'elle est de retour en développement actif.

http://rubygems.org/gems/diff-lcs

5
Grant Hutchins
t=s2.chars; s1.chars.map{|c| c == t.shift ? c : '^'}.join

Cette simple ligne donne un ^ aux positions qui ne correspondent pas. C'est souvent suffisant et il est possible de copier/coller.

3
Steve

Juste pour le bénéfice des gens de Windows: diffy a l'air génial mais je crois que cela ne fonctionnera que sur * nix (corrigez-moi si je me trompe). Cela n'a certainement pas fonctionné sur ma machine.

Differ a fonctionné pour moi (Windows 7 x64, Ruby 1.8.7).

2
russthegibbon

Je viens de trouver un nouveau projet qui semble assez flexible:

http://github.com/pvande/differ/tree/master

L'essayer et essaiera de publier une sorte de rapport.

2
Sam Saffron

J'avais le même doute et la solution que j'ai trouvée n'est pas 100% Ruby, mais c'est la meilleure pour moi. Le problème avec diff.rb est qu'il n'a pas un joli formateur, pour montrer les différences de manière humanisée. J'ai donc utilisé diff du système d'exploitation avec ce code:

 def diff str1, str2
   system "diff #{file_for str1} #{file_for str2}"
 end

 private
 def file_for text
   exp = Tempfile.new("bk", "/tmp").open
   exp.write(text)
   exp.close
   exp.path
 end
2
Daniel Cukier

Pour obtenir une résolution caractère par caractère, j'ai ajouté une nouvelle fonction à gem damerau-levenshtein

require "damerau-levenshtein"
differ = DamerauLevenshtein::Differ.new
differ.run "Something", "Smothing"
# returns ["S<ins>o</ins>m<subst>e</subst>thing", 
#  "S<del>o</del>m<subst>o</subst>thing"]

ou avec analyse:

require "damerau-levenshtein"
require "nokogiri"

differ = DamerauLevenshtein::Differ.new
res = differ.run("Something", "Smothing!")
nodes = Nokogiri::XML("<root>#{res.first}</root>")

markup = nodes.root.children.map do |n|
  case n.name
  when "text"
    n.text
  when "del"
    "~~#{n.children.first.text}~~"
  when "ins"
    "*#{n.children.first.text}*"
  when "subst"
    "**#{n.children.first.text}**"
  end
end.join("")

puts markup
1
dimus
1
grosser