forked from smallstep/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwrite.go
More file actions
160 lines (139 loc) · 4.06 KB
/
Copy pathwrite.go
File metadata and controls
160 lines (139 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package utils
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/pkg/errors"
"github.com/smallstep/cli-utils/command"
"github.com/smallstep/cli-utils/errs"
"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 os.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()
}
// WriteSnippet writes the given data on the given filename. It surrounds the
// data with a header and footer, and it will replace the previous one.
func WriteSnippet(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 errs.FileError(err, filename)
}
// Read file contents
b, err := os.ReadFile(filename)
if err != nil && !os.IsNotExist(err) {
return errs.FileError(err, filename)
}
// Detect previous configuration
_, start, end := findConfiguration(bytes.NewReader(b))
// Replace previous configuration
f, err := OpenFile(filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perm)
if err != nil {
return errs.FileError(err, filename)
}
if len(b) > 0 {
f.Write(b[:start])
if start == end {
f.WriteString("\n")
}
}
fmt.Fprintf(f, "%s @ %s\n", SnippetHeader, time.Now().UTC().Format(time.RFC3339))
f.Write(data)
if !bytes.HasSuffix(data, []byte("\n")) {
f.WriteString("\n")
}
f.WriteString(SnippetFooter + "\n")
if len(b) > 0 {
f.Write(b[end:])
}
return f.Close()
}
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) (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, SnippetHeader):
inConfig = true
start = counter.offset - int64(len(line)+1)
case inConfig && strings.HasPrefix(line, SnippetFooter):
return lines, start, counter.offset
case inConfig:
lines = append(lines, line)
}
}
return lines, counter.offset, counter.offset
}