Générer et appliquer des diffs en python
Existe-t-il un moyen 'prêt à l'emploi' en python de générer une liste de différences entre deux textes, puis d'appliquer ce diff à un fichier pour obtenir l'autre, plus tard?
Je veux conserver l'historique des révisions d'un texte, mais je ne veux pas enregistrer le texte entier pour chaque révision s'il n'y a qu'une seule ligne éditée. J'ai regardé difflib , mais je ne pouvais pas voir comment générer une liste de seulement les lignes éditées qui peuvent encore être utilisées pour modifier un texte pour obtenir l'autre.
6 réponses
Avez-vous regardé diff-match-patch de google? Apparemment, google Docs utilise cet ensemble d'algorithmes. Il comprend non seulement un module diff, mais aussi un module patch, de sorte que vous pouvez générer le fichier le plus récent à partir de fichiers et de diffs plus anciens.
Une version python est incluse.
AFAIK la plupart des algorithmes de diff utilisent une correspondance simple la plus longue sous-séquence commune , pour trouver la partie commune entre deux textes et tout ce qui reste est considéré comme la différence. Il ne devrait pas être trop difficile de coder votre propre algorithme de programmation dynamique pour y parvenir en python, la page wikipedia ci-dessus fournit également l'algorithme.
Est-ce que ce doit être une solution python?
Mes premières pensées quant à une solution serait d'utiliser soit un système de contrôle de Version (Subversion, Git, etc.) ou diff
/ patch
utilitaires qui sont standard avec un système unix, ou font partie de cygwin
pour un système Windows.
J'ai implémenté une fonction Python pure pour appliquer des correctifs diff pour récupérer l'une des chaînes d'entrée, j'espère que quelqu'un le trouvera utile. Il utilise analyse le format Unified diff .
import re
_hdr_pat = re.compile("^@@ -(\d+),?(\d+)? \+(\d+),?(\d+)? @@$")
def apply_patch(s,patch,revert=False):
"""
Apply unified diff patch to string s to recover newer string.
If revert is True, treat s as the newer string, recover older string.
"""
s = s.splitlines(True)
p = patch.splitlines(True)
t = ''
i = sl = 0
(midx,sign) = (1,'+') if not revert else (3,'-')
while i < len(p) and p[i].startswith(("---","+++")): i += 1 # skip header lines
while i < len(p):
m = _hdr_pat.match(p[i])
if not m: raise Exception("Cannot process diff")
i += 1
l = int(m.group(midx))-1 + (m.group(midx+1) == '0')
t += ''.join(s[sl:l])
sl = l
while i < len(p) and p[i][0] != '@':
if i+1 < len(p) and p[i+1][0] == '\\': line = p[i][:-1]; i += 2
else: line = p[i]; i += 1
if len(line) > 0:
if line[0] == sign or line[0] == ' ': t += line[1:]
sl += (line[0] != sign)
t += ''.join(s[sl:])
return t
S'il y a des lignes d'en-tête ("--- ...\n","+++ ...\n")
, il les ignore. Si nous avons une chaîne de diff unifiée diffstr
représentant le diff entre oldstr
et newstr
:
# recreate `newstr` from `oldstr`+patch
newstr = apply_patch(oldstr, diffstr)
# recreate `oldstr` from `newstr`+patch
oldstr = apply_patch(newstr, diffstr, True)
En Python, vous pouvez générer un diff unifié de deux chaînes en utilisant difflib (partie de la norme bibliothèque):
import difflib
_no_eol = "\ No newline at end of file"
def make_patch(a,b):
"""
Get unified string diff between two strings. Trims top two lines.
Returns empty string if strings are identical.
"""
diffs = difflib.unified_diff(a.splitlines(True),b.splitlines(True),n=0)
try: _,_ = next(diffs),next(diffs)
except StopIteration: pass
return ''.join([d if d[-1] == '\n' else d+'\n'+_no_eol+'\n' for d in diffs])
Sous unix: diff -U0 a.txt b.txt
Le Code est sur GitHub ici avec des tests utilisant des caractères unicode ASCII et aléatoires: https://gist.github.com/noporpoise/16e731849eb1231e86d78f9dfeca3abc
Vous pouvez probablement utiliser unified_diff pour générer la liste des différences dans un fichier. Seuls les textes modifiés dans votre fichier peuvent être écrits dans un nouveau fichier texte où vous pouvez l'utiliser pour votre référence future. C'est le code qui vous aide à écrire seulement la différence pour votre nouveau fichier. J'espère que c'est ce que vous demandez !
diff = difflib.unified_diff(old_file, new_file, lineterm='')
lines = list(diff)[2:]
# linesT = list(diff)[0:3]
print (lines[0])
added = [lineA for lineA in lines if lineA[0] == '+']
with open("output.txt", "w") as fh1:
for line in added:
fh1.write(line)
print '+',added
removed = [lineB for lineB in lines if lineB[0] == '-']
with open("output.txt", "a") as fh1:
for line in removed:
fh1.write(line)
print '-',removed
Utilisez ceci dans votre code pour enregistrer uniquement la sortie de différence !