diff --git a/src/restic/options/options.go b/src/restic/options/options.go index c5d9ff3e3..03d06c32d 100644 --- a/src/restic/options/options.go +++ b/src/restic/options/options.go @@ -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) diff --git a/src/restic/options/options_test.go b/src/restic/options/options_test.go index a5ab83952..7b9eb7db8 100644 --- a/src/restic/options/options_test.go +++ b/src/restic/options/options_test.go @@ -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) + } + }) + } +}