Skip to content

Commit 4be2223

Browse files
committed
In output-mode inject do not fail if file not found
The following scenarios can happen for --output-mode inject: - file exists, comments exist: inject output between comments - file exists, comments not found: append output at the end of file - file exists, but empty: save the whole output into the file - file not found: create the target file and save the ooutput into it Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>
1 parent f957813 commit 4be2223

File tree

7 files changed

+173
-56
lines changed

7 files changed

+173
-56
lines changed

docs/user-guide/configuration.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,9 @@ flag) is not empty. Insersion behavior can be controlled by `output.mode` (or
185185

186186
- `inject` (default)
187187

188-
Partially replace the `output-file` with generated output. This will fail if
189-
`output-file` doesn't exist. Also will fail if `output-file` doesn't already
190-
have surrounding comments.
188+
Partially replace the `output-file` with generated output. This will create
189+
the `output-file` if it doesn't exist. It will also append to `output-file`
190+
if it doesn't have surrounding comments.
191191

192192
- `replace`
193193

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<!-- BEGIN_TF_DOCS -->
2+
Lorem ipsum dolor sit amet, consectetur adipiscing elit
3+
<!-- END_TF_DOCS -->
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<!-- BEGIN_TF_DOCS -->
2+
Lorem ipsum dolor sit amet, consectetur adipiscing elit
3+
<!-- END_TF_DOCS -->
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Foo
2+
3+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4+
5+
## Bar
6+
7+
sed do eiusmod tempor incididunt ut labore et dolore magna
8+
aliqua.
9+
10+
- Ut enim ad minim veniam
11+
- quis nostrud exercitation
12+
13+
ullamco laboris nisi ut aliquip ex ea commodo consequat.
14+
Duis aute irure dolor in reprehenderit in voluptate velit
15+
16+
## Baz
17+
18+
esse cillum dolore eu fugiat nulla pariatur.
19+
20+
<!-- BEGIN_TF_DOCS -->
21+
Lorem ipsum dolor sit amet, consectetur adipiscing elit
22+
<!-- END_TF_DOCS -->
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Foo
2+
3+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4+
5+
## Bar
6+
7+
sed do eiusmod tempor incididunt ut labore et dolore magna
8+
aliqua.
9+
10+
- Ut enim ad minim veniam
11+
- quis nostrud exercitation
12+
13+
ullamco laboris nisi ut aliquip ex ea commodo consequat.
14+
Duis aute irure dolor in reprehenderit in voluptate velit
15+
16+
## Baz
17+
18+
esse cillum dolore eu fugiat nulla pariatur.

internal/cli/writer.go

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
)
2222

