Skip to content

Commit 34f9758

Browse files
committed
rework ParseReader with moving regexp to options with error instead of a panic
1 parent b0e1a0e commit 34f9758

File tree

3 files changed

+76
-37
lines changed

3 files changed

+76
-37
lines changed

configparser.go

+34-37
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ type ConfigParser struct {
4747
func (d Dict) Keys() []string {
4848
keys := make([]string, 0, len(d))
4949

50+
if len(d) == 0 {
51+
return keys
52+
}
53+
5054
for key := range d {
5155
keys = append(keys, key)
5256
}
@@ -90,9 +94,8 @@ func NewWithOptions(opts ...optFunc) *ConfigParser {
9094
func NewWithDefaults(defaults Dict) (*ConfigParser, error) {
9195
p := New()
9296
for key, value := range defaults {
93-
if err := p.defaults.Add(key, value); err != nil {
94-
return nil, fmt.Errorf("failed to add %q to %q: %w", key, value, err)
95-
}
97+
// Add never returns an error.
98+
_ = p.defaults.Add(key, value)
9699
}
97100
return p, nil
98101
}
@@ -191,34 +194,28 @@ func (p *ConfigParser) SaveWithDelimiter(filename, delimiter string) error {
191194
}
192195

193196
// ParseReader parses data into ConfigParser from provided reader.
194-
func (p *ConfigParser) ParseReader(in io.Reader) error {
195-
reader := bufio.NewReader(in)
196-
var lineNo int
197-
var curSect *Section
198-
var key, value string
199-
200-
keyValue := regexp.MustCompile(
201-
fmt.Sprintf(
202-
`([^%[1]s\s][^%[1]s]*)\s*(?P<vi>[%[1]s]+)\s*(.*)$`,
203-
p.opt.delimiters,
204-
),
205-
)
206-
keyWNoValue := regexp.MustCompile(
207-
fmt.Sprintf(
208-
`([^%[1]s\s][^%[1]s]*)\s*((?P<vi>[%[1]s]+)\s*(.*)$)?`,
209-
p.opt.delimiters,
210-
),
197+
func (p *ConfigParser) ParseReader(in io.Reader) (err error) {
198+
var (
199+
reader = bufio.NewReader(in)
200+
201+
lineNo int
202+
key, value string
203+
curSect *Section
211204
)
212205

206+
keyValue, keyWNoValue, err := p.opt.compileRegex()
207+
if err != nil {
208+
return err
209+
}
210+
213211
for {
214212
l, _, err := reader.ReadLine()
215213
if err != nil {
216214
// If error is end of file, then current key should be checked before return.
217215
if errors.Is(err, io.EOF) {
218216
if key != "" {
219-
if err := curSect.Add(key, value); err != nil {
220-
return fmt.Errorf("failed to add %q = %q: %w", key, value, err)
221-
}
217+
// Add never returns an error.
218+
_ = curSect.Add(key, value)
222219
}
223220

224221
return nil
@@ -255,9 +252,8 @@ func (p *ConfigParser) ParseReader(in io.Reader) error {
255252
// multiline prefixes or it is an empty line which is not allowed within values,
256253
// then it counts as the value parsing is finished and it can be added
257254
// to the current section.
258-
if err := curSect.Add(key, value); err != nil {
259-
return fmt.Errorf("failed to add %q = %q: %w", key, value, err)
260-
}
255+
// Add never returns an error.
256+
_ = curSect.Add(key, value)
261257

262258
// Drop key-value pair to empty strings.
263259
key, value = "", ""
@@ -298,19 +294,20 @@ func (p *ConfigParser) ParseReader(in io.Reader) error {
298294
}
299295

300296
value = p.opt.inlineCommentPrefixes.Split(match[3])
301-
} else if match = keyWNoValue.FindStringSubmatch(line); len(match) > 0 &&
302-
p.opt.allowNoValue {
303-
if curSect == nil {
304-
return fmt.Errorf("missing section header: %d %s", lineNo, line)
305-
}
306-
key = strings.TrimSpace(match[1])
307-
if p.opt.strict {
308-
if err := p.inOptions(key); err != nil {
309-
return err
297+
} else if p.opt.allowNoValue {
298+
if match = keyWNoValue.FindStringSubmatch(line); len(match) > 0 {
299+
if curSect == nil {
300+
return fmt.Errorf("missing section header: %d %s", lineNo, line)
301+
}
302+
key = strings.TrimSpace(match[1])
303+
if p.opt.strict {
304+
if err := p.inOptions(key); err != nil {
305+
return err
306+
}
310307
}
311-
}
312308

313-
value = p.opt.inlineCommentPrefixes.Split(match[4])
309+
value = p.opt.inlineCommentPrefixes.Split(match[4])
310+
}
314311
}
315312
}
316313
}

configparser_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,21 @@ broken_option = this value will miss
150150
})
151151
}
152152

153+
func (s *ConfigParserSuite) TestKeyValueRegexError(c *C) {
154+
p := configparser.NewWithOptions(configparser.Delimiters("=-"))
155+
err := p.ParseReader(strings.NewReader(""))
156+
c.Assert(err, ErrorMatches, ".*error parsing regexp: invalid escape sequence.*")
157+
}
158+
159+
func (s *ConfigParserSuite) TestKeyNoValueRegexError(c *C) {
160+
p := configparser.NewWithOptions(
161+
configparser.Delimiters("\\"),
162+
configparser.AllowNoValue,
163+
)
164+
err := p.ParseReader(strings.NewReader(""))
165+
c.Assert(err, ErrorMatches, ".*error parsing regexp: missing closing ].*")
166+
}
167+
153168
func assertSuccessful(c *C, err error) {
154169
c.Assert(err, IsNil)
155170
}

options.go

+27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package configparser
22

33
import (
4+
"fmt"
5+
"regexp"
46
"strings"
57

68
"github.com/bigkevmcd/go-configparser/chainmap"
@@ -22,6 +24,31 @@ type options struct {
2224
strict bool
2325
}
2426

27+
func (o *options) compileRegex() (
28+
keyValue *regexp.Regexp, keyWNoValue *regexp.Regexp, err error,
29+
) {
30+
if o.allowNoValue {
31+
keyWNoValue, err = regexp.Compile(
32+
fmt.Sprintf(
33+
`([^%[1]s\s][^%[1]s]*)\s*((?P<vi>[%[1]s]+)\s*(.*)$)?`,
34+
o.delimiters,
35+
),
36+
)
37+
if err != nil {
38+
return
39+
}
40+
}
41+
42+
keyValue, err = regexp.Compile(
43+
fmt.Sprintf(
44+
`([^%[1]s\s][^%[1]s]*)\s*(?P<vi>[%[1]s]+)\s*(.*)$`,
45+
o.delimiters,
46+
),
47+
)
48+
49+
return
50+
}
51+
2552
// Converter contains custom convert functions for available types.
2653
type Converter map[int]ConvertFunc
2754

0 commit comments

Comments
 (0)