rclone/fs/enum.go
Nick Craig-Wood 3092f82dcc fs: re-implement CutoffMode, LogLevel, TerminalColorMode with Enum
This almost 100% backwards compatible. The only difference being that
in the rc options/get output CutoffMode, LogLevel, TerminalColorMode
will be output as strings instead of integers. This is a lot more
convenient for the user. They still accept integer inputs though so
the fallout from this should be minimal.
2023-10-03 15:14:24 +01:00

110 lines
2.3 KiB
Go

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 or an integer
func (e *Enum[C]) UnmarshalJSON(in []byte) error {
choices := e.Choices()
return UnmarshalJSONFlag(in, e, func(i int64) error {
if i < 0 || i >= int64(len(choices)) {
return fmt.Errorf("%d is out of range: must be 0..%d", i, len(choices))
}
*e = Enum[C](i)
return nil
})
}
// MarshalJSON encodes it as string
func (e *Enum[C]) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}