mirror of
https://github.com/rclone/rclone.git
synced 2025-01-03 12:59:32 +01:00
fs: create fs.Enum for easy creation of parameters from a list of choices
This commit is contained in:
parent
3553cc4a5f
commit
60a6ef914c
@ -24,8 +24,7 @@ func camelToSnake(in string) string {
|
|||||||
// StringToInterface turns in into an interface{} the same type as def
|
// StringToInterface turns in into an interface{} the same type as def
|
||||||
func StringToInterface(def interface{}, in string) (newValue interface{}, err error) {
|
func StringToInterface(def interface{}, in string) (newValue interface{}, err error) {
|
||||||
typ := reflect.TypeOf(def)
|
typ := reflect.TypeOf(def)
|
||||||
switch typ.Kind() {
|
if typ.Kind() == reflect.String && typ.Name() == "string" {
|
||||||
case reflect.String:
|
|
||||||
// Pass strings unmodified
|
// Pass strings unmodified
|
||||||
return in, nil
|
return in, nil
|
||||||
}
|
}
|
||||||
|
107
fs/enum.go
Normal file
107
fs/enum.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum is an option which can only be one of the Choices.
|
||||||
|
//
|
||||||
|
// Suggested implementation is something like this:
|
||||||
|
//
|
||||||
|
// type choice = Enum[choices]
|
||||||
|
//
|
||||||
|
// const (
|
||||||
|
// choiceA choice = iota
|
||||||
|
// choiceB
|
||||||
|
// choiceC
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// type choices struct{}
|
||||||
|
//
|
||||||
|
// func (choices) Choices() []string {
|
||||||
|
// return []string{
|
||||||
|
// choiceA: "A",
|
||||||
|
// choiceB: "B",
|
||||||
|
// choiceC: "C",
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
type Enum[C Choices] byte
|
||||||
|
|
||||||
|
// Choices returns the valid choices for this type.
|
||||||
|
//
|
||||||
|
// It must work on the zero value.
|
||||||
|
//
|
||||||
|
// Note that when using this in an Option the ExampleChoices will be
|
||||||
|
// filled in automatically.
|
||||||
|
type Choices interface {
|
||||||
|
// Choices returns the valid choices for this type
|
||||||
|
Choices() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String renders the Enum as a string
|
||||||
|
func (e Enum[C]) String() string {
|
||||||
|
choices := e.Choices()
|
||||||
|
if int(e) >= len(choices) {
|
||||||
|
return fmt.Sprintf("Unknown(%d)", e)
|
||||||
|
}
|
||||||
|
return choices[e]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choices returns the possible values of the Enum.
|
||||||
|
func (e Enum[C]) Choices() []string {
|
||||||
|
var c C
|
||||||
|
return c.Choices()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help returns a comma separated list of all possible states.
|
||||||
|
func (e Enum[C]) Help() string {
|
||||||
|
return strings.Join(e.Choices(), ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Enum entries
|
||||||
|
func (e *Enum[C]) Set(s string) error {
|
||||||
|
for i, choice := range e.Choices() {
|
||||||
|
if strings.EqualFold(s, choice) {
|
||||||
|
*e = Enum[C](i)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid choice %q from: %s", s, e.Help())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type of the value.
|
||||||
|
//
|
||||||
|
// If C has a Type() string method then it will be used instead.
|
||||||
|
func (e Enum[C]) Type() string {
|
||||||
|
var c C
|
||||||
|
if do, ok := any(c).(typer); ok {
|
||||||
|
return do.Type()
|
||||||
|
}
|
||||||
|
return strings.Join(e.Choices(), "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the fmt.Scanner interface
|
||||||
|
func (e *Enum[C]) Scan(s fmt.ScanState, ch rune) error {
|
||||||
|
token, err := s.Token(true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.Set(string(token))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON parses it as a string
|
||||||
|
func (e *Enum[C]) UnmarshalJSON(in []byte) error {
|
||||||
|
var choice string
|
||||||
|
err := json.Unmarshal(in, &choice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.Set(choice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes it as string
|
||||||
|
func (e *Enum[C]) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(e.String())
|
||||||
|
}
|
139
fs/enum_test.go
Normal file
139
fs/enum_test.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type choices struct{}
|
||||||
|
|
||||||
|
func (choices) Choices() []string {
|
||||||
|
return []string{
|
||||||
|
choiceA: "A",
|
||||||
|
choiceB: "B",
|
||||||
|
choiceC: "C",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type choice = Enum[choices]
|
||||||
|
|
||||||
|
const (
|
||||||
|
choiceA choice = iota
|
||||||
|
choiceB
|
||||||
|
choiceC
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check it satisfies the interfaces
|
||||||
|
var (
|
||||||
|
_ flagger = (*choice)(nil)
|
||||||
|
_ flaggerNP = choice(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnumString(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in choice
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{choiceA, "A"},
|
||||||
|
{choiceB, "B"},
|
||||||
|
{choiceC, "C"},
|
||||||
|
{choice(100), "Unknown(100)"},
|
||||||
|
} {
|
||||||
|
got := test.in.String()
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumType(t *testing.T) {
|
||||||
|
assert.Equal(t, "A|B|C", choiceA.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enum with Type() on the choices
|
||||||
|
type choicestype struct{}
|
||||||
|
|
||||||
|
func (choicestype) Choices() []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (choicestype) Type() string {
|
||||||
|
return "potato"
|
||||||
|
}
|
||||||
|
|
||||||
|
type choicetype = Enum[choicestype]
|
||||||
|
|
||||||
|
func TestEnumTypeWithFunction(t *testing.T) {
|
||||||
|
assert.Equal(t, "potato", choicetype(0).Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumHelp(t *testing.T) {
|
||||||
|
assert.Equal(t, "A, B, C", choice(0).Help())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumSet(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in string
|
||||||
|
want choice
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"A", choiceA, false},
|
||||||
|
{"B", choiceB, false},
|
||||||
|
{"C", choiceC, false},
|
||||||
|
{"D", choice(100), true},
|
||||||
|
} {
|
||||||
|
var got choice
|
||||||
|
err := got.Set(test.in)
|
||||||
|
if test.err {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumScan(t *testing.T) {
|
||||||
|
var v choice
|
||||||
|
n, err := fmt.Sscan(" A ", &v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, n)
|
||||||
|
assert.Equal(t, choiceA, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumUnmarshalJSON(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in string
|
||||||
|
want choice
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{`"A"`, choiceA, false},
|
||||||
|
{`"B"`, choiceB, false},
|
||||||
|
{`"D"`, choice(0), true},
|
||||||
|
} {
|
||||||
|
var got choice
|
||||||
|
err := json.Unmarshal([]byte(test.in), &got)
|
||||||
|
if test.err {
|
||||||
|
require.Error(t, err, test.in)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err, test.in)
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.want, got, test.in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumMarshalJSON(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in choice
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{choiceA, `"A"`},
|
||||||
|
{choiceB, `"B"`},
|
||||||
|
} {
|
||||||
|
got, err := json.Marshal(&test.in)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.want, string(got), fmt.Sprintf("%#v", test.in))
|
||||||
|
}
|
||||||
|
}
|
@ -60,6 +60,15 @@ func (os Options) setValues() {
|
|||||||
if o.Default == nil {
|
if o.Default == nil {
|
||||||
o.Default = ""
|
o.Default = ""
|
||||||
}
|
}
|
||||||
|
// Create options for Enums
|
||||||
|
if do, ok := o.Default.(Choices); ok && len(o.Examples) == 0 {
|
||||||
|
o.Exclusive = true
|
||||||
|
o.Required = true
|
||||||
|
o.Examples = make(OptionExamples, len(do.Choices()))
|
||||||
|
for i, choice := range do.Choices() {
|
||||||
|
o.Examples[i].Value = choice
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user