Skip to content

Commit 5b4759d

Browse files
committed
add list of new options
* interpolation * commentPrefixes * inlineCommentPrefixes * defaultSection * delimeters * converters * strict TODO: investigate `emptyLines` realisation
1 parent d6c4d4e commit 5b4759d

13 files changed

+556
-175
lines changed

chainmap.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package configparser
2+
3+
type chainMap struct {
4+
maps []Dict
5+
}
6+
7+
// NewChainMap default interpolator.
8+
func NewChainMap() *chainMap {
9+
return &chainMap{
10+
maps: make([]Dict, 0),
11+
}
12+
}
13+
14+
func (c *chainMap) Add(dicts ...Dict) {
15+
c.maps = append(c.maps, dicts...)
16+
}
17+
18+
func (c *chainMap) Len() int {
19+
return len(c.maps)
20+
}
21+
22+
func (c *chainMap) Get(key string) string {
23+
var value string
24+
25+
for _, dict := range c.maps {
26+
if result, present := dict[key]; present {
27+
value = result
28+
}
29+
}
30+
return value
31+
}

chainmap/chainmap.go

-31
This file was deleted.

chainmap/chainmap_test.go

-53
This file was deleted.

chainmap_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package configparser_test
2+
3+
import (
4+
"testing"
5+
6+
gc "gopkg.in/check.v1"
7+
8+
"github.com/bigkevmcd/go-configparser"
9+
)
10+
11+
func TestChainMap(t *testing.T) { gc.TestingT(t) }
12+
13+
type ChainMapSuite struct {
14+
c configparser.Interpolator
15+
dict1 configparser.Dict
16+
dict2 configparser.Dict
17+
}
18+
19+
var _ = gc.Suite(&ChainMapSuite{})
20+
21+
func (s *ChainMapSuite) SetUpTest(c *gc.C) {
22+
s.c = configparser.NewChainMap()
23+
s.dict1 = make(configparser.Dict)
24+
s.dict2 = make(configparser.Dict)
25+
s.dict1["testing"] = "2"
26+
s.dict1["value"] = "3"
27+
s.dict2["value"] = "4"
28+
}
29+
30+
func (s *ChainMapSuite) TestLen(c *gc.C) {
31+
s.c.Add(s.dict1, s.dict2)
32+
c.Assert(s.c.Len(), gc.Equals, 2)
33+
}
34+
35+
func (s *ChainMapSuite) TestGet1(c *gc.C) {
36+
s.c.Add(s.dict1, s.dict2)
37+
38+
result := s.c.Get("unknown")
39+
c.Assert(result, gc.Equals, "")
40+
}
41+
42+
func (s *ChainMapSuite) TestGet2(c *gc.C) {
43+
s.c.Add(s.dict1, s.dict2)
44+
45+
result := s.c.Get("value")
46+
c.Assert(result, gc.Equals, "4")
47+
result = s.c.Get("testing")
48+
c.Assert(result, gc.Equals, "2")
49+
}
50+
51+
func (s *ChainMapSuite) TestGet3(c *gc.C) {
52+
s.c.Add(s.dict2, s.dict1)
53+
54+
result := s.c.Get("value")
55+
c.Assert(result, gc.Equals, "3")
56+
}

configparser.go

