post inicial
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Límites de la generación de diffs: por encima, usar "diff -u" del sistema.
|
||||
const (
|
||||
maxDiffLines = 50000
|
||||
maxEditDist = 5000
|
||||
)
|
||||
|
||||
// ErrTooDifferent indica que los archivos difieren demasiado para calcular
|
||||
// el diff con memoria acotada.
|
||||
var ErrTooDifferent = errors.New("files are too different to diff efficiently; use exec diff -u")
|
||||
|
||||
type opKind byte
|
||||
|
||||
const (
|
||||
opEqual opKind = ' '
|
||||
opDelete opKind = '-'
|
||||
opInsert opKind = '+'
|
||||
)
|
||||
|
||||
type edit struct {
|
||||
kind opKind
|
||||
text string
|
||||
a, b int // índices en old/new (0-based) para calcular cabeceras
|
||||
}
|
||||
|
||||
// Unified genera un diff unificado entre old y new con context líneas de
|
||||
// contexto. Devuelve "" si son iguales. Sirve para documentar exactamente
|
||||
// los cambios realizados (§40, §88) y para crear parches propios.
|
||||
func Unified(oldName, newName, old, new string, context int) (string, error) {
|
||||
if old == new {
|
||||
return "", nil
|
||||
}
|
||||
// Las líneas conservan su "\n": así una última línea sin salto final se
|
||||
// distingue de la misma línea con salto, y el diff lo refleja.
|
||||
a, b := splitKeep(old), splitKeep(new)
|
||||
if len(a) > maxDiffLines || len(b) > maxDiffLines {
|
||||
return "", ErrTooDifferent
|
||||
}
|
||||
edits, err := myers(a, b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "--- %s\n+++ %s\n", oldName, newName)
|
||||
// Agrupar cambios en hunks con contexto.
|
||||
for i := 0; i < len(edits); {
|
||||
// Buscar el siguiente cambio.
|
||||
for i < len(edits) && edits[i].kind == opEqual {
|
||||
i++
|
||||
}
|
||||
if i >= len(edits) {
|
||||
break
|
||||
}
|
||||
start := max(i-context, 0)
|
||||
end := i
|
||||
// Extender mientras los cambios estén separados por <= 2*context iguales.
|
||||
for end < len(edits) {
|
||||
if edits[end].kind != opEqual {
|
||||
end++
|
||||
continue
|
||||
}
|
||||
run := end
|
||||
for run < len(edits) && edits[run].kind == opEqual {
|
||||
run++
|
||||
}
|
||||
if run >= len(edits) || run-end > 2*context {
|
||||
end = min(end+context, len(edits))
|
||||
break
|
||||
}
|
||||
end = run
|
||||
}
|
||||
writeHunk(&sb, edits[start:end])
|
||||
i = end
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func writeHunk(sb *strings.Builder, es []edit) {
|
||||
oldStart, newStart := -1, -1
|
||||
oldN, newN := 0, 0
|
||||
for _, e := range es {
|
||||
if e.kind != opInsert {
|
||||
if oldStart < 0 {
|
||||
oldStart = e.a
|
||||
}
|
||||
oldN++
|
||||
}
|
||||
if e.kind != opDelete {
|
||||
if newStart < 0 {
|
||||
newStart = e.b
|
||||
}
|
||||
newN++
|
||||
}
|
||||
}
|
||||
// Para lados vacíos, la convención es la línea anterior (o 0).
|
||||
if oldStart < 0 {
|
||||
oldStart = es[0].a - 1
|
||||
}
|
||||
if newStart < 0 {
|
||||
newStart = es[0].b - 1
|
||||
}
|
||||
fmt.Fprintf(sb, "@@ -%s +%s @@\n", rangeStr(oldStart, oldN), rangeStr(newStart, newN))
|
||||
for _, e := range es {
|
||||
sb.WriteByte(byte(e.kind))
|
||||
sb.WriteString(strings.TrimSuffix(e.text, "\n"))
|
||||
sb.WriteByte('\n')
|
||||
if !strings.HasSuffix(e.text, "\n") {
|
||||
sb.WriteString("\\ No newline at end of file\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// splitKeep divide en líneas conservando el "\n" de cada una.
|
||||
func splitKeep(s string) []string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
lines := strings.SplitAfter(s, "\n")
|
||||
if lines[len(lines)-1] == "" {
|
||||
lines = lines[:len(lines)-1]
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func rangeStr(start, n int) string {
|
||||
if n == 1 {
|
||||
return fmt.Sprint(start + 1)
|
||||
}
|
||||
if n == 0 {
|
||||
return fmt.Sprintf("%d,0", start+1)
|
||||
}
|
||||
return fmt.Sprintf("%d,%d", start+1, n)
|
||||
}
|
||||
|
||||
// myers calcula el script de edición mínimo (algoritmo de Myers, O((N+M)D)).
|
||||
// Guarda solo la franja [-d, d] de cada iteración, así la memoria es O(D²).
|
||||
func myers(a, b []string) ([]edit, error) {
|
||||
n, m := len(a), len(b)
|
||||
var trace [][]int
|
||||
v := map[int]int{1: 0}
|
||||
var dFinal int
|
||||
found := false
|
||||
for d := 0; d <= n+m; d++ {
|
||||
if d > maxEditDist {
|
||||
return nil, ErrTooDifferent
|
||||
}
|
||||
snap := make([]int, 2*d+3)
|
||||
for k := -d; k <= d; k += 2 {
|
||||
var x int
|
||||
if k == -d || (k != d && v[k-1] < v[k+1]) {
|
||||
x = v[k+1]
|
||||
} else {
|
||||
x = v[k-1] + 1
|
||||
}
|
||||
y := x - k
|
||||
for x < n && y < m && a[x] == b[y] {
|
||||
x++
|
||||
y++
|
||||
}
|
||||
v[k] = x
|
||||
if x >= n && y >= m {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
for k := -d - 1; k <= d+1; k++ {
|
||||
snap[k+d+1] = v[k]
|
||||
}
|
||||
trace = append(trace, snap)
|
||||
if found {
|
||||
dFinal = d
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
get := func(d, k int) int {
|
||||
s := trace[d]
|
||||
idx := k + d + 1
|
||||
if idx < 0 || idx >= len(s) {
|
||||
return 0
|
||||
}
|
||||
return s[idx]
|
||||
}
|
||||
var rev []edit
|
||||
x, y := n, m
|
||||
for d := dFinal; d > 0; d-- {
|
||||
k := x - y
|
||||
var prevK int
|
||||
if k == -d || (k != d && get(d-1, k-1) < get(d-1, k+1)) {
|
||||
prevK = k + 1
|
||||
} else {
|
||||
prevK = k - 1
|
||||
}
|
||||
prevX := get(d-1, prevK)
|
||||
prevY := prevX - prevK
|
||||
for x > prevX && y > prevY {
|
||||
x--
|
||||
y--
|
||||
rev = append(rev, edit{kind: opEqual, text: a[x], a: x, b: y})
|
||||
}
|
||||
if x == prevX {
|
||||
y--
|
||||
rev = append(rev, edit{kind: opInsert, text: b[y], a: x, b: y})
|
||||
} else {
|
||||
x--
|
||||
rev = append(rev, edit{kind: opDelete, text: a[x], a: x, b: y})
|
||||
}
|
||||
}
|
||||
for x > 0 && y > 0 {
|
||||
x--
|
||||
y--
|
||||
rev = append(rev, edit{kind: opEqual, text: a[x], a: x, b: y})
|
||||
}
|
||||
out := make([]edit, len(rev))
|
||||
for i := range rev {
|
||||
out[i] = rev[len(rev)-1-i]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user