summaryrefslogtreecommitdiff
path: root/vendor/github.com/smallstep/cli-utils/fileutil/write.go
diff options
context:
space:
mode:
authorFelix Hanley <felix@userspace.com.au>2025-01-20 04:25:05 +0000
committerFelix Hanley <felix@userspace.com.au>2025-01-20 04:25:05 +0000
commitf82adc0030a993ff25cbf70cf81d75900f455e6a (patch)
tree74bd8e701161262fb34d415aebfef064dbb842d6 /vendor/github.com/smallstep/cli-utils/fileutil/write.go
parent260b74748ab6e0a10d73cd97061996bd4cc70481 (diff)
downloadcaddy-f82adc0030a993ff25cbf70cf81d75900f455e6a.tar.gz
caddy-f82adc0030a993ff25cbf70cf81d75900f455e6a.tar.bz2
Upgrade caddy to 2.9.1
Diffstat (limited to 'vendor/github.com/smallstep/cli-utils/fileutil/write.go')
-rw-r--r--vendor/github.com/smallstep/cli-utils/fileutil/write.go232
1 files changed, 232 insertions, 0 deletions
diff --git a/vendor/github.com/smallstep/cli-utils/fileutil/write.go b/vendor/github.com/smallstep/cli-utils/fileutil/write.go
new file mode 100644
index 0000000..ba8194b
--- /dev/null
+++ b/vendor/github.com/smallstep/cli-utils/fileutil/write.go
@@ -0,0 +1,232 @@
+package fileutil
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/pkg/errors"
+
+ "github.com/smallstep/cli-utils/command"
+ "github.com/smallstep/cli-utils/ui"
+)
+
+var (
+ // ErrFileExists is the error returned if a file exists.
+ ErrFileExists = errors.New("file exists")
+
+ // ErrIsDir is the error returned if the file is a directory.
+ ErrIsDir = errors.New("file is a directory")
+
+ // SnippetHeader is the header of a step generated snippet in a
+ // configuration file.
+ SnippetHeader = "# autogenerated by step"
+
+ // SnippetFooter is the header of a step generated snippet in a
+ // configuration file.
+ SnippetFooter = "# end"
+)
+
+// WriteFile wraps ioutil.WriteFile with a prompt to overwrite a file if
+// the file exists. It returns ErrFileExists if the user picks to not overwrite
+// the file. If force is set to true, the prompt will not be presented and the
+// file if exists will be overwritten.
+func WriteFile(filename string, data []byte, perm os.FileMode) error {
+ if command.IsForce() {
+ return os.WriteFile(filename, data, perm)
+ }
+
+ st, err := os.Stat(filename)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return os.WriteFile(filename, data, perm)
+ }
+ return errors.Wrapf(err, "error reading information for %s", filename)
+ }
+
+ if st.IsDir() {
+ return ErrIsDir
+ }
+
+ str, err := ui.Prompt(fmt.Sprintf("Would you like to overwrite %s [y/n]", filename), ui.WithValidateYesNo())
+ if err != nil {
+ return err
+ }
+ switch strings.ToLower(strings.TrimSpace(str)) {
+ case "y", "yes":
+ case "n", "no":
+ return ErrFileExists
+ }
+
+ return os.WriteFile(filename, data, perm)
+}
+
+// AppendNewLine appends the given data at the end of the file. If the last
+// character of the file does not contain an LF it prepends it to the data.
+func AppendNewLine(filename string, data []byte, perm os.FileMode) error {
+ f, err := OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, perm)
+ if err != nil {
+ return err
+ }
+ // Read last character
+ if st, err := f.File.Stat(); err == nil && st.Size() != 0 {
+ last := make([]byte, 1)
+ f.Seek(-1, 2)
+ f.Read(last)
+ if last[0] != '\n' {
+ f.WriteString("\n")
+ }
+ }
+ f.Write(data)
+ return f.Close()
+}
+
+func writeChunk(filename string, data []byte, hasHeaderFooter bool, header, footer string, perm os.FileMode) error {
+ // Get file permissions
+ if st, err := os.Stat(filename); err == nil {
+ perm = st.Mode()
+ } else if !os.IsNotExist(err) {
+ return FileError(err, filename)
+ }
+
+ // Read file contents
+ b, err := os.ReadFile(filename)
+ if err != nil && !os.IsNotExist(err) {
+ return FileError(err, filename)
+ }
+
+ // Detect previous configuration
+ _, start, end := findConfiguration(bytes.NewReader(b), header, footer)
+
+ // Replace previous configuration
+ f, err := OpenFile(filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perm)
+ if err != nil {
+ return FileError(err, filename)
+ }
+ if len(b) > 0 {
+ f.Write(b[:start])
+ if start == end {
+ f.WriteString("\n")
+ }
+ }
+ if !hasHeaderFooter {
+ fmt.Fprintf(f, "%s @ %s\n", header, time.Now().UTC().Format(time.RFC3339))
+ }
+ f.Write(data)
+ if !bytes.HasSuffix(data, []byte("\n")) {
+ f.WriteString("\n")
+ }
+ if !hasHeaderFooter {
+ f.WriteString(footer + "\n")
+ }
+ if len(b) > 0 {
+ f.Write(b[end:])
+ }
+ return f.Close()
+}
+
+// WriteSnippet writes the given data into the given filename. It surrounds the
+// data with a default header and footer, and it will replace the previous one.
+func WriteSnippet(filename string, data []byte, perm os.FileMode) error {
+ return writeChunk(filename, data, false, SnippetHeader, SnippetFooter, perm)
+}
+
+// PrependLine prepends the given line into the given filename and removes
+// other instances of the line in the file.
+func PrependLine(filename string, data []byte, perm os.FileMode) error {
+ // Get file permissions
+ if st, err := os.Stat(filename); err == nil {
+ perm = st.Mode()
+ } else if !os.IsNotExist(err) {
+ return FileError(err, filename)
+ }
+
+ // Read file contents
+ b, err := os.ReadFile(filename)
+ if err != nil && !os.IsNotExist(err) {
+ return FileError(err, filename)
+ }
+
+ line := string(data)
+ result := []string{string(data)}
+ for _, l := range strings.Split(string(b), "\n") {
+ if l != "" && !strings.HasPrefix(l, line) {
+ result = append(result, l)
+ }
+ }
+
+ if err := os.WriteFile(filename, []byte(strings.Join(result, "\n")), perm); err != nil {
+ return FileError(err, filename)
+ }
+ return nil
+}
+
+// RemoveLine removes a single line which contains the given substring from the
+// given file.
+func RemoveLine(filename, substr string) error {
+ var perm os.FileMode
+ // Get file permissions
+ st, err := os.Stat(filename)
+ switch {
+ case os.IsNotExist(err):
+ return nil
+ case err != nil:
+ return FileError(err, filename)
+ default:
+ perm = st.Mode()
+ }
+
+ // Read file contents
+ b, err := os.ReadFile(filename)
+ if err != nil && !os.IsNotExist(err) {
+ return FileError(err, filename)
+ }
+
+ old := strings.Split(string(b), "\n")
+ for i, l := range old {
+ if !strings.Contains(l, substr) {
+ continue
+ }
+ if err := os.WriteFile(filename, []byte(strings.Join(append(old[:i], old[i+1:]...), "\n")), perm); err != nil {
+ return FileError(err, filename)
+ }
+ break
+ }
+
+ return nil
+}
+
+type offsetCounter struct {
+ offset int64
+}
+
+func (o *offsetCounter) ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
+ advance, token, err = bufio.ScanLines(data, atEOF)
+ o.offset += int64(advance)
+ return
+}
+
+func findConfiguration(r io.Reader, header, footer string) (lines []string, start, end int64) {
+ var inConfig bool
+ counter := new(offsetCounter)
+ scanner := bufio.NewScanner(r)
+ scanner.Split(counter.ScanLines)
+ for scanner.Scan() {
+ line := scanner.Text()
+ switch {
+ case !inConfig && strings.HasPrefix(line, header):
+ inConfig = true
+ start = counter.offset - int64(len(line)+1)
+ case inConfig && strings.HasPrefix(line, footer):
+ return lines, start, counter.offset
+ case inConfig:
+ lines = append(lines, line)
+ }
+ }
+
+ return lines, counter.offset, counter.offset
+}