options: Allow registering

This commit is contained in:
Alexander Neumann 2017-04-13 23:55:20 +02:00
parent 541484d142
commit b7671dafc8
2 changed files with 167 additions and 0 deletions

View file

@ -3,6 +3,7 @@ package options
import (
"reflect"
"restic/errors"
"sort"
"strconv"
"strings"
"time"
@ -11,6 +12,80 @@ import (
// 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)
}
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)
}
sort.Sort(helpList(opts))
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 {
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)

View file

@ -218,3 +218,95 @@ func TestOptionsApplyInvalid(t *testing.T) {
})
}
}
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{
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{
Help{Name: "foo", Text: "bar text help"},
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{
Help{Name: "bar", Text: "bar text help"},
Help{Name: "foo", Text: "bar text help"},
},
},
{
&teststruct,
[]Help{
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{
Help{Namespace: "local", Name: "foo", Text: "bar text help"},
Help{Namespace: "sftp", Name: "foo", Text: "bar text help2"},
Help{Namespace: "sftp", Name: "bar", Text: "bar text help"},
},
},
}
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)
}
})
}
}