Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion file.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,32 @@ func (f *File) Append(source interface{}, others ...interface{}) error {
return f.Reload()
}

func writeCommentLines(buf *bytes.Buffer, comment, indent string) error {
if len(comment) == 0 {
return nil
}
lines := strings.Split(comment, LineBreak)
for i := range lines {
if len(lines[i]) == 0 {
continue
}
if len(indent) > 0 {
if _, err := buf.WriteString(indent); err != nil {
return err
}
}
if lines[i][0] != '#' && lines[i][0] != ';' {
lines[i] = "; " + strings.TrimSpace(lines[i])
} else {
lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
}
if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
return err
}
}
return nil
}

func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight

Expand Down Expand Up @@ -366,7 +392,7 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
}
} else {
// Write nothing if default section is empty
if len(sec.keyList) == 0 {
if len(sec.keyList) == 0 && len(sec.TrailingComment) == 0 {
continue
}
}
Expand All @@ -376,6 +402,9 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
if _, err := buf.WriteString(sec.rawBody); err != nil {
return nil, err
}
if err := writeCommentLines(buf, sec.TrailingComment, ""); err != nil {
return nil, err
}

if PrettySection && !isLastSection {
// Put a line between sections
Expand Down Expand Up @@ -495,6 +524,10 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
}
}

if err := writeCommentLines(buf, sec.TrailingComment, ""); err != nil {
return nil, err
}

if PrettySection && !isLastSection {
// Put a line between sections
if _, err := buf.WriteString(LineBreak); err != nil {
Expand Down
28 changes: 28 additions & 0 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"os"
"runtime"
"sort"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -474,6 +475,33 @@ bar2 = " val ue2 "
bar3 = " val ue3 "
`, buf.String())
})

t.Run("keep trailing comments after last section on rewrite", func(t *testing.T) {
// Ansible blockinfile markers: # BEGIN before the section, # END after its keys.
src := `# BEGIN
[profile test]
sso_region = us-east-1
output = json
# END
`
f, err := Load([]byte(src))
require.NoError(t, err)
assert.Equal(t, "# BEGIN", f.Section("profile test").Comment)
assert.Equal(t, "# END", f.Section("profile test").TrailingComment)

_, err = f.NewSection("profile extra")
require.NoError(t, err)
f.Section("profile extra").Key("region").SetValue("us-west-2")

var buf bytes.Buffer
_, err = f.WriteTo(&buf)
require.NoError(t, err)
out := buf.String()
endIdx := strings.Index(out, "# END")
newIdx := strings.Index(out, "[profile extra]")
assert.True(t, endIdx >= 0 && newIdx > endIdx, "trailing # END should stay with the original section, got:\n%s", out)
assert.Contains(t, out, "[profile extra]")
})
}

func TestFile_SaveTo(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,5 +524,11 @@ func (f *File) parse(reader io.Reader) (err error) {
p.comment.Reset()
lastRegularKey = key
}
// Comments after the last key/section are otherwise dropped on EOF
// (e.g. Ansible blockinfile # END markers). Keep them on the last section.
if p.comment.Len() > 0 {
section.TrailingComment = strings.TrimSpace(p.comment.String())
p.comment.Reset()
}
return nil
}
13 changes: 9 additions & 4 deletions section.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ import (
type Section struct {
f *File
Comment string
name string
keys map[string]*Key
keyList []string
keysHash map[string]string
// TrailingComment is comments after the last key of this section
// (or after a raw section body) through EOF. Comments that sit
// immediately before the next [section] still attach to that next
// section's Comment, matching existing parse behavior.
TrailingComment string
name string
keys map[string]*Key
keyList []string
keysHash map[string]string

isRawSection bool
rawBody string
Expand Down