2323
const (
24-
errFileEmpty = "file content is empty"
2524
errTemplateEmpty = "template is missing"
2625
errBeginCommentMissing = "begin comment is missing"
2726
errEndCommentMissing = "end comment is missing"
@@ -31,6 +30,7 @@ const (
3130
// stdoutWriter writes content to os.Stdout.
3231
type stdoutWriter struct{}
3332

33+
// Write content to Stdout
3434
func (sw *stdoutWriter) Write(p []byte) (int, error) {
3535
return os.Stdout.Write([]byte(string(p) + "\n"))
3636
}
@@ -60,11 +60,10 @@ type fileWriter struct {
6060
writer io.Writer
6161
}
6262

63+
// Write content to target file
6364
func (fw *fileWriter) Write(p []byte) (int, error) {
6465
filename := fw.fullFilePath()
6566

66-
var buf bytes.Buffer
67-
6867
if fw.template == "" {
6968
// template is optional for mode replace
7069
if fw.mode == outputModeReplace {
@@ -73,61 +72,97 @@ func (fw *fileWriter) Write(p []byte) (int, error) {
7372
return 0, errors.New(errTemplateEmpty)
7473
}
7574

76-
tmpl := template.Must(template.New("content").Parse(fw.template))
77-
if err := tmpl.ExecuteTemplate(&buf, "content", struct {
78-
Content string
79-
}{
80-
Content: string(p),
81-
}); err != nil {
75+
// apply template to generated output
76+
buf, err := fw.apply(p)
77+
if err != nil {
8278
return 0, err
8379
}
8480

8581
// Replace the content of 'filename' with generated output,
86-
// no further processing is reequired for mode 'replace'.
82+
// no further processing is required for mode 'replace'.
8783
if fw.mode == outputModeReplace {
8884
return fw.write(filename, buf.Bytes())
8985
}
9086

91-
content := buf.String()
92-
93-
f, err := os.ReadFile(filename)
87+
content, err := os.ReadFile(filename)
9488
if err != nil {
95-
return 0, err
89+
// In mode 'inject', if target file not found:
90+
// create it and save the generated output into it.
91+
return fw.write(filename, buf.Bytes())
92+
}
93+
94+
if len(content) == 0 {
95+
// In mode 'inject', if target file is found BUT it's empty:
96+
// save the generated output into it.
97+
return fw.write(filename, buf.Bytes())
98+
}
99+
100+
return fw.inject(filename, string(content), buf.String())
101+
}
102+
103+
// fullFilePath of the target file. If file is absolute path it will be
104+
// used as is, otherwise dir (i.e. module root folder) will be joined to
105+
// it.
106+
func (fw *fileWriter) fullFilePath() string {
107+
if filepath.IsAbs(fw.file) {
108+
return fw.file
109+
}
110+
return filepath.Join(fw.dir, fw.file)
111+
}
112+
113+
// apply template to generated output
114+
func (fw *fileWriter) apply(p []byte) (bytes.Buffer, error) {
115+
type content struct {
116+
Content string
96117
}
97118

98-
fc := string(f)
99-
if fc == "" {
100-
return 0, errors.New(errFileEmpty)
119+
var buf bytes.Buffer
120+
121+
tmpl := template.Must(template.New("content").Parse(fw.template))
122+
err := tmpl.ExecuteTemplate(&buf, "content", content{string(p)})
123+
124+
return buf, err
125+
}
126+
127+
// inject generated output into file.
128+
func (fw *fileWriter) inject(filename string, content string, generated string) (int, error) {
129+
before := strings.Index(content, fw.begin)
130+
after := strings.Index(content, fw.end)
131+
132+
// current file content doesn't have surrounding
133+
// so we're going to append the generated output
134+
// to current file.
135+
if before < 0 && after < 0 {
136+
return fw.write(filename, []byte(content+"\n"+generated))
101137
}
102138

103-
before := strings.Index(fc, fw.begin)
139+
// begin comment is missing
104140
if before < 0 {
105141
return 0, errors.New(errBeginCommentMissing)
106142
}
107-
content = fc[:before] + content
108143

109-
after := strings.Index(fc, fw.end)
144+
generated = content[:before] + generated
145+
146+
// end comment is missing
110147
if after < 0 {
111148
return 0, errors.New(errEndCommentMissing)
112149
}
150+
151+
// end comment is before begin comment
113152
if after < before {
114153
return 0, errors.New(errEndCommentBeforeBegin)
115154
}
116-
content += fc[after+len(fw.end):]
117155

118-
return fw.write(filename, []byte(content))
156+
generated += content[after+len(fw.end):]
157+
158+
return fw.write(filename, []byte(generated))
119159
}
120160

161+
// wrtie the content to io.Writer. If no io.Writer is available,
162+
// it will be written to 'filename'.
121163
func (fw *fileWriter) write(filename string, p []byte) (int, error) {
122164
if fw.writer != nil {
123165
return fw.writer.Write(p)
124166
}
125167
return len(p), os.WriteFile(filename, p, 0644)
126168
}
127-
128-
func (fw *fileWriter) fullFilePath() string {
129-
if filepath.IsAbs(fw.file) {
130-
return fw.file
131-
}
132-
return filepath.Join(fw.dir, fw.file)
133-
}

internal/cli/writer_test.go

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,42 @@ func TestFileWriter(t *testing.T) {
8080
wantErr: false,
8181
errMsg: "",
8282
},
83+
"ModeInjectEmptyFile": {
84+
file: "empty-file.md",
85+
mode: "inject",
86+
template: OutputTemplate,
87+
begin: outputBeginComment,
88+
end: outputEndComment,
89+
writer: &bytes.Buffer{},
90+
91+
expected: "mode-inject-empty-file",
92+
wantErr: false,
93+
errMsg: "",
94+
},
95+
"ModeInjectNoCommentAppend": {
96+
file: "mode-inject-no-comment.md",
97+
mode: "inject",
98+
template: OutputTemplate,
99+
begin: outputBeginComment,
100+
end: outputEndComment,
101+
writer: &bytes.Buffer{},
102+
103+
expected: "mode-inject-no-comment",
104+
wantErr: false,
105+
errMsg: "",
106+
},
107+
"ModeInjectFileMissing": {
108+
file: "file-missing.md",
109+
mode: "inject",
110+
template: OutputTemplate,
111+
begin: outputBeginComment,
112+
end: outputEndComment,
113+
writer: &bytes.Buffer{},
114+
115+
expected: "mode-inject-file-missing",
116+
wantErr: false,
117+
errMsg: "",
118+
},
83119
"ModeReplaceWithComment": {
84120
file: "mode-replace.md",
85121
mode: "replace",
@@ -92,6 +128,30 @@ func TestFileWriter(t *testing.T) {
92128
wantErr: false,
93129
errMsg: "",
94130
},
131+
"ModeReplaceWithCommentEmptyFile": {
132+
file: "mode-replace.md",
133+
mode: "replace",
134+
template: OutputTemplate,
135+
begin: outputBeginComment,
136+
end: outputEndComment,
137+
writer: &bytes.Buffer{},
138+
139+
expected: "mode-replace-with-comment",
140+
wantErr: false,
141+
errMsg: "",
142+
},
143+
"ModeReplaceWithCommentFileMissing": {
144+
file: "file-missing.md",
145+
mode: "replace",
146+
template: OutputTemplate,
147+
begin: outputBeginComment,
148+
end: outputEndComment,
149+
writer: &bytes.Buffer{},
150+
151+
expected: "mode-replace-with-comment",
152+
wantErr: false,
153+
errMsg: "",
154+
},
95155
"ModeReplaceWithoutComment": {
96156
file: "mode-replace.md",
97157
mode: "replace",
@@ -118,18 +178,6 @@ func TestFileWriter(t *testing.T) {
118178
},
119179

120180
// Error writes
121-
"ModeInjectNoFile": {
122-
file: "file-missing.md",
123-
mode: "inject",
124-
template: OutputTemplate,
125-
begin: outputBeginComment,
126-
end: outputEndComment,
127-
writer: nil,
128-
129-
expected: "",
130-
wantErr: true,
131-
errMsg: "open testdata/writer/file-missing.md: no such file or directory",
132-
},
133181
"EmptyTemplate": {
134182
file: "not-applicable.md",
135183
mode: "inject",
@@ -142,18 +190,6 @@ func TestFileWriter(t *testing.T) {
142190
wantErr: true,
143191
errMsg: "template is missing",
144192
},
145-
"EmptyFile": {
146-
file: "empty-file.md",
147-
mode: "inject",
148-
template: OutputTemplate,
149-
begin: outputBeginComment,
150-
end: outputEndComment,
151-
writer: nil,
152-
153-
expected: "",
154-
wantErr: true,
155-
errMsg: "file content is empty",
156-
},
157193
"BeginCommentMissing": {
158194
file: "begin-comment-missing.md",
159195
mode: "inject",

0 commit comments

Comments
 (0)