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.
This commit is contained in:
Nick Craig-Wood 2023-09-27 15:31:47 +01:00
parent 60a6ef914c
commit 3092f82dcc
8 changed files with 53 additions and 145 deletions

View File

@ -1,58 +1,22 @@
package fs package fs
import ( type cutoffModeChoices struct{}
"fmt"
"strings" func (cutoffModeChoices) Choices() []string {
) return []string{
CutoffModeHard: "HARD",
CutoffModeSoft: "SOFT",
CutoffModeCautious: "CAUTIOUS",
}
}
// CutoffMode describes the possible delete modes in the config // CutoffMode describes the possible delete modes in the config
type CutoffMode byte type CutoffMode = Enum[cutoffModeChoices]
// MaxTransferMode constants // CutoffMode constants
const ( const (
CutoffModeHard CutoffMode = iota CutoffModeHard CutoffMode = iota
CutoffModeSoft CutoffModeSoft
CutoffModeCautious CutoffModeCautious
CutoffModeDefault = CutoffModeHard CutoffModeDefault = CutoffModeHard
) )
var cutoffModeToString = []string{
CutoffModeHard: "HARD",
CutoffModeSoft: "SOFT",
CutoffModeCautious: "CAUTIOUS",
}
// String turns a LogLevel into a string
func (m CutoffMode) String() string {
if m >= CutoffMode(len(cutoffModeToString)) {
return fmt.Sprintf("CutoffMode(%d)", m)
}
return cutoffModeToString[m]
}
// Set a LogLevel
func (m *CutoffMode) Set(s string) error {
for n, name := range cutoffModeToString {
if s != "" && name == strings.ToUpper(s) {
*m = CutoffMode(n)
return nil
}
}
return fmt.Errorf("unknown cutoff mode %q", s)
}
// Type of the value
func (m CutoffMode) Type() string {
return "string"
}
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
func (m *CutoffMode) UnmarshalJSON(in []byte) error {
return UnmarshalJSONFlag(in, m, func(i int64) error {
if i < 0 || i >= int64(len(cutoffModeToString)) {
return fmt.Errorf("out of range cutoff mode %d", i)
}
*m = (CutoffMode)(i)
return nil
})
}

View File

@ -22,7 +22,7 @@ func TestCutoffModeString(t *testing.T) {
}{ }{
{CutoffModeHard, "HARD"}, {CutoffModeHard, "HARD"},
{CutoffModeSoft, "SOFT"}, {CutoffModeSoft, "SOFT"},
{99, "CutoffMode(99)"}, {99, "Unknown(99)"},
} { } {
cm := test.in cm := test.in
got := cm.String() got := cm.String()

View File

@ -91,14 +91,17 @@ func (e *Enum[C]) Scan(s fmt.ScanState, ch rune) error {
return e.Set(string(token)) return e.Set(string(token))
} }
// UnmarshalJSON parses it as a string // UnmarshalJSON parses it as a string or an integer
func (e *Enum[C]) UnmarshalJSON(in []byte) error { func (e *Enum[C]) UnmarshalJSON(in []byte) error {
var choice string choices := e.Choices()
err := json.Unmarshal(in, &choice) return UnmarshalJSONFlag(in, e, func(i int64) error {
if err != nil { if i < 0 || i >= int64(len(choices)) {
return err return fmt.Errorf("%d is out of range: must be 0..%d", i, len(choices))
} }
return e.Set(choice) *e = Enum[C](i)
return nil
})
} }
// MarshalJSON encodes it as string // MarshalJSON encodes it as string

View File

