From 3092f82dcc9212cafa09590aad6bb9b2d5d03fe5 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 27 Sep 2023 15:31:47 +0100 Subject: [PATCH] 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. --- fs/cutoffmode.go | 58 +++++++----------------------------- fs/cutoffmode_test.go | 2 +- fs/enum.go | 17 ++++++----- fs/enum_test.go | 14 +++++---- fs/log.go | 53 ++++++++------------------------ fs/log_test.go | 2 +- fs/terminalcolormode.go | 50 +++++-------------------------- fs/terminalcolormode_test.go | 2 +- 8 files changed, 53 insertions(+), 145 deletions(-) diff --git a/fs/cutoffmode.go b/fs/cutoffmode.go index 231d1aa44..759068ddc 100644 --- a/fs/cutoffmode.go +++ b/fs/cutoffmode.go @@ -1,58 +1,22 @@ package fs -import ( - "fmt" - "strings" -) +type cutoffModeChoices struct{} + +func (cutoffModeChoices) Choices() []string { + return []string{ + CutoffModeHard: "HARD", + CutoffModeSoft: "SOFT", + CutoffModeCautious: "CAUTIOUS", + } +} // CutoffMode describes the possible delete modes in the config -type CutoffMode byte +type CutoffMode = Enum[cutoffModeChoices] -// MaxTransferMode constants +// CutoffMode constants const ( CutoffModeHard CutoffMode = iota CutoffModeSoft CutoffModeCautious 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 - }) -} diff --git a/fs/cutoffmode_test.go b/fs/cutoffmode_test.go index d8eaacd48..867209843 100644 --- a/fs/cutoffmode_test.go +++ b/fs/cutoffmode_test.go @@ -22,7 +22,7 @@ func TestCutoffModeString(t *testing.T) { }{ {CutoffModeHard, "HARD"}, {CutoffModeSoft, "SOFT"}, - {99, "CutoffMode(99)"}, + {99, "Unknown(99)"}, } { cm := test.in got := cm.String() diff --git a/fs/enum.go b/fs/enum.go index fe54405e1..3a0127f39 100644 --- a/fs/enum.go +++ b/fs/enum.go @@ -91,14 +91,17 @@ func (e *Enum[C]) Scan(s fmt.ScanState, ch rune) error { 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 { - var choice string - err := json.Unmarshal(in, &choice) - if err != nil { - return err - } - return e.Set(choice) + 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 diff --git a/fs/enum_test.go b/fs/enum_test.go index 3413ff259..67dc36056 100644 --- a/fs/enum_test.go +++ b/fs/enum_test.go @@ -107,16 +107,20 @@ func TestEnumUnmarshalJSON(t *testing.T) { for _, test := range []struct { in string want choice - err bool + err string }{ - {`"A"`, choiceA, false}, - {`"B"`, choiceB, false}, - {`"D"`, choice(0), true}, + {`"A"`, choiceA, ""}, + {`"B"`, choiceB, ""}, + {`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 err := json.Unmarshal([]byte(test.in), &got) - if test.err { + if test.err != "" { require.Error(t, err, test.in) + assert.ErrorContains(t, err, test.err) } else { require.NoError(t, err, test.in) } diff --git a/fs/log.go b/fs/log.go index c85db3779..b04c77252 100644 --- a/fs/log.go +++ b/fs/log.go @@ -10,7 +10,7 @@ import ( ) // 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 // subset. @@ -34,52 +34,25 @@ const ( LogLevelDebug // Debug level, needs -vv ) -var logLevelToString = []string{ - LogLevelEmergency: "EMERGENCY", - LogLevelAlert: "ALERT", - LogLevelCritical: "CRITICAL", - LogLevelError: "ERROR", - LogLevelWarning: "WARNING", - LogLevelNotice: "NOTICE", - LogLevelInfo: "INFO", - LogLevelDebug: "DEBUG", -} +type logLevelChoices struct{} -// String turns a LogLevel into a string -func (l LogLevel) String() string { - if l >= LogLevel(len(logLevelToString)) { - return fmt.Sprintf("LogLevel(%d)", l) +func (logLevelChoices) Choices() []string { + return []string{ + LogLevelEmergency: "EMERGENCY", + LogLevelAlert: "ALERT", + LogLevelCritical: "CRITICAL", + LogLevelError: "ERROR", + LogLevelWarning: "WARNING", + LogLevelNotice: "NOTICE", + LogLevelInfo: "INFO", + LogLevelDebug: "DEBUG", } - return logLevelToString[l] } -// Set a LogLevel -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 { +func (logLevelChoices) Type() string { 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 var LogPrintPid = false diff --git a/fs/log_test.go b/fs/log_test.go index e6f012785..e536622f4 100644 --- a/fs/log_test.go +++ b/fs/log_test.go @@ -39,7 +39,7 @@ func TestLogLevelString(t *testing.T) { }{ {LogLevelEmergency, "EMERGENCY"}, {LogLevelDebug, "DEBUG"}, - {99, "LogLevel(99)"}, + {99, "Unknown(99)"}, } { logLevel := test.in got := logLevel.String() diff --git a/fs/terminalcolormode.go b/fs/terminalcolormode.go index 7da58e13f..44ec4b94a 100644 --- a/fs/terminalcolormode.go +++ b/fs/terminalcolormode.go @@ -1,12 +1,7 @@ package fs -import ( - "fmt" - "strings" -) - // TerminalColorMode describes how ANSI codes should be handled -type TerminalColorMode byte +type TerminalColorMode = Enum[terminalColorModeChoices] // TerminalColorMode constants const ( @@ -15,43 +10,12 @@ const ( TerminalColorModeAlways ) -var terminalColorModeToString = []string{ - TerminalColorModeAuto: "AUTO", - TerminalColorModeNever: "NEVER", - TerminalColorModeAlways: "ALWAYS", -} +type terminalColorModeChoices struct{} -// String converts a TerminalColorMode to a string -func (m TerminalColorMode) String() string { - if m >= TerminalColorMode(len(terminalColorModeToString)) { - return fmt.Sprintf("TerminalColorMode(%d)", m) +func (terminalColorModeChoices) Choices() []string { + return []string{ + TerminalColorModeAuto: "AUTO", + 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 - }) } diff --git a/fs/terminalcolormode_test.go b/fs/terminalcolormode_test.go index 6060b4342..4d6e0405d 100644 --- a/fs/terminalcolormode_test.go +++ b/fs/terminalcolormode_test.go @@ -17,7 +17,7 @@ func TestTerminalColorModeString(t *testing.T) { {TerminalColorModeAuto, "AUTO"}, {TerminalColorModeAlways, "ALWAYS"}, {TerminalColorModeNever, "NEVER"}, - {36, "TerminalColorMode(36)"}, + {36, "Unknown(36)"}, } { tcm := test.in assert.Equal(t, test.want, tcm.String(), test.in)