85 lines
1.9 KiB
Go
85 lines
1.9 KiB
Go
package toolkit
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
|
|
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
|
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
|
"github.com/sergi/go-diff/diffmatchpatch"
|
|
)
|
|
|
|
var differ = diffmatchpatch.New()
|
|
|
|
// WriteFileCheckChanged uses the given callback to collect data to be written
|
|
// to the given filepath with the given permission bits set. If the file's
|
|
// contents have been modified (or if the file didn't previously exist) then
|
|
// true is returned.
|
|
func WriteFileCheckChanged(
|
|
ctx context.Context,
|
|
logger *mlog.Logger,
|
|
path string,
|
|
mode os.FileMode,
|
|
fn func(io.Writer) error,
|
|
) (
|
|
bool, error,
|
|
) {
|
|
var (
|
|
buf = new(bytes.Buffer)
|
|
h = sha256.New()
|
|
w = io.MultiWriter(buf, h)
|
|
|
|
changed bool
|
|
)
|
|
|
|
if err := fn(w); err != nil {
|
|
return false, fmt.Errorf("callback returned: %w", err)
|
|
}
|
|
|
|
f, err := os.Open(path)
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
changed = true
|
|
// fine, we'll just write the file
|
|
} else if err != nil {
|
|
return false, fmt.Errorf("reading contents of existing file: %w", err)
|
|
} else {
|
|
var (
|
|
existingBuf = new(bytes.Buffer)
|
|
existingH = sha256.New()
|
|
existingW = io.MultiWriter(existingBuf, existingH)
|
|
_, copyErr = io.Copy(existingW, f)
|
|
closeErr = f.Close()
|
|
)
|
|
|
|
if err := errors.Join(closeErr, copyErr); err != nil {
|
|
return false, fmt.Errorf("hashing existing file: %w", err)
|
|
}
|
|
|
|
changed = !bytes.Equal(h.Sum(nil), existingH.Sum(nil))
|
|
|
|
if changed && logger.MaxLevel() >= mlog.LevelDebug.Int() {
|
|
var (
|
|
ctx = mctx.Annotate(ctx, "path", path)
|
|
diff = differ.DiffPrettyText(
|
|
differ.DiffMain(existingBuf.String(), buf.String(), false),
|
|
)
|
|
)
|
|
logger.Debug(ctx, "WriteFileCheckChanged diff:\n"+diff)
|
|
}
|
|
}
|
|
|
|
if changed {
|
|
if err := os.WriteFile(path, buf.Bytes(), mode); err != nil {
|
|
return false, fmt.Errorf("writing file: %w", err)
|
|
}
|
|
}
|
|
|
|
return changed, nil
|
|
}
|