Add String() methods to parsed types (#48)

This enables clients to move back and forth between parsed objects and
text patches. The generated patches are semantically equal to the parsed
object and should re-parse to the same object, but may not be
byte-for-byte identical to the original input.

In my testing, formatted text patches are usually identical to the
input, but there may be cases where this is not true. Binary patches
always differ. This is because Go's 'compress/flate' package ends
streams with an empty block instead of adding the end-of-stream flag to
the last non-empty block, like Git's C implementation. Since the streams
will always be different for this reason, I chose to also enable default
compression (the test patches I generated with Git used no compression.)

The main tests for this feature involve parsing, formatting, and then
re-parsing a patch to make sure we get equal objects.

Formatting is handled by a new internal formatter type, which allows
writing all data to the same stream. This isn't exposed publicly right
now, but will be useful if there's a need for more flexible formatting
functions in the future, like formatting to a user-provided io.Writer.
This commit is contained in:
Billy Keyes
2024-08-11 14:57:07 -04:00
committed by GitHub
parent 9e0997ef65
commit 8584cd59af
20 changed files with 746 additions and 4 deletions
+33 -2
View File
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"os"
"strings"
)
// File describes changes to a single file. It can be either a text file or a
@@ -38,6 +39,15 @@ type File struct {
ReverseBinaryFragment *BinaryFragment
}
// String returns a git diff representation of this file. The value can be
// parsed by this library to obtain the same File, but may not be the same as
// the original input.
func (f *File) String() string {
var diff strings.Builder
newFormatter(&diff).FormatFile(f)
return diff.String()
}
// TextFragment describes changed lines starting at a specific line in a text file.
type TextFragment struct {
Comment string
@@ -57,9 +67,20 @@ type TextFragment struct {
Lines []Line
}
// Header returns the canonical header of this fragment.
// String returns a git diff format of this fragment. See [File.String] for
// more details on this format.
func (f *TextFragment) String() string {
var diff strings.Builder
newFormatter(&diff).FormatTextFragment(f)
return diff.String()
}
// Header returns a git diff header of this fragment. See [File.String] for
// more details on this format.
func (f *TextFragment) Header() string {
return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", f.OldPosition, f.OldLines, f.NewPosition, f.NewLines, f.Comment)
var hdr strings.Builder
newFormatter(&hdr).FormatTextFragmentHeader(f)
return hdr.String()
}
// Validate checks that the fragment is self-consistent and appliable. Validate
@@ -197,3 +218,13 @@ const (
// BinaryPatchLiteral indicates the data is the exact file content
BinaryPatchLiteral
)
// String returns a git diff format of this fragment. Due to differences in
// zlib implementation between Go and Git, encoded binary data in the result
// will likely differ from what Git produces for the same input. See
// [File.String] for more details on this format.
func (f *BinaryFragment) String() string {
var diff strings.Builder
newFormatter(&diff).FormatBinaryFragment(f)
return diff.String()
}