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 }