Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 61f0188

Browse files
smolamcuadros
authored andcommitted
formats/config: Added encoder/decoder for git config files. (#97)
* WIP: Add config format parser. * add decoder based on gcfg. Portions of code taken from: https://github.com/go-gcfg/gcfg/blob/5b9f94ee80b2331c3982477bd84be8edd857df33/read.go * add git config encoder and config methods. * use format/config in storage/filesystem for read * use format/config in storage/filesystem to write * formats/config: improve docs. * formats/config: improve tests. * formats/config: use our fork of gcfg; improve api. * formats/config: improve api. * storage/filesystem: fix gofmt * formats/config: use NoSubsection constant. * formats/config: add doc.go * formats/config: requested sytle changes. * formats/config: do not use *_test packages.
1 parent 194da90 commit 61f0188

File tree

14 files changed

+1017
-54
lines changed

14 files changed

+1017
-54
lines changed

formats/config/common.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package config
2+
3+
// New creates a new config instance.
4+
func New() *Config {
5+
return &Config{}
6+
}
7+
8+
type Config struct {
9+
Comment *Comment
10+
Sections Sections
11+
Includes Includes
12+
}
13+
14+
type Includes []*Include
15+
16+
// A reference to a included configuration.
17+
type Include struct {
18+
Path string
19+
Config *Config
20+
}
21+
22+
type Comment string
23+
24+
const (
25+
NoSubsection = ""
26+
)
27+
28+
func (c *Config) Section(name string) *Section {
29+
for i := len(c.Sections) - 1; i >= 0; i-- {
30+
s := c.Sections[i]
31+
if s.IsName(name) {
32+
return s
33+
}
34+
}
35+
s := &Section{Name: name}
36+
c.Sections = append(c.Sections, s)
37+
return s
38+
}
39+
40+
// AddOption is a convenience method to add an option to a given
41+
// section and subsection.
42+
//
43+
// Use the NoSubsection constant for the subsection argument
44+
// if no subsection is wanted.
45+
func (s *Config) AddOption(section string, subsection string, key string, value string) *Config {
46+
if subsection == "" {
47+
s.Section(section).AddOption(key, value)
48+
} else {
49+
s.Section(section).Subsection(subsection).AddOption(key, value)
50+
}
51+
52+
return s
53+
}
54+
55+
// SetOption is a convenience method to set an option to a given
56+
// section and subsection.
57+
//
58+
// Use the NoSubsection constant for the subsection argument
59+
// if no subsection is wanted.
60+
func (s *Config) SetOption(section string, subsection string, key string, value string) *Config {
61+
if subsection == "" {
62+
s.Section(section).SetOption(key, value)
63+
} else {
64+
s.Section(section).Subsection(subsection).SetOption(key, value)
65+
}
66+
67+
return s
68+
}
69+
70+
func (c *Config) RemoveSection(name string) *Config {
71+
result := Sections{}
72+
for _, s := range c.Sections {
73+
if !s.IsName(name) {
74+
result = append(result, s)
75+
}
76+
}
77+
78+
c.Sections = result
79+
return c
80+
}
81+
82+
func (c *Config) RemoveSubsection(section string, subsection string) *Config {
83+
for _, s := range c.Sections {
84+
if s.IsName(section) {
85+
result := Subsections{}
86+
for _, ss := range s.Subsections {
87+
if !ss.IsName(subsection) {
88+
result = append(result, ss)
89+
}
90+
}
91+
s.Subsections = result
92+
}
93+
}
94+
95+
return c
96+
}

formats/config/common_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package config
2+
3+
import (
4+
"testing"
5+
6+
. "gopkg.in/check.v1"
7+
)
8+
9+
func Test(t *testing.T) { TestingT(t) }
10+
11+
type CommonSuite struct{}
12+
13+
var _ = Suite(&CommonSuite{})
14+
15+
func (s *CommonSuite) TestConfig_SetOption(c *C) {
16+
obtained := New().SetOption("section", "", "key1", "value1")
17+
expected := &Config{
18+
Sections: []*Section{
19+
{
20+
Name: "section",
21+
Options: []*Option{
22+
{Key: "key1", Value: "value1"},
23+
},
24+
},
25+
},
26+
}
27+
c.Assert(obtained, DeepEquals, expected)
28+
obtained = obtained.SetOption("section", "", "key1", "value1")
29+
c.Assert(obtained, DeepEquals, expected)
30+
31+
obtained = New().SetOption("section", "subsection", "key1", "value1")
32+
expected = &Config{
33+
Sections: []*Section{
34+
{
35+
Name: "section",
36+
Subsections: []*Subsection{
37+
{
38+
Name: "subsection",
39+
Options: []*Option{
40+
{Key: "key1", Value: "value1"},
41+
},
42+
},
43+
},
44+
},
45+
},
46+
}
47+
c.Assert(obtained, DeepEquals, expected)
48+
obtained = obtained.SetOption("section", "subsection", "key1", "value1")
49+
c.Assert(obtained, DeepEquals, expected)
50+
}
51+
52+
func (s *CommonSuite) TestConfig_AddOption(c *C) {
53+
obtained := New().AddOption("section", "", "key1", "value1")
54+
expected := &Config{
55+
Sections: []*Section{
56+
{
57+
Name: "section",
58+
Options: []*Option{
59+
{Key: "key1", Value: "value1"},
60+
},
61+
},
62+
},
63+
}
64+
c.Assert(obtained, DeepEquals, expected)
65+
}
66+
67+
func (s *CommonSuite) TestConfig_RemoveSection(c *C) {
68+
sect := New().
69+
AddOption("section1", "", "key1", "value1").
70+
AddOption("section2", "", "key1", "value1")
71+
expected := New().
72+
AddOption("section1", "", "key1", "value1")
73+
c.Assert(sect.RemoveSection("other"), DeepEquals, sect)
74+
c.Assert(sect.RemoveSection("section2"), DeepEquals, expected)
75+
}
76+
77+
func (s *CommonSuite) TestConfig_RemoveSubsection(c *C) {
78+
sect := New().
79+
AddOption("section1", "sub1", "key1", "value1").
80+
AddOption("section1", "sub2", "key1", "value1")
81+
expected := New().
82+
AddOption("section1", "sub1", "key1", "value1")
83+
c.Assert(sect.RemoveSubsection("section1", "other"), DeepEquals, sect)
84+
c.Assert(sect.RemoveSubsection("other", "other"), DeepEquals, sect)
85+
c.Assert(sect.RemoveSubsection("section1", "sub2"), DeepEquals, expected)
86+
}

formats/config/decoder.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package config
2+
3+
import (
4+
"io"
5+
6+
"github.com/src-d/gcfg"
7+
)
8+
9+
// A Decoder reads and decodes config files from an input stream.
10+
type Decoder struct {
11+
io.Reader
12+
}
13+
14+
// NewDecoder returns a new decoder that reads from r.
15+
func NewDecoder(r io.Reader) *Decoder {
16+
return &Decoder{r}
17+
}
18+
19+
// Decode reads the whole config from its input and stores it in the
20+
// value pointed to by config.
21+
func (d *Decoder) Decode(config *Config) error {
22+
cb := func(s string, ss string, k string, v string, bv bool) error {
23+
if ss == "" && k == "" {
24+
config.Section(s)
25+
return nil
26+
}
27+
28+
if ss != "" && k == "" {
29+
config.Section(s).Subsection(ss)
30+
return nil
31+
}
32+
33+
config.AddOption(s, ss, k, v)
34+
return nil
35+
}
36+
return gcfg.ReadWithCallback(d, cb)
37+
}

formats/config/decoder_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
6+
. "gopkg.in/check.v1"
7+
)
8+
9+
type DecoderSuite struct{}
10+
11+
var _ = Suite(&DecoderSuite{})
12+
13+
func (s *DecoderSuite) TestDecode(c *C) {
14+
for idx, fixture := range fixtures {
15+
r := bytes.NewReader([]byte(fixture.Raw))
16+
d := NewDecoder(r)
17+
cfg := &Config{}
18+
err := d.Decode(cfg)
19+
c.Assert(err, IsNil, Commentf("decoder error for fixture: %d", idx))
20+
c.Assert(cfg, DeepEquals, fixture.Config, Commentf("bad result for fixture: %d", idx))
21+
}
22+
}
23+
24+
func (s *DecoderSuite) TestDecodeFailsWithIdentBeforeSection(c *C) {
25+
t := `
26+
key=value
27+
[section]
28+
key=value
29+
`
30+
decodeFails(c, t)
31+
}
32+
33+
func (s *DecoderSuite) TestDecodeFailsWithEmptySectionName(c *C) {
34+
t := `
35+
[]
36+
key=value
37+
`
38+
decodeFails(c, t)
39+
}
40+
41+
func (s *DecoderSuite) TestDecodeFailsWithEmptySubsectionName(c *C) {
42+
t := `
43+
[remote ""]
44+
key=value
45+
`
46+
decodeFails(c, t)
47+
}
48+
49+
func (s *DecoderSuite) TestDecodeFailsWithBadSubsectionName(c *C) {
50+
t := `
51+
[remote origin"]
52+
key=value
53+
`
54+
decodeFails(c, t)
55+
t = `
56+
[remote "origin]
57+
key=value
58+
`
59+
decodeFails(c, t)
60+
}
61+
62+
func (s *DecoderSuite) TestDecodeFailsWithTrailingGarbage(c *C) {
63+
t := `
64+
[remote]garbage
65+
key=value
66+
`
67+
decodeFails(c, t)
68+
t = `
69+
[remote "origin"]garbage
70+
key=value
71+
`
72+
decodeFails(c, t)
73+
}
74+
75+
func (s *DecoderSuite) TestDecodeFailsWithGarbage(c *C) {
76+
decodeFails(c, "---")
77+
decodeFails(c, "????")
78+
decodeFails(c, "[sect\nkey=value")
79+
decodeFails(c, "sect]\nkey=value")
80+
decodeFails(c, `[section]key="value`)
81+
decodeFails(c, `[section]key=value"`)
82+
}
83+
84+
func decodeFails(c *C, text string) {
85+
r := bytes.NewReader([]byte(text))
86+
d := NewDecoder(r)
87+
cfg := &Config{}
88+
err := d.Decode(cfg)
89+
c.Assert(err, NotNil)
90+
}

0 commit comments

Comments
 (0)