summaryrefslogtreecommitdiff
path: root/src/pkg/patch/textdiff.go
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2009-11-04 17:55:06 -0800
committerRuss Cox <rsc@golang.org>2009-11-04 17:55:06 -0800
commitce9b3deaa4fa22e9d5580df05e9762b988793baa (patch)
treeeacfeb41baaf6752deba91909b8f96d90dea476e /src/pkg/patch/textdiff.go
parent782c3f76d1c7af511daf0d44ba29737f3e1de655 (diff)
downloadgolang-ce9b3deaa4fa22e9d5580df05e9762b988793baa.tar.gz
package patch
R=r http://go/go-review/1018043
Diffstat (limited to 'src/pkg/patch/textdiff.go')
-rw-r--r--src/pkg/patch/textdiff.go171
1 files changed, 171 insertions, 0 deletions
diff --git a/src/pkg/patch/textdiff.go b/src/pkg/patch/textdiff.go
new file mode 100644
index 000000000..db8527682
--- /dev/null
+++ b/src/pkg/patch/textdiff.go
@@ -0,0 +1,171 @@
+package patch
+
+import (
+ "bytes";
+ "os";
+)
+
+type TextDiff []TextChunk
+
+// A TextChunk specifies an edit to a section of a file:
+// the text beginning at Line, which should be exactly Old,
+// is to be replaced with New.
+type TextChunk struct {
+ Line int;
+ Old []byte;
+ New []byte;
+}
+
+func ParseTextDiff(raw []byte) (TextDiff, os.Error) {
+ // Copy raw so it is safe to keep references to slices.
+ _, chunks := sections(raw, "@@ -");
+ delta := 0;
+ diff := make(TextDiff, len(chunks));
+ for i, raw := range chunks {
+ c := &diff[i];
+
+ // Parse start line: @@ -oldLine,oldCount +newLine,newCount @@ junk
+ chunk := splitLines(raw);
+ chunkHeader := chunk[0];
+ var ok bool;
+ var oldLine, oldCount, newLine, newCount int;
+ s := chunkHeader;
+ if oldLine, s, ok = atoi(s, "@@ -", 10); !ok {
+ ErrChunkHdr:
+ return nil, SyntaxError("unexpected chunk header line: " + string(chunkHeader));
+ }
+ if len(s) == 0 || s[0] != ',' {
+ oldCount = 1;
+ } else if oldCount, s, ok = atoi(s, ",", 10); !ok {
+ goto ErrChunkHdr;
+ }
+ if newLine, s, ok = atoi(s, " +", 10); !ok {
+ goto ErrChunkHdr;
+ }
+ if len(s) == 0 || s[0] != ',' {
+ newCount = 1;
+ } else if newCount, s, ok = atoi(s, ",", 10); !ok {
+ goto ErrChunkHdr;
+ }
+ if !hasPrefix(s, " @@") {
+ goto ErrChunkHdr;
+ }
+
+ // Special case: for created or deleted files, the empty half
+ // is given as starting at line 0. Translate to line 1.
+ if oldCount == 0 && oldLine == 0 {
+ oldLine = 1;
+ }
+ if newCount == 0 && newLine == 0 {
+ newLine = 1;
+ }
+
+ // Count lines in text
+ var dropOldNL, dropNewNL bool;
+ var nold, nnew int;
+ var lastch byte;
+ chunk = chunk[1:len(chunk)];
+ for _, l := range chunk {
+ if nold == oldCount && nnew == newCount && (len(l) == 0 || l[0] != '\\') {
+ if len(bytes.TrimSpace(l)) != 0 {
+ return nil, SyntaxError("too many chunk lines");
+ }
+ continue;
+ }
+ if len(l) == 0 {
+ return nil, SyntaxError("empty chunk line");
+ }
+ switch l[0] {
+ case '+':
+ nnew++;
+ case '-':
+ nold++;
+ case ' ':
+ nnew++;
+ nold++;
+ case '\\':
+ if _, ok := skip(l, "\\ No newline at end of file"); ok {
+ switch lastch {
+ case '-':
+ dropOldNL = true;
+ case '+':
+ dropNewNL = true;
+ case ' ':
+ dropOldNL = true;
+ dropNewNL = true;
+ default:
+ return nil, SyntaxError("message `\\ No newline at end of file' out of context");
+ }
+ break;
+ }
+ fallthrough;
+ default:
+ return nil, SyntaxError("unexpected chunk line: " + string(l));
+ }
+ lastch = l[0];
+ }
+
+ // Does it match the header?
+ if nold != oldCount || nnew != newCount {
+ return nil, SyntaxError("chunk header does not match line count: " + string(chunkHeader));
+ }
+ if oldLine+delta != newLine {
+ return nil, SyntaxError("chunk delta is out of sync with previous chunks");
+ }
+ delta += nnew-nold;
+ c.Line = oldLine;
+
+ var old, new bytes.Buffer;
+ nold = 0;
+ nnew = 0;
+ for _, l := range chunk {
+ if nold == oldCount && nnew == newCount {
+ break;
+ }
+ ch, l := l[0], l[1:len(l)];
+ if ch == '\\' {
+ continue;
+ }
+ if ch != '+' {
+ old.Write(l);
+ nold++;
+ }
+ if ch != '-' {
+ new.Write(l);
+ nnew++;
+ }
+ }
+ c.Old = old.Bytes();
+ c.New = new.Bytes();
+ if dropOldNL {
+ c.Old = c.Old[0 : len(c.Old)-1];
+ }
+ if dropNewNL {
+ c.New = c.New[0 : len(c.New)-1];
+ }
+ }
+ return diff, nil;
+}
+
+var ErrPatchFailure = os.NewError("patch did not apply cleanly")
+
+// Apply applies the changes listed in the diff
+// to the data, returning the new version.
+func (d TextDiff) Apply(data []byte) ([]byte, os.Error) {
+ var buf bytes.Buffer;
+ line := 1;
+ for _, c := range d {
+ var ok bool;
+ var prefix []byte;
+ prefix, data, ok = getLine(data, c.Line - line);
+ if !ok || !bytes.HasPrefix(data, c.Old) {
+ return nil, ErrPatchFailure;
+ }
+ buf.Write(prefix);
+ data = data[len(c.Old):len(data)];
+ buf.Write(c.New);
+ line = c.Line + bytes.Count(c.Old, newline);
+ }
+ buf.Write(data);
+ return buf.Bytes(), nil;
+}