Skip to content

Commit 0ff251a

Browse files
committedJan 26, 2023
update with suggested Options:
* add Options to control Parser behavior * add NewWithOptions * add ParseReaderWithOptions * add ParseWithOptions moved main parser logic into configparser.ParseReader
1 parent 2eb406a commit 0ff251a

File tree

4 files changed

+107
-58
lines changed

4 files changed

+107
-58
lines changed
 

‎configparser.go

+90-52
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package configparser
22

33
import (
44
"bufio"
5+
"bytes"
56
"fmt"
67
"io"
78
"os"
@@ -23,9 +24,6 @@ var (
2324
interpolater = regexp.MustCompile(`%\(([^)]*)\)s`)
2425
)
2526

26-
// AllowNoValue allows parser to save options without values as empty strings.
27-
var AllowNoValue bool
28-
2927
var boolMapping = map[string]bool{
3028
"1": true,
3129
"true": true,
@@ -48,6 +46,12 @@ type Config map[string]*Section
4846
type ConfigParser struct {
4947
config Config
5048
defaults *Section
49+
opt *Options
50+
}
51+
52+
// Options allows to control parser behavior.
53+
type Options struct {
54+
AllowNoValue bool
5155
}
5256

5357
// Keys returns a sorted slice of keys
@@ -75,22 +79,29 @@ func New() *ConfigParser {
7579
return &ConfigParser{
7680
config: make(Config),
7781
defaults: newSection(defaultSectionName),
82+
opt: &Options{},
7883
}
7984
}
8085

81-
// NewWithDefaults allows creation of a new ConfigParser with a pre-existing
82-
// Dict.
83-
func NewWithDefaults(defaults Dict) (*ConfigParser, error) {
84-
p := ConfigParser{
86+
// NewWithOptions creates a new ConfigParser with options.
87+
func NewWithOptions(opt *Options) *ConfigParser {
88+
return &ConfigParser{
8589
config: make(Config),
8690
defaults: newSection(defaultSectionName),
91+
opt: opt,
8792
}
93+
}
94+
95+
// NewWithDefaults allows creation of a new ConfigParser with a pre-existing
96+
// Dict.
97+
func NewWithDefaults(defaults Dict) (*ConfigParser, error) {
98+
p := New()
8899
for key, value := range defaults {
89100
if err := p.defaults.Add(key, value); err != nil {
90101
return nil, fmt.Errorf("failed to add %q to %q: %w", key, value, err)
91102
}
92103
}
93-
return &p, nil
104+
return p, nil
94105
}
95106

96107
// NewConfigParserFromFile creates a new ConfigParser struct populated from the
@@ -106,53 +117,17 @@ func NewConfigParserFromFile(filename string) (*ConfigParser, error) {
106117
// ParseReader parses a ConfigParser from the provided input.
107118
func ParseReader(in io.Reader) (*ConfigParser, error) {
108119
p := New()
109-
reader := bufio.NewReader(in)
110-
var lineNo int
111-
var err error
112-
var curSect *Section
120+
err := p.ParseReader(in)
113121

114-
for err == nil {
115-
l, _, err := reader.ReadLine()
116-
if err != nil {
117-
break
118-
}
119-
lineNo++
120-
if len(l) == 0 {
121-
continue
122-
}
123-
line := strings.TrimFunc(string(l), unicode.IsSpace) // ensures sectionHeader regex will match
122+
return p, err
123+
}
124124

125-
// Skip comment lines and empty lines
126-
if strings.HasPrefix(line, "#") || line == "" {
127-
continue
128-
}
125+
// ParseReaderWithOptions parses a ConfigParser from the provided input with given options.
126+
func ParseReaderWithOptions(in io.Reader, opt *Options) (*ConfigParser, error) {
127+
p := NewWithOptions(opt)
128+
err := p.ParseReader(in)
129129

130-
if match := sectionHeader.FindStringSubmatch(line); len(match) > 0 {
131-
section := match[1]
132-
if section == defaultSectionName {
133-
curSect = p.defaults
134-
} else if _, present := p.config[section]; !present {
135-
curSect = newSection(section)
136-
p.config[section] = curSect
137-
}
138-
} else if match = keyValue.FindStringSubmatch(line); len(match) > 0 {
139-
if curSect == nil {
140-
return nil, fmt.Errorf("missing section header: %d %s", lineNo, line)
141-
}
142-
key := strings.TrimSpace(match[1])
143-
value := match[3]
144-
if err := curSect.Add(key, value); err != nil {
145-
return nil, fmt.Errorf("failed to add %q = %q: %w", key, value, err)
146-
}
147-
} else if match = keyWNoValue.FindStringSubmatch(line); len(match) > 0 && AllowNoValue && curSect != nil {
148-
key := strings.TrimSpace(match[1])
149-
value := match[4]
150-
if err := curSect.Add(key, value); err != nil {
151-
return nil, fmt.Errorf("failed to add %q = %q: %w", key, value, err)
152-
}
153-
}
154-
}
155-
return p, nil
130+
return p, err
156131
}
157132

158133
// Parse takes a filename and parses it into a ConfigParser value.
@@ -169,6 +144,17 @@ func Parse(filename string) (*ConfigParser, error) {
169144
return p, nil
170145
}
171146

147+
// ParseWithOptions takes a filename and parses it into a ConfigParser value with given options.
148+
func ParseWithOptions(filename string, opt *Options) (*ConfigParser, error) {
149+
p := NewWithOptions(opt)
150+
data, err := os.ReadFile(filename)
151+
if err != nil {
152+
return nil, err
153+
}
154+
err = p.ParseReader(bytes.NewReader(data))
155+
return p, err
156+
}
157+
172158
func writeSection(file *os.File, delimiter string, section *Section) error {
173159
_, err := file.WriteString(fmt.Sprintf("[%s]\n", section.Name))
174160
if err != nil {
@@ -210,3 +196,55 @@ func (p *ConfigParser) SaveWithDelimiter(filename, delimiter string) error {
210196

211197
return nil
212198
}
199+
200+
// ParseReader parses data into ConfigParser from provided reader.
201+
func (p *ConfigParser) ParseReader(in io.Reader) error {
202+
reader := bufio.NewReader(in)
203+
var lineNo int
204+
var err error
205+
var curSect *Section
206+
207+
for err == nil {
208+
l, _, err := reader.ReadLine()
209+
if err != nil {
210+
break
211+
}
212+
lineNo++
213+
if len(l) == 0 {
214+
continue
215+
}
216+
line := strings.TrimFunc(string(l), unicode.IsSpace) // ensures sectionHeader regex will match
217+
218+
// Skip comment lines and empty lines
219+
if strings.HasPrefix(line, "#") || line == "" {
220+
continue
221+
}
222+
223+
if match := sectionHeader.FindStringSubmatch(line); len(match) > 0 {
224+
section := match[1]
225+
if section == defaultSectionName {
226+
curSect = p.defaults
227+
} else if _, present := p.config[section]; !present {
228+
curSect = newSection(section)
229+
p.config[section] = curSect
230+
}
231+
} else if match = keyValue.FindStringSubmatch(line); len(match) > 0 {
232+
if curSect == nil {
233+
return fmt.Errorf("missing section header: %d %s", lineNo, line)
234+
}
235+
key := strings.TrimSpace(match[1])
236+
value := match[3]
237+
if err := curSect.Add(key, value); err != nil {
238+
return fmt.Errorf("failed to add %q = %q: %w", key, value, err)
239+
}
240+
} else if match = keyWNoValue.FindStringSubmatch(line); len(match) > 0 && p.opt.AllowNoValue && curSect != nil {
241+
key := strings.TrimSpace(match[1])
242+
value := match[4]
243+
if err := curSect.Add(key, value); err != nil {
244+
return fmt.Errorf("failed to add %q = %q: %w", key, value, err)
245+
}
246+
}
247+
}
248+
249+
return nil
250+
}

‎configparser_test.go

+13-5
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,9 @@ func (s *ConfigParserSuite) TestParseFromReader(c *gc.C) {
123123
}
124124

125125
// If AllowNoValue is set to true, parser should recognize options without values.
126-
func (s *ConfigParserSuite) TestParseFromReaderWNoValue(c *gc.C) {
127-
configparser.AllowNoValue = true
128-
defer func() { configparser.AllowNoValue = false }()
129-
130-
parsed, err := configparser.ParseReader(strings.NewReader("[empty]\noption\n\n"))
126+
func (s *ConfigParserSuite) TestParseReaderWithOptionsWNoValue(c *gc.C) {
127+
opt := &configparser.Options{AllowNoValue: true}
128+
parsed, err := configparser.ParseReaderWithOptions(strings.NewReader("[empty]\noption\n\n"), opt)
131129
c.Assert(err, gc.IsNil)
132130

133131
ok, err := parsed.HasOption("empty", "option")
@@ -138,3 +136,13 @@ func (s *ConfigParserSuite) TestParseFromReaderWNoValue(c *gc.C) {
138136
func assertSuccessful(c *gc.C, err error) {
139137
c.Assert(err, gc.IsNil)
140138
}
139+
140+
func (s *ConfigParserSuite) TestParseWithOptions(c *gc.C) {
141+
opt := &configparser.Options{AllowNoValue: true}
142+
parsed, err := configparser.ParseWithOptions("testdata/example.cfg", opt)
143+
c.Assert(err, gc.IsNil)
144+
145+
ok, err := parsed.HasOption("empty", "foo")
146+
c.Assert(err, gc.IsNil)
147+
c.Assert(ok, Equals, true)
148+
}

‎methods_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func (s *ConfigParserSuite) TestDefaultsWithNoDefaults(c *gc.C) {
2222
// Sections() should return a list of section names excluding [DEFAULT]
2323
func (s *ConfigParserSuite) TestSections(c *gc.C) {
2424
result := s.p.Sections()
25-
c.Assert(result, gc.DeepEquals, []string{"follower", "whitespace"})
25+
c.Assert(result, gc.DeepEquals, []string{"empty", "follower", "whitespace"})
2626
}
2727

2828
// AddSection(section) should create a new section in the configuration

‎testdata/example.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ TableName: MyCaseSensitiveTableName
1717

1818
[whitespace]
1919
foo = bar
20+
21+
[empty]
22+
foo

0 commit comments

Comments
 (0)
Please sign in to comment.