Skip to content
Closed
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
60 changes: 48 additions & 12 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strconv"
"strings"
"unicode"
"unicode/utf8"
)

const minReaderBufferSize = 4096
Expand Down Expand Up @@ -233,6 +234,52 @@ func hasSurroundedQuote(in string, quote byte) bool {
strings.IndexByte(in[1:], quote) == len(in)-2
}

func isAlphaNum(s string, i int) bool {
if i <= 0 {
return false
}
r, _ := utf8.DecodeLastRuneInString(s[:i])
return unicode.IsLetter(r) || unicode.IsDigit(r)
}

// findInlineComment finds the index of the inline comment delimiter in line.
// It skips comment delimiters that occur inside matched quotes (' or ").
func findInlineComment(line string, spaceBefore bool) int {
for i := 0; i < len(line); i++ {
if (line[i] == '\'' || line[i] == '"') && !isAlphaNum(line, i) {
q := line[i]
matched := false
for j := i + 1; j < len(line); j++ {
if line[j] == q {
backslashes := 0
for k := j - 1; k >= i+1 && line[k] == '\\'; k-- {
backslashes++
}
if backslashes%2 == 0 {
i = j
matched = true
break
}
}
}
if matched {
continue
}
}

if spaceBefore {
if strings.HasPrefix(line[i:], " #") || strings.HasPrefix(line[i:], " ;") {
return i
}
} else {
if line[i] == '#' || line[i] == ';' {
return i
}
}
}
return -1
}

func (p *parser) readValue(in []byte, bufferSize int) (string, error) {

line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
Expand Down Expand Up @@ -278,22 +325,11 @@ func (p *parser) readValue(in []byte, bufferSize int) (string, error) {

// Check if ignore inline comment
if !p.options.IgnoreInlineComment {
var i int
if p.options.SpaceBeforeInlineComment {
i = strings.Index(line, " #")
if i == -1 {
i = strings.Index(line, " ;")
}

} else {
i = strings.IndexAny(line, "#;")
}

i := findInlineComment(line, p.options.SpaceBeforeInlineComment)
if i > -1 {
p.comment.WriteString(line[i:])
line = strings.TrimSpace(line[:i])
}

}

// Trim single and double quotes
Expand Down
101 changes: 101 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,104 @@ func TestBadLoad(t *testing.T) {
})
})
}

func TestCommentInsideQuotes(t *testing.T) {
t.Run("preserve comment characters inside single quoted values", func(t *testing.T) {
f, err := Load([]byte(`
[background]
primary-color = '#e18477'
secondary-color = '#000000'
`))
require.NoError(t, err)
require.NotNil(t, f)

assert.Equal(t, "#e18477", f.Section("background").Key("primary-color").String())
assert.Equal(t, "#000000", f.Section("background").Key("secondary-color").String())
})

t.Run("preserve comment characters inside double quoted values", func(t *testing.T) {
f, err := Load([]byte(`
[background]
primary-color = "#e18477"
secondary-color = "#000000"
`))
require.NoError(t, err)
require.NotNil(t, f)

assert.Equal(t, "#e18477", f.Section("background").Key("primary-color").String())
assert.Equal(t, "#000000", f.Section("background").Key("secondary-color").String())
})

t.Run("quoted values with trailing inline comments", func(t *testing.T) {
f, err := Load([]byte(`
[background]
primary-color = '#e18477' # inline comment
secondary-color = "#000000" ; another comment
`))
require.NoError(t, err)
require.NotNil(t, f)

assert.Equal(t, "#e18477", f.Section("background").Key("primary-color").String())
assert.Equal(t, "# inline comment", f.Section("background").Key("primary-color").Comment)
assert.Equal(t, "#000000", f.Section("background").Key("secondary-color").String())
assert.Equal(t, "; another comment", f.Section("background").Key("secondary-color").Comment)
})

t.Run("preserve surrounded quotes with comment characters inside", func(t *testing.T) {
f, err := LoadSources(LoadOptions{
PreserveSurroundedQuote: true,
}, []byte(`
[background]
primary-color = '#e18477'
secondary-color = "#000000"
`))
require.NoError(t, err)
require.NotNil(t, f)

assert.Equal(t, "'#e18477'", f.Section("background").Key("primary-color").String())
assert.Equal(t, "\"#000000\"", f.Section("background").Key("secondary-color").String())
})

t.Run("space before inline comment with comment characters inside quotes", func(t *testing.T) {
f, err := LoadSources(LoadOptions{
SpaceBeforeInlineComment: true,
}, []byte(`
[background]
primary-color = '#e18477' # comment
with-space = 'color # 1' # comment 2
`))
require.NoError(t, err)
require.NotNil(t, f)

assert.Equal(t, "#e18477", f.Section("background").Key("primary-color").String())
assert.Equal(t, "# comment", f.Section("background").Key("primary-color").Comment)
assert.Equal(t, "color # 1", f.Section("background").Key("with-space").String())
assert.Equal(t, "# comment 2", f.Section("background").Key("with-space").Comment)
})

t.Run("url containing hash symbol inside quotes", func(t *testing.T) {
f, err := Load([]byte(`
[links]
docs = 'https://example.com/page#overview'
issue = "https://github.com/go-ini/ini/issues#393" ; see issue
`))
require.NoError(t, err)
require.NotNil(t, f)

assert.Equal(t, "https://example.com/page#overview", f.Section("links").Key("docs").String())
assert.Equal(t, "https://github.com/go-ini/ini/issues#393", f.Section("links").Key("issue").String())
assert.Equal(t, "; see issue", f.Section("links").Key("issue").Comment)
})

t.Run("unquoted contraction with inline comment", func(t *testing.T) {
f, err := Load([]byte(`
[general]
action = don't # comment
`))
require.NoError(t, err)
require.NotNil(t, f)

assert.Equal(t, "don't", f.Section("general").Key("action").String())
assert.Equal(t, "# comment", f.Section("general").Key("action").Comment)
})
}