+43-20
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package configparser
33
import (
44
"bufio"
55
"bytes"
6+
"errors"
67
"fmt"
78
"io"
89
"os"
@@ -12,18 +13,13 @@ import (
1213
"unicode"
1314
)
1415

15-
const (
16-
defaultSectionName = "DEFAULT"
17-
maxInterpolationDepth int = 10
18-
)
19-
2016
var (
2117
sectionHeader = regexp.MustCompile(`^\[([^]]+)\]$`)
22-
keyValue = regexp.MustCompile(`([^:=\s][^:=]*)\s*(?P<vi>[:=])\s*(.*)$`)
23-
keyWNoValue = regexp.MustCompile(`([^:=\s][^:=]*)\s*((?P<vi>[:=])\s*(.*)$)?`)
2418
interpolater = regexp.MustCompile(`%\(([^)]*)\)s`)
2519
)
2620

21+
var ErrAlreadyExist = errors.New("already exist")
22+
2723
var boolMapping = map[string]bool{
2824
"1": true,
2925
"true": true,
@@ -74,20 +70,20 @@ func New() *ConfigParser {
7470
return &ConfigParser{
7571
config: make(Config),
7672
defaults: newSection(defaultSectionName),
77-
opt: &options{},
73+
opt: defaultOptions(),
7874
}
7975
}
8076

8177
// NewWithOptions creates a new ConfigParser with options.
82-
func NewWithOptions(opts ...OptFunc) *ConfigParser {
83-
opt := &options{}
78+
func NewWithOptions(opts ...optFunc) *ConfigParser {
79+
opt := defaultOptions()
8480
for _, fn := range opts {
8581
fn(opt)
8682
}
8783

8884
return &ConfigParser{
8985
config: make(Config),
90-
defaults: newSection(defaultSectionName),
86+
defaults: newSection(opt.defaultSection),
9187
opt: opt,
9288
}
9389
}
@@ -123,7 +119,7 @@ func ParseReader(in io.Reader) (*ConfigParser, error) {
123119
}
124120

125121
// ParseReaderWithOptions parses a ConfigParser from the provided input with given options.
126-
func ParseReaderWithOptions(in io.Reader, opts ...OptFunc) (*ConfigParser, error) {
122+
func ParseReaderWithOptions(in io.Reader, opts ...optFunc) (*ConfigParser, error) {
127123
p := NewWithOptions(opts...)
128124
err := p.ParseReader(in)
129125

@@ -145,7 +141,7 @@ func Parse(filename string) (*ConfigParser, error) {
145141
}
146142

147143
// ParseWithOptions takes a filename and parses it into a ConfigParser value with given options.
148-
func ParseWithOptions(filename string, opts ...OptFunc) (*ConfigParser, error) {
144+
func ParseWithOptions(filename string, opts ...optFunc) (*ConfigParser, error) {
149145
p := NewWithOptions(opts...)
150146
data, err := os.ReadFile(filename)
151147
if err != nil {
@@ -201,10 +197,22 @@ func (p *ConfigParser) SaveWithDelimiter(filename, delimiter string) error {
201197
func (p *ConfigParser) ParseReader(in io.Reader) error {
202198
reader := bufio.NewReader(in)
203199
var lineNo int
204-
var err error
205200
var curSect *Section
206201

207-
for err == nil {
202+
keyValue := regexp.MustCompile(
203+
fmt.Sprintf(
204+
`([^%[1]s\s][^%[1]s]*)\s*(?P<vi>[%[1]s]+)\s*(.*)$`,
205+
p.opt.delimeters,
206+
),
207+
)
208+
keyWNoValue := regexp.MustCompile(
209+
fmt.Sprintf(
210+
`([^%[1]s\s][^%[1]s]*)\s*((?P<vi>[%[1]s]+)\s*(.*)$)?`,
211+
p.opt.delimeters,
212+
),
213+
)
214+
215+
for {
208216
l, _, err := reader.ReadLine()
209217
if err != nil {
210218
break
@@ -216,30 +224,45 @@ func (p *ConfigParser) ParseReader(in io.Reader) error {
216224
line := strings.TrimFunc(string(l), unicode.IsSpace) // ensures sectionHeader regex will match
217225

218226
// Skip comment lines and empty lines
219-
if strings.HasPrefix(line, "#") || line == "" {
227+
if p.opt.commentPrefixes.In(line) || line == "" {
220228
continue
221229
}
222230

223231
if match := sectionHeader.FindStringSubmatch(line); len(match) > 0 {
224232
section := match[1]
225-
if section == defaultSectionName {
233+
if section == p.opt.defaultSection {
226234
curSect = p.defaults
227235
} else if _, present := p.config[section]; !present {
228236
curSect = newSection(section)
229237
p.config[section] = curSect
238+
} else if p.opt.strict {
239+
return fmt.Errorf("section %q error: %w", section, ErrAlreadyExist)
230240
}
231241
} else if match = keyValue.FindStringSubmatch(line); len(match) > 0 {
232242
if curSect == nil {
233243
return fmt.Errorf("missing section header: %d %s", lineNo, line)
234244
}
235245
key := strings.TrimSpace(match[1])
236-
value := match[3]
246+
if p.opt.strict {
247+
if err := p.inOptions(key); err != nil {
248+
return err
249+
}
250+
}
251+
252+
value := p.opt.inlineCommentPrefixes.Split(match[3])
237253
if err := curSect.Add(key, value); err != nil {
238254
return fmt.Errorf("failed to add %q = %q: %w", key, value, err)
239255
}
240-
} else if match = keyWNoValue.FindStringSubmatch(line); len(match) > 0 && p.opt.allowNoValue && curSect != nil {
256+
} else if match = keyWNoValue.FindStringSubmatch(line); len(match) > 0 &&
257+
p.opt.allowNoValue && curSect != nil {
241258
key := strings.TrimSpace(match[1])
242-
value := match[4]
259+
if p.opt.strict {
260+
if err := p.inOptions(key); err != nil {
261+
return err
262+
}
263+
}
264+
265+
value := p.opt.inlineCommentPrefixes.Split(match[4])
243266
if err := curSect.Add(key, value); err != nil {
244267
return fmt.Errorf("failed to add %q = %q: %w", key, value, err)
245268
}

0 commit comments

Comments
 (0)