@@ -3,6 +3,7 @@ package configparser
3
3
import (
4
4
"bufio"
5
5
"bytes"
6
+ "errors"
6
7
"fmt"
7
8
"io"
8
9
"os"
@@ -12,18 +13,13 @@ import (
12
13
"unicode"
13
14
)
14
15
15
- const (
16
- defaultSectionName = "DEFAULT"
17
- maxInterpolationDepth int = 10
18
- )
19
-
20
16
var (
21
17
sectionHeader = regexp .MustCompile (`^\[([^]]+)\]$` )
22
- keyValue = regexp .MustCompile (`([^:=\s][^:=]*)\s*(?P<vi>[:=])\s*(.*)$` )
23
- keyWNoValue = regexp .MustCompile (`([^:=\s][^:=]*)\s*((?P<vi>[:=])\s*(.*)$)?` )
24
18
interpolater = regexp .MustCompile (`%\(([^)]*)\)s` )
25
19
)
26
20
21
+ var ErrAlreadyExist = errors .New ("already exist" )
22
+
27
23
var boolMapping = map [string ]bool {
28
24
"1" : true ,
29
25
"true" : true ,
@@ -74,20 +70,20 @@ func New() *ConfigParser {
74
70
return & ConfigParser {
75
71
config : make (Config ),
76
72
defaults : newSection (defaultSectionName ),
77
- opt : & options {} ,
73
+ opt : defaultOptions () ,
78
74
}
79
75
}
80
76
81
77
// 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 ()
84
80
for _ , fn := range opts {
85
81
fn (opt )
86
82
}
87
83
88
84
return & ConfigParser {
89
85
config : make (Config ),
90
- defaults : newSection (defaultSectionName ),
86
+ defaults : newSection (opt . defaultSection ),
91
87
opt : opt ,
92
88
}
93
89
}
@@ -123,7 +119,7 @@ func ParseReader(in io.Reader) (*ConfigParser, error) {
123
119
}
124
120
125
121
// 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 ) {
127
123
p := NewWithOptions (opts ... )
128
124
err := p .ParseReader (in )
129
125
@@ -145,7 +141,7 @@ func Parse(filename string) (*ConfigParser, error) {
145
141
}
146
142
147
143
// 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 ) {
149
145
p := NewWithOptions (opts ... )
150
146
data , err := os .ReadFile (filename )
151
147
if err != nil {
@@ -201,10 +197,22 @@ func (p *ConfigParser) SaveWithDelimiter(filename, delimiter string) error {
201
197
func (p * ConfigParser ) ParseReader (in io.Reader ) error {
202
198
reader := bufio .NewReader (in )
203
199
var lineNo int
204
- var err error
205
200
var curSect * Section
206
201
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 {
208
216
l , _ , err := reader .ReadLine ()
209
217
if err != nil {
210
218
break
@@ -216,30 +224,45 @@ func (p *ConfigParser) ParseReader(in io.Reader) error {
216
224
line := strings .TrimFunc (string (l ), unicode .IsSpace ) // ensures sectionHeader regex will match
217
225
218
226
// Skip comment lines and empty lines
219
- if strings . HasPrefix (line , "#" ) || line == "" {
227
+ if p . opt . commentPrefixes . In (line ) || line == "" {
220
228
continue
221
229
}
222
230
223
231
if match := sectionHeader .FindStringSubmatch (line ); len (match ) > 0 {
224
232
section := match [1 ]
225
- if section == defaultSectionName {
233
+ if section == p . opt . defaultSection {
226
234
curSect = p .defaults
227
235
} else if _ , present := p .config [section ]; ! present {
228
236
curSect = newSection (section )
229
237
p .config [section ] = curSect
238
+ } else if p .opt .strict {
239
+ return fmt .Errorf ("section %q error: %w" , section , ErrAlreadyExist )
230
240
}
231
241
} else if match = keyValue .FindStringSubmatch (line ); len (match ) > 0 {
232
242
if curSect == nil {
233
243
return fmt .Errorf ("missing section header: %d %s" , lineNo , line )
234
244
}
235
245
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 ])
237
253
if err := curSect .Add (key , value ); err != nil {
238
254
return fmt .Errorf ("failed to add %q = %q: %w" , key , value , err )
239
255
}
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 {
241
258
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 ])
243
266
if err := curSect .Add (key , value ); err != nil {
244
267
return fmt .Errorf ("failed to add %q = %q: %w" , key , value , err )
245
268
}
0 commit comments