config: add List type

This commit is contained in:
Fabian Möller 2018-08-19 14:31:39 +02:00 committed by Nick Craig-Wood
parent 29fa840d3a
commit f818df52b8
2 changed files with 181 additions and 0 deletions

94
fs/config/config_list.go Normal file
View file

@ -0,0 +1,94 @@
package config
import (
"bytes"
"encoding/csv"
"fmt"
)
// CommaSepList is a comma separated config value
// It uses the encoding/csv rules for quoting and escaping
type CommaSepList []string
// SpaceSepList is a space separated config value
// It uses the encoding/csv rules for quoting and escaping
type SpaceSepList []string
type genericList []string
func (l CommaSepList) String() string {
return genericList(l).string(',')
}
// Set the List entries
func (l *CommaSepList) Set(s string) error {
return (*genericList)(l).set(',', []byte(s))
}
// Type of the value
func (CommaSepList) Type() string {
return "[]string"
}
// Scan implements the fmt.Scanner interface
func (l *CommaSepList) Scan(s fmt.ScanState, ch rune) error {
return (*genericList)(l).scan(',', s, ch)
}
func (l SpaceSepList) String() string {
return genericList(l).string(' ')
}
// Set the List entries
func (l *SpaceSepList) Set(s string) error {
return (*genericList)(l).set(' ', []byte(s))
}
// Type of the value
func (SpaceSepList) Type() string {
return "[]string"
}
// Scan implements the fmt.Scanner interface
func (l *SpaceSepList) Scan(s fmt.ScanState, ch rune) error {
return (*genericList)(l).scan(' ', s, ch)
}
func (gl genericList) string(sep rune) string {
var buf bytes.Buffer
w := csv.NewWriter(&buf)
w.Comma = sep
err := w.Write(gl)
if err != nil {
// can only happen if w.Comma is invalid
panic(err)
}
w.Flush()
return string(bytes.TrimSpace(buf.Bytes()))
}
func (gl *genericList) set(sep rune, b []byte) error {
if len(b) == 0 {
*gl = nil
return nil
}
r := csv.NewReader(bytes.NewReader(b))
r.Comma = sep
record, err := r.Read()
switch _err := err.(type) {
case nil:
*gl = record
case *csv.ParseError:
err = _err.Err // remove line numbers from the error message
}
return err
}
func (gl *genericList) scan(sep rune, s fmt.ScanState, ch rune) error {
token, err := s.Token(true, func(rune) bool { return true })
if err != nil {
return err
}
return gl.set(sep, bytes.TrimSpace(token))
}

View file

@ -0,0 +1,87 @@
package config
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func must(err error) {
if err != nil {
panic(err)
}
}
func ExampleSpaceSepList() {
for _, s := range []string{
`remotea:test/dir remoteb:`,
`"remotea:test/space dir" remoteb:`,
`"remotea:test/quote""dir" remoteb:`,
} {
var l SpaceSepList
must(l.Set(s))
fmt.Printf("%#v\n", l)
}
// Output:
// config.SpaceSepList{"remotea:test/dir", "remoteb:"}
// config.SpaceSepList{"remotea:test/space dir", "remoteb:"}
// config.SpaceSepList{"remotea:test/quote\"dir", "remoteb:"}
}
func ExampleCommaSepList() {
for _, s := range []string{
`remotea:test/dir,remoteb:`,
`"remotea:test/space dir",remoteb:`,
`"remotea:test/quote""dir",remoteb:`,
} {
var l CommaSepList
must(l.Set(s))
fmt.Printf("%#v\n", l)
}
// Output:
// config.CommaSepList{"remotea:test/dir", "remoteb:"}
// config.CommaSepList{"remotea:test/space dir", "remoteb:"}
// config.CommaSepList{"remotea:test/quote\"dir", "remoteb:"}
}
func TestSpaceSepListSet(t *testing.T) {
type tc struct {
in string
out SpaceSepList
err string
}
tests := []tc{
{``, nil, ""},
{`\`, SpaceSepList{`\`}, ""},
{`\\`, SpaceSepList{`\\`}, ""},
{`potato`, SpaceSepList{`potato`}, ""},
{`po\tato`, SpaceSepList{`po\tato`}, ""},
{`potato\`, SpaceSepList{`potato\`}, ""},
{`'potato`, SpaceSepList{`'potato`}, ""},
{`pot'ato`, SpaceSepList{`pot'ato`}, ""},
{`potato'`, SpaceSepList{`potato'`}, ""},
{`"potato"`, SpaceSepList{`potato`}, ""},
{`'potato'`, SpaceSepList{`'potato'`}, ""},
{`potato apple`, SpaceSepList{`potato`, `apple`}, ""},
{`potato\ apple`, SpaceSepList{`potato\`, `apple`}, ""},
{`"potato apple"`, SpaceSepList{`potato apple`}, ""},
{`"potato'apple"`, SpaceSepList{`potato'apple`}, ""},
{`"potato''apple"`, SpaceSepList{`potato''apple`}, ""},
{`"potato' 'apple"`, SpaceSepList{`potato' 'apple`}, ""},
{`potato="apple"`, nil, `bare " in non-quoted-field`},
{`apple "potato`, nil, "extraneous"},
{`apple pot"ato`, nil, "bare \" in non-quoted-field"},
{`potato"`, nil, "bare \" in non-quoted-field"},
}
for _, tc := range tests {
var l SpaceSepList
err := l.Set(tc.in)
if tc.err == "" {
require.NoErrorf(t, err, "input: %q", tc.in)
} else {
require.Containsf(t, err.Error(), tc.err, "input: %q", tc.in)
}
require.Equalf(t, tc.out, l, "input: %q", tc.in)
}
}