@ -107,16 +107,20 @@ func TestEnumUnmarshalJSON(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
in string in string
want choice want choice
err bool err string
}{ }{
{`"A"`, choiceA, false}, {`"A"`, choiceA, ""},
{`"B"`, choiceB, false}, {`"B"`, choiceB, ""},
{`"D"`, choice(0), true}, {`0`, choiceA, ""},
{`1`, choiceB, ""},
{`"D"`, choice(0), `invalid choice "D" from: A, B, C`},
{`100`, choice(0), `100 is out of range: must be 0..3`},
} { } {
var got choice var got choice
err := json.Unmarshal([]byte(test.in), &got) err := json.Unmarshal([]byte(test.in), &got)
if test.err { if test.err != "" {
require.Error(t, err, test.in) require.Error(t, err, test.in)
assert.ErrorContains(t, err, test.err)
} else { } else {
require.NoError(t, err, test.in) require.NoError(t, err, test.in)
} }

View File

@ -10,7 +10,7 @@ import (
) )
// LogLevel describes rclone's logs. These are a subset of the syslog log levels. // LogLevel describes rclone's logs. These are a subset of the syslog log levels.
type LogLevel byte type LogLevel = Enum[logLevelChoices]
// Log levels. These are the syslog levels of which we only use a // Log levels. These are the syslog levels of which we only use a
// subset. // subset.
@ -34,52 +34,25 @@ const (
LogLevelDebug // Debug level, needs -vv LogLevelDebug // Debug level, needs -vv
) )
var logLevelToString = []string{ type logLevelChoices struct{}
LogLevelEmergency: "EMERGENCY",
LogLevelAlert: "ALERT",
LogLevelCritical: "CRITICAL",
LogLevelError: "ERROR",
LogLevelWarning: "WARNING",
LogLevelNotice: "NOTICE",
LogLevelInfo: "INFO",
LogLevelDebug: "DEBUG",
}
// String turns a LogLevel into a string func (logLevelChoices) Choices() []string {
func (l LogLevel) String() string { return []string{
if l >= LogLevel(len(logLevelToString)) { LogLevelEmergency: "EMERGENCY",
return fmt.Sprintf("LogLevel(%d)", l) LogLevelAlert: "ALERT",
LogLevelCritical: "CRITICAL",
LogLevelError: "ERROR",
LogLevelWarning: "WARNING",
LogLevelNotice: "NOTICE",
LogLevelInfo: "INFO",
LogLevelDebug: "DEBUG",
} }
return logLevelToString[l]
} }
// Set a LogLevel func (logLevelChoices) Type() string {
func (l *LogLevel) Set(s string) error {
for n, name := range logLevelToString {
if s != "" && name == s {
*l = LogLevel(n)
return nil
}
}
return fmt.Errorf("unknown log level %q", s)
}
// Type of the value
func (l LogLevel) Type() string {
return "LogLevel" return "LogLevel"
} }
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
func (l *LogLevel) UnmarshalJSON(in []byte) error {
return UnmarshalJSONFlag(in, l, func(i int64) error {
if i < 0 || i >= int64(LogLevel(len(logLevelToString))) {
return fmt.Errorf("unknown log level %d", i)
}
*l = (LogLevel)(i)
return nil
})
}
// LogPrintPid enables process pid in log // LogPrintPid enables process pid in log
var LogPrintPid = false var LogPrintPid = false

View File

@ -39,7 +39,7 @@ func TestLogLevelString(t *testing.T) {
}{ }{
{LogLevelEmergency, "EMERGENCY"}, {LogLevelEmergency, "EMERGENCY"},
{LogLevelDebug, "DEBUG"}, {LogLevelDebug, "DEBUG"},
{99, "LogLevel(99)"}, {99, "Unknown(99)"},
} { } {
logLevel := test.in logLevel := test.in
got := logLevel.String() got := logLevel.String()

View File

@ -1,12 +1,7 @@
package fs package fs
import (
"fmt"
"strings"
)
// TerminalColorMode describes how ANSI codes should be handled // TerminalColorMode describes how ANSI codes should be handled
type TerminalColorMode byte type TerminalColorMode = Enum[terminalColorModeChoices]
// TerminalColorMode constants // TerminalColorMode constants
const ( const (
@ -15,43 +10,12 @@ const (
TerminalColorModeAlways TerminalColorModeAlways
) )
var terminalColorModeToString = []string{ type terminalColorModeChoices struct{}
TerminalColorModeAuto: "AUTO",
TerminalColorModeNever: "NEVER",
TerminalColorModeAlways: "ALWAYS",
}
// String converts a TerminalColorMode to a string func (terminalColorModeChoices) Choices() []string {
func (m TerminalColorMode) String() string { return []string{
if m >= TerminalColorMode(len(terminalColorModeToString)) { TerminalColorModeAuto: "AUTO",
return fmt.Sprintf("TerminalColorMode(%d)", m) TerminalColorModeNever: "NEVER",
TerminalColorModeAlways: "ALWAYS",
} }
return terminalColorModeToString[m]
}
// Set a TerminalColorMode
func (m *TerminalColorMode) Set(s string) error {
for n, name := range terminalColorModeToString {
if s != "" && name == strings.ToUpper(s) {
*m = TerminalColorMode(n)
return nil
}
}
return fmt.Errorf("unknown terminal color mode %q", s)
}
// Type of TerminalColorMode
func (m TerminalColorMode) Type() string {
return "string"
}
// UnmarshalJSON converts a string/integer in JSON to a TerminalColorMode
func (m *TerminalColorMode) UnmarshalJSON(in []byte) error {
return UnmarshalJSONFlag(in, m, func(i int64) error {
if i < 0 || i >= int64(len(terminalColorModeToString)) {
return fmt.Errorf("out of range terminal color mode %d", i)
}
*m = (TerminalColorMode)(i)
return nil
})
} }

View File

@ -17,7 +17,7 @@ func TestTerminalColorModeString(t *testing.T) {
{TerminalColorModeAuto, "AUTO"}, {TerminalColorModeAuto, "AUTO"},
{TerminalColorModeAlways, "ALWAYS"}, {TerminalColorModeAlways, "ALWAYS"},
{TerminalColorModeNever, "NEVER"}, {TerminalColorModeNever, "NEVER"},
{36, "TerminalColorMode(36)"}, {36, "Unknown(36)"},
} { } {
tcm := test.in tcm := test.in
assert.Equal(t, test.want, tcm.String(), test.in) assert.Equal(t, test.want, tcm.String(), test.in)