Moves files

This commit is contained in:
Alexander Neumann
2017-07-23 14:19:13 +02:00
parent d1bd160b0a
commit 83d1a46526
284 changed files with 0 additions and 0 deletions

217
internal/options/options.go Normal file
View File

@@ -0,0 +1,217 @@
package options
import (
"reflect"
"restic/errors"
"sort"
"strconv"
"strings"
"time"
)
// Options holds options in the form key=value.
type Options map[string]string
var opts []Help
// Register allows registering options so that they can be listed with List.
func Register(ns string, cfg interface{}) {
opts = appendAllOptions(opts, ns, cfg)
}
// List returns a list of all registered options (using Register()).
func List() (list []Help) {
list = make([]Help, 0, len(opts))
for _, opt := range opts {
list = append(list, opt)
}
return list
}
// appendAllOptions appends all options in cfg to opts, sorted by namespace.
func appendAllOptions(opts []Help, ns string, cfg interface{}) []Help {
for _, opt := range listOptions(cfg) {
opt.Namespace = ns
opts = append(opts, opt)
}
sort.Sort(helpList(opts))
return opts
}
// listOptions returns a list of options of cfg.
func listOptions(cfg interface{}) (opts []Help) {
// resolve indirection if cfg is a pointer
v := reflect.Indirect(reflect.ValueOf(cfg))
for i := 0; i < v.NumField(); i++ {
f := v.Type().Field(i)
h := Help{
Name: f.Tag.Get("option"),
Text: f.Tag.Get("help"),
}
if h.Name == "" {
continue
}
opts = append(opts, h)
}
return opts
}
// Help contains information about an option.
type Help struct {
Namespace string
Name string
Text string
}
type helpList []Help
// Len is the number of elements in the collection.
func (h helpList) Len() int {
return len(h)
}
// Less reports whether the element with
// index i should sort before the element with index j.
func (h helpList) Less(i, j int) bool {
if h[i].Namespace == h[j].Namespace {
return h[i].Name < h[j].Name
}
return h[i].Namespace < h[j].Namespace
}
// Swap swaps the elements with indexes i and j.
func (h helpList) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
// splitKeyValue splits at the first equals (=) sign.
func splitKeyValue(s string) (key string, value string) {
data := strings.SplitN(s, "=", 2)
key = strings.ToLower(strings.TrimSpace(data[0]))
if len(data) == 1 {
// no equals sign is treated as the empty value
return key, ""
}
return key, strings.TrimSpace(data[1])
}
// Parse takes a slice of key=value pairs and returns an Options type.
// The key may include namespaces, separated by dots. Example: "foo.bar=value".
// Keys are converted to lower-case.
func Parse(in []string) (Options, error) {
opts := make(Options, len(in))
for _, opt := range in {
key, value := splitKeyValue(opt)
if key == "" {
return Options{}, errors.Fatalf("empty key is not a valid option")
}
if v, ok := opts[key]; ok && v != value {
return Options{}, errors.Fatalf("key %q present more than once", key)
}
opts[key] = value
}
return opts, nil
}
// Extract returns an Options type with all keys in namespace ns, which is
// also stripped from the keys. ns must end with a dot.
func (o Options) Extract(ns string) Options {
l := len(ns)
if ns[l-1] != '.' {
ns += "."
l++
}
opts := make(Options)
for k, v := range o {
if !strings.HasPrefix(k, ns) {
continue
}
opts[k[l:]] = v
}
return opts
}
// Apply sets the options on dst via reflection, using the struct tag `option`.
// The namespace argument (ns) is only used for error messages.
func (o Options) Apply(ns string, dst interface{}) error {
v := reflect.ValueOf(dst).Elem()
fields := make(map[string]reflect.StructField)
for i := 0; i < v.NumField(); i++ {
f := v.Type().Field(i)
tag := f.Tag.Get("option")
if tag == "" {
continue
}
if _, ok := fields[tag]; ok {
panic("option tag " + tag + " is not unique in " + v.Type().Name())
}
fields[tag] = f
}
for key, value := range o {
field, ok := fields[key]
if !ok {
if ns != "" {
key = ns + "." + key
}
return errors.Fatalf("option %v is not known", key)
}
i := field.Index[0]
switch v.Type().Field(i).Type.Name() {
case "string":
v.Field(i).SetString(value)
case "int":
vi, err := strconv.ParseInt(value, 0, 32)
if err != nil {
return err
}
v.Field(i).SetInt(vi)
case "uint":
vi, err := strconv.ParseUint(value, 0, 32)
if err != nil {
return err
}
v.Field(i).SetUint(vi)
case "Duration":
d, err := time.ParseDuration(value)
if err != nil {
return err
}
v.Field(i).SetInt(int64(d))
default:
panic("type " + v.Type().Field(i).Type.Name() + " not handled")
}
}
return nil
}

View File

@@ -0,0 +1,312 @@
package options
import (
"fmt"
"reflect"
"testing"
"time"
)
var optsTests = []struct {
input []string
output Options
}{
{
[]string{"foo=bar", "bar=baz ", "k="},
Options{
"foo": "bar",
"bar": "baz",
"k": "",
},
},
{
[]string{"Foo=23", "baR", "k=thing with spaces"},
Options{
"foo": "23",
"bar": "",
"k": "thing with spaces",
},
},
{
[]string{"k=thing with spaces", "k2=more spaces = not evil"},
Options{
"k": "thing with spaces",
"k2": "more spaces = not evil",
},
},
{
[]string{"x=1", "foo=bar", "y=2", "foo=bar"},
Options{
"x": "1",
"y": "2",
"foo": "bar",
},
},
}
func TestParseOptions(t *testing.T) {
for i, test := range optsTests {
t.Run(fmt.Sprintf("test-%v", i), func(t *testing.T) {
opts, err := Parse(test.input)
if err != nil {
t.Fatalf("unable to parse options: %v", err)
}
if !reflect.DeepEqual(opts, test.output) {
t.Fatalf("wrong result, want:\n %#v\ngot:\n %#v", test.output, opts)
}
})
}
}
var invalidOptsTests = []struct {
input []string
err string
}{
{
[]string{"=bar", "bar=baz", "k="},
"empty key is not a valid option",
},
{
[]string{"x=1", "foo=bar", "y=2", "foo=baz"},
`key "foo" present more than once`,
},
}
func TestParseInvalidOptions(t *testing.T) {
for _, test := range invalidOptsTests {
t.Run(test.err, func(t *testing.T) {
_, err := Parse(test.input)
if err == nil {
t.Fatalf("expected error (%v) not found, err is nil", test.err)
}
if err.Error() != test.err {
t.Fatalf("expected error %q, got %q", test.err, err.Error())
}
})
}
}
var extractTests = []struct {
input Options
ns string
output Options
}{
{
input: Options{
"foo.bar:": "baz",
"s3.timeout": "10s",
"sftp.timeout": "5s",
"global": "foobar",
},
ns: "s3",
output: Options{
"timeout": "10s",
},
},
}
func TestOptionsExtract(t *testing.T) {
for _, test := range extractTests {
t.Run(test.ns, func(t *testing.T) {
opts := test.input.Extract(test.ns)
if !reflect.DeepEqual(opts, test.output) {
t.Fatalf("wrong result, want:\n %#v\ngot:\n %#v", test.output, opts)
}
})
}
}
// Target is used for Apply() tests
type Target struct {
Name string `option:"name"`
ID int `option:"id"`
Timeout time.Duration `option:"timeout"`
Other string
}
var setTests = []struct {
input Options
output Target
}{
{
Options{
"name": "foobar",
},
Target{
Name: "foobar",
},
},
{
Options{
"name": "foobar",
"id": "1234",
},
Target{
Name: "foobar",
ID: 1234,
},
},
{
Options{
"timeout": "10m3s",
},
Target{
Timeout: time.Duration(10*time.Minute + 3*time.Second),
},
},
}
func TestOptionsApply(t *testing.T) {
for i, test := range setTests {
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
var dst Target
err := test.input.Apply("", &dst)
if err != nil {
t.Fatal(err)
}
if dst != test.output {
t.Fatalf("wrong result, want:\n %#v\ngot:\n %#v", test.output, dst)
}
})
}
}
var invalidSetTests = []struct {
input Options
namespace string
err string
}{
{
Options{
"first_name": "foobar",
},
"ns",
"option ns.first_name is not known",
},
{
Options{
"id": "foobar",
},
"ns",
`strconv.ParseInt: parsing "foobar": invalid syntax`,
},
{
Options{
"timeout": "2134",
},
"ns",
`time: missing unit in duration 2134`,
},
}
func TestOptionsApplyInvalid(t *testing.T) {
for i, test := range invalidSetTests {
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
var dst Target
err := test.input.Apply(test.namespace, &dst)
if err == nil {
t.Fatalf("expected error %v not found", test.err)
}
if err.Error() != test.err {
t.Fatalf("expected error %q, got %q", test.err, err.Error())
}
})
}
}
func TestListOptions(t *testing.T) {
var teststruct = struct {
Foo string `option:"foo" help:"bar text help"`
}{}
var tests = []struct {
cfg interface{}
opts []Help
}{
{
struct {
Foo string `option:"foo" help:"bar text help"`
}{},
[]Help{
{Name: "foo", Text: "bar text help"},
},
},
{
struct {
Foo string `option:"foo" help:"bar text help"`
Bar string `option:"bar" help:"bar text help"`
}{},
[]Help{
{Name: "foo", Text: "bar text help"},
{Name: "bar", Text: "bar text help"},
},
},
{
struct {
Bar string `option:"bar" help:"bar text help"`
Foo string `option:"foo" help:"bar text help"`
}{},
[]Help{
{Name: "bar", Text: "bar text help"},
{Name: "foo", Text: "bar text help"},
},
},
{
&teststruct,
[]Help{
{Name: "foo", Text: "bar text help"},
},
},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
opts := listOptions(test.cfg)
if !reflect.DeepEqual(opts, test.opts) {
t.Fatalf("wrong opts, want:\n %v\ngot:\n %v", test.opts, opts)
}
})
}
}
func TestAppendAllOptions(t *testing.T) {
var tests = []struct {
cfgs map[string]interface{}
opts []Help
}{
{
map[string]interface{}{
"local": struct {
Foo string `option:"foo" help:"bar text help"`
}{},
"sftp": struct {
Foo string `option:"foo" help:"bar text help2"`
Bar string `option:"bar" help:"bar text help"`
}{},
},
[]Help{
{Namespace: "local", Name: "foo", Text: "bar text help"},
{Namespace: "sftp", Name: "bar", Text: "bar text help"},
{Namespace: "sftp", Name: "foo", Text: "bar text help2"},
},
},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
var opts []Help
for ns, cfg := range test.cfgs {
opts = appendAllOptions(opts, ns, cfg)
}
if !reflect.DeepEqual(opts, test.opts) {
t.Fatalf("wrong list, want:\n %v\ngot:\n %v", test.opts, opts)
}
})
}
}