mirror of
https://github.com/rclone/rclone.git
synced 2024-11-07 09:04:52 +01:00
fs: Add string alternatives for setting options over the rc
Before this change options were read and set in native format. This means for example nanoseconds for durations or an integer for enumerated types, which isn't very convenient for humans. This change enables these types to be set with a string with the syntax as used in the command line instead, so `"10s"` rather than `10000000000` or `"DEBUG"` rather than `8` for log level.
This commit is contained in:
parent
e32f08f37b
commit
ae3963e4b4
@ -203,8 +203,6 @@ Rather than
|
||||
rclone rc operations/list --json '{"fs": "/tmp", "remote": "test", "opt": {"showHash": true}}'
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Special parameters
|
||||
|
||||
The rc interface supports some special parameters which apply to
|
||||
@ -294,6 +292,29 @@ $ rclone rc --json '{ "group": "job/1" }' core/stats
|
||||
}
|
||||
```
|
||||
|
||||
## Data types
|
||||
|
||||
When the API returns types, these will mostly be straight forward
|
||||
integer, string or boolean types.
|
||||
|
||||
However some of the types returned by the [options/get](#options-get)
|
||||
call and taken by the [options/set](#options-set) calls as well as the
|
||||
`vfsOpt` and the `mountOpt` are as follows:
|
||||
|
||||
- `Duration` - these are returned as an integer duration in
|
||||
nanoseconds. They may be set as an integer, or they may be set with
|
||||
time string, eg "5s". See the [options section](/docs/#options) for
|
||||
more info.
|
||||
- `Size` - these are returned as an integer number of bytes. They may
|
||||
be set as an integer or they may be set with a size suffix string,
|
||||
eg "10M". See the [options section](/docs/#options) for more info.
|
||||
- Enumerated type (such as `CutoffMode`, `DumpFlags`, `LogLevel`,
|
||||
`VfsCacheMode` - these will be returned as an integer and may be set
|
||||
as an integer but more conveniently they can be set as a string, eg
|
||||
"HARD" for `CutoffMode` or `DEBUG` for `LogLevel`.
|
||||
- `BandwidthSpec` - this will be set and returned as a string, eg
|
||||
"1M".
|
||||
|
||||
## Supported commands
|
||||
{{< rem autogenerated start "- run make rcdocs - don't edit here" >}}
|
||||
### backend/command: Runs a backend command. {#backend-command}
|
||||
@ -1155,17 +1176,18 @@ changed like this.
|
||||
|
||||
For example:
|
||||
|
||||
This sets DEBUG level logs (-vv)
|
||||
This sets DEBUG level logs (-vv) (these can be set by number or string)
|
||||
|
||||
rclone rc options/set --json '{"main": {"LogLevel": "DEBUG"}}'
|
||||
rclone rc options/set --json '{"main": {"LogLevel": 8}}'
|
||||
|
||||
And this sets INFO level logs (-v)
|
||||
|
||||
rclone rc options/set --json '{"main": {"LogLevel": 7}}'
|
||||
rclone rc options/set --json '{"main": {"LogLevel": "INFO"}}'
|
||||
|
||||
And this sets NOTICE level logs (normal without -v)
|
||||
|
||||
rclone rc options/set --json '{"main": {"LogLevel": 6}}'
|
||||
rclone rc options/set --json '{"main": {"LogLevel": "NOTICE"}}'
|
||||
|
||||
### pluginsctl/addPlugin: Add a plugin using url {#pluginsctl-addPlugin}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -264,3 +265,19 @@ func (x BwTimetable) LimitAt(tt time.Time) BwTimeSlot {
|
||||
func (x BwTimetable) Type() string {
|
||||
return "BwTimetable"
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals a string value
|
||||
func (x *BwTimetable) UnmarshalJSON(in []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(in, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return x.Set(s)
|
||||
}
|
||||
|
||||
// MarshalJSON marshals as a string value
|
||||
func (x BwTimetable) MarshalJSON() ([]byte, error) {
|
||||
s := x.String()
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Check it satisfies the interface
|
||||
var _ pflag.Value = (*BwTimetable)(nil)
|
||||
var _ flagger = (*BwTimetable)(nil)
|
||||
|
||||
func TestBwTimetableSet(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
@ -464,3 +464,102 @@ func TestBwTimetableLimitAt(t *testing.T) {
|
||||
assert.Equal(t, test.want, slot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBwTimetableUnmarshalJSON(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want BwTimetable
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
`"Mon-10:20,bad"`,
|
||||
BwTimetable(nil),
|
||||
true,
|
||||
},
|
||||
{
|
||||
`"0"`,
|
||||
BwTimetable{
|
||||
BwTimeSlot{DayOfTheWeek: 0, HHMM: 0, Bandwidth: BwPair{Tx: 0, Rx: 0}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
`"666"`,
|
||||
BwTimetable{
|
||||
BwTimeSlot{DayOfTheWeek: 0, HHMM: 0, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
`"666:333"`,
|
||||
BwTimetable{
|
||||
BwTimeSlot{DayOfTheWeek: 0, HHMM: 0, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 333 * 1024}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
`"10:20,666"`,
|
||||
BwTimetable{
|
||||
BwTimeSlot{DayOfTheWeek: 0, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 1, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 2, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 3, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 4, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 5, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 6, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
} {
|
||||
var bwt BwTimetable
|
||||
err := json.Unmarshal([]byte(test.in), &bwt)
|
||||
if test.err {
|
||||
require.Error(t, err, test.in)
|
||||
} else {
|
||||
require.NoError(t, err, test.in)
|
||||
}
|
||||
assert.Equal(t, test.want, bwt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBwTimetableMarshalJSON(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in BwTimetable
|
||||
want string
|
||||
}{
|
||||
{
|
||||
BwTimetable{
|
||||
BwTimeSlot{DayOfTheWeek: 0, HHMM: 0, Bandwidth: BwPair{Tx: 0, Rx: 0}},
|
||||
},
|
||||
`"0"`,
|
||||
},
|
||||
{
|
||||
BwTimetable{
|
||||
BwTimeSlot{DayOfTheWeek: 0, HHMM: 0, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
},
|
||||
`"666k"`,
|
||||
},
|
||||
{
|
||||
BwTimetable{
|
||||
BwTimeSlot{DayOfTheWeek: 0, HHMM: 0, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 333 * 1024}},
|
||||
},
|
||||
`"666k:333k"`,
|
||||
},
|
||||
{
|
||||
BwTimetable{
|
||||
BwTimeSlot{DayOfTheWeek: 0, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 1, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 2, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 3, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 4, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 5, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
BwTimeSlot{DayOfTheWeek: 6, HHMM: 1020, Bandwidth: BwPair{Tx: 666 * 1024, Rx: 666 * 1024}},
|
||||
},
|
||||
`"Sun-10:20,666k Mon-10:20,666k Tue-10:20,666k Wed-10:20,666k Thu-10:20,666k Fri-10:20,666k Sat-10:20,666k"`,
|
||||
},
|
||||
} {
|
||||
got, err := json.Marshal(test.in)
|
||||
require.NoError(t, err, test.want)
|
||||
assert.Equal(t, test.want, string(got))
|
||||
}
|
||||
}
|
||||
|
@ -47,3 +47,14 @@ func (m *CutoffMode) Set(s string) error {
|
||||
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 errors.Errorf("Out of range cutoff mode %d", i)
|
||||
}
|
||||
*m = (CutoffMode)(i)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,76 @@
|
||||
package fs
|
||||
|
||||
import "github.com/spf13/pflag"
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Check it satisfies the interface
|
||||
var _ pflag.Value = (*CutoffMode)(nil)
|
||||
var _ flagger = (*CutoffMode)(nil)
|
||||
|
||||
func TestCutoffModeString(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in CutoffMode
|
||||
want string
|
||||
}{
|
||||
{CutoffModeHard, "HARD"},
|
||||
{CutoffModeSoft, "SOFT"},
|
||||
{99, "CutoffMode(99)"},
|
||||
} {
|
||||
cm := test.in
|
||||
got := cm.String()
|
||||
assert.Equal(t, test.want, got, test.in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCutoffModeSet(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want CutoffMode
|
||||
err bool
|
||||
}{
|
||||
{"hard", CutoffModeHard, false},
|
||||
{"SOFT", CutoffModeSoft, false},
|
||||
{"Cautious", CutoffModeCautious, false},
|
||||
{"Potato", 0, true},
|
||||
} {
|
||||
cm := CutoffMode(0)
|
||||
err := cm.Set(test.in)
|
||||
if test.err {
|
||||
require.Error(t, err, test.in)
|
||||
} else {
|
||||
require.NoError(t, err, test.in)
|
||||
}
|
||||
assert.Equal(t, test.want, cm, test.in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCutoffModeUnmarshalJSON(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want CutoffMode
|
||||
err bool
|
||||
}{
|
||||
{`"hard"`, CutoffModeHard, false},
|
||||
{`"SOFT"`, CutoffModeSoft, false},
|
||||
{`"Cautious"`, CutoffModeCautious, false},
|
||||
{`"Potato"`, 0, true},
|
||||
{strconv.Itoa(int(CutoffModeHard)), CutoffModeHard, false},
|
||||
{strconv.Itoa(int(CutoffModeSoft)), CutoffModeSoft, false},
|
||||
{`99`, 0, true},
|
||||
{`-99`, 0, true},
|
||||
} {
|
||||
var cm CutoffMode
|
||||
err := json.Unmarshal([]byte(test.in), &cm)
|
||||
if test.err {
|
||||
require.Error(t, err, test.in)
|
||||
} else {
|
||||
require.NoError(t, err, test.in)
|
||||
}
|
||||
assert.Equal(t, test.want, cm, test.in)
|
||||
}
|
||||
}
|
||||
|
@ -91,3 +91,11 @@ func (f *DumpFlags) Set(s string) error {
|
||||
func (f *DumpFlags) Type() string {
|
||||
return "DumpFlags"
|
||||
}
|
||||
|
||||
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
|
||||
func (f *DumpFlags) UnmarshalJSON(in []byte) error {
|
||||
return UnmarshalJSONFlag(in, f, func(i int64) error {
|
||||
*f = (DumpFlags)(i)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Check it satisfies the interface
|
||||
var _ pflag.Value = (*DumpFlags)(nil)
|
||||
var _ flagger = (*DumpFlags)(nil)
|
||||
|
||||
func TestDumpFlagsString(t *testing.T) {
|
||||
assert.Equal(t, "", DumpFlags(0).String())
|
||||
@ -56,3 +57,39 @@ func TestDumpFlagsType(t *testing.T) {
|
||||
f := DumpFlags(0)
|
||||
assert.Equal(t, "DumpFlags", f.Type())
|
||||
}
|
||||
|
||||
func TestDumpFlagsUnmarshallJSON(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want DumpFlags
|
||||
wantErr string
|
||||
}{
|
||||
{`""`, DumpFlags(0), ""},
|
||||
{`"bodies"`, DumpBodies, ""},
|
||||
{`"bodies,headers,auth"`, DumpBodies | DumpHeaders | DumpAuth, ""},
|
||||
{`"bodies,headers,auth"`, DumpBodies | DumpHeaders | DumpAuth, ""},
|
||||
{`"headers,bodies,requests,responses,auth,filters"`, DumpHeaders | DumpBodies | DumpRequests | DumpResponses | DumpAuth | DumpFilters, ""},
|
||||
{`"headers,bodies,unknown,auth"`, 0, "Unknown dump flag \"unknown\""},
|
||||
{`0`, DumpFlags(0), ""},
|
||||
{strconv.Itoa(int(DumpBodies)), DumpBodies, ""},
|
||||
{strconv.Itoa(int(DumpBodies | DumpHeaders | DumpAuth)), DumpBodies | DumpHeaders | DumpAuth, ""},
|
||||
} {
|
||||
f := DumpFlags(-1)
|
||||
initial := f
|
||||
err := json.Unmarshal([]byte(test.in), &f)
|
||||
if err != nil {
|
||||
if test.wantErr == "" {
|
||||
t.Errorf("Got an error when not expecting one on %q: %v", test.in, err)
|
||||
} else {
|
||||
assert.Contains(t, err.Error(), test.wantErr)
|
||||
}
|
||||
assert.Equal(t, initial, f, test.want)
|
||||
} else {
|
||||
if test.wantErr != "" {
|
||||
t.Errorf("Got no error when expecting one on %q", test.in)
|
||||
} else {
|
||||
assert.Equal(t, test.want, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rclone/rclone/fs/config/configmap"
|
||||
"github.com/rclone/rclone/fs/fserrors"
|
||||
"github.com/rclone/rclone/lib/pacer"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
11
fs/log.go
11
fs/log.go
@ -69,6 +69,17 @@ func (l *LogLevel) Type() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
// 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 errors.Errorf("Unknown log level %d", i)
|
||||
}
|
||||
*l = (LogLevel)(i)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// LogPrint sends the text to the logger of level
|
||||
var LogPrint = func(level LogLevel, text string) {
|
||||
text = fmt.Sprintf("%-6s: %s", level, text)
|
||||
|
@ -1,15 +1,17 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Check it satisfies the interface
|
||||
var _ pflag.Value = (*LogLevel)(nil)
|
||||
var _ flagger = (*LogLevel)(nil)
|
||||
var _ fmt.Stringer = LogValueItem{}
|
||||
|
||||
type withString struct{}
|
||||
@ -26,3 +28,65 @@ func TestLogValue(t *testing.T) {
|
||||
x = LogValueHide("x", withString{})
|
||||
assert.Equal(t, "", x.String())
|
||||
}
|
||||
|
||||
func TestLogLevelString(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in LogLevel
|
||||
want string
|
||||
}{
|
||||
{LogLevelEmergency, "EMERGENCY"},
|
||||
{LogLevelDebug, "DEBUG"},
|
||||
{99, "LogLevel(99)"},
|
||||
} {
|
||||
logLevel := test.in
|
||||
got := logLevel.String()
|
||||
assert.Equal(t, test.want, got, test.in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogLevelSet(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want LogLevel
|
||||
err bool
|
||||
}{
|
||||
{"EMERGENCY", LogLevelEmergency, false},
|
||||
{"DEBUG", LogLevelDebug, false},
|
||||
{"Potato", 100, true},
|
||||
} {
|
||||
logLevel := LogLevel(100)
|
||||
err := logLevel.Set(test.in)
|
||||
if test.err {
|
||||
require.Error(t, err, test.in)
|
||||
} else {
|
||||
require.NoError(t, err, test.in)
|
||||
}
|
||||
assert.Equal(t, test.want, logLevel, test.in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogLevelUnmarshalJSON(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want LogLevel
|
||||
err bool
|
||||
}{
|
||||
{`"EMERGENCY"`, LogLevelEmergency, false},
|
||||
{`"DEBUG"`, LogLevelDebug, false},
|
||||
{`"Potato"`, 100, true},
|
||||
{strconv.Itoa(int(LogLevelEmergency)), LogLevelEmergency, false},
|
||||
{strconv.Itoa(int(LogLevelDebug)), LogLevelDebug, false},
|
||||
{"Potato", 100, true},
|
||||
{`99`, 100, true},
|
||||
{`-99`, 100, true},
|
||||
} {
|
||||
logLevel := LogLevel(100)
|
||||
err := json.Unmarshal([]byte(test.in), &logLevel)
|
||||
if test.err {
|
||||
require.Error(t, err, test.in)
|
||||
} else {
|
||||
require.NoError(t, err, test.in)
|
||||
}
|
||||
assert.Equal(t, test.want, logLevel, test.in)
|
||||
}
|
||||
}
|
||||
|
@ -196,6 +196,14 @@ func (d Duration) Type() string {
|
||||
return "Duration"
|
||||
}
|
||||
|
||||
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
|
||||
func (d *Duration) UnmarshalJSON(in []byte) error {
|
||||
return UnmarshalJSONFlag(in, d, func(i int64) error {
|
||||
*d = Duration(i)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Scan implements the fmt.Scanner interface
|
||||
func (d *Duration) Scan(s fmt.ScanState, ch rune) error {
|
||||
token, err := s.Token(true, nil)
|
||||
|
@ -1,18 +1,18 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Check it satisfies the interface
|
||||
var _ pflag.Value = (*Duration)(nil)
|
||||
var _ flagger = (*Duration)(nil)
|
||||
|
||||
func TestParseDuration(t *testing.T) {
|
||||
now := time.Date(2020, 9, 5, 8, 15, 5, 250, time.UTC)
|
||||
@ -149,3 +149,40 @@ func TestDurationScan(t *testing.T) {
|
||||
assert.Equal(t, 1, n)
|
||||
assert.Equal(t, Duration(17*60*time.Second), v)
|
||||
}
|
||||
|
||||
func TestParseUnmarshalJSON(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want time.Duration
|
||||
err bool
|
||||
}{
|
||||
{`""`, 0, true},
|
||||
{`"0"`, 0, false},
|
||||
{`"1ms"`, time.Millisecond, false},
|
||||
{`"1s"`, time.Second, false},
|
||||
{`"1m"`, time.Minute, false},
|
||||
{`"1h"`, time.Hour, false},
|
||||
{`"1d"`, time.Hour * 24, false},
|
||||
{`"1w"`, time.Hour * 24 * 7, false},
|
||||
{`"1M"`, time.Hour * 24 * 30, false},
|
||||
{`"1y"`, time.Hour * 24 * 365, false},
|
||||
{`"off"`, time.Duration(DurationOff), false},
|
||||
{`"error"`, 0, true},
|
||||
{"0", 0, false},
|
||||
{"1000000", time.Millisecond, false},
|
||||
{"1000000000", time.Second, false},
|
||||
{"60000000000", time.Minute, false},
|
||||
{"3600000000000", time.Hour, false},
|
||||
{"9223372036854775807", time.Duration(DurationOff), false},
|
||||
{"error", 0, true},
|
||||
} {
|
||||
var duration Duration
|
||||
err := json.Unmarshal([]byte(test.in), &duration)
|
||||
if test.err {
|
||||
require.Error(t, err, test.in)
|
||||
} else {
|
||||
require.NoError(t, err, test.in)
|
||||
}
|
||||
assert.Equal(t, Duration(test.want), duration, test.in)
|
||||
}
|
||||
}
|
||||
|
@ -89,17 +89,18 @@ changed like this.
|
||||
|
||||
For example:
|
||||
|
||||
This sets DEBUG level logs (-vv)
|
||||
This sets DEBUG level logs (-vv) (these can be set by number or string)
|
||||
|
||||
rclone rc options/set --json '{"main": {"LogLevel": "DEBUG"}}'
|
||||
rclone rc options/set --json '{"main": {"LogLevel": 8}}'
|
||||
|
||||
And this sets INFO level logs (-v)
|
||||
|
||||
rclone rc options/set --json '{"main": {"LogLevel": 7}}'
|
||||
rclone rc options/set --json '{"main": {"LogLevel": "INFO"}}'
|
||||
|
||||
And this sets NOTICE level logs (normal without -v)
|
||||
|
||||
rclone rc options/set --json '{"main": {"LogLevel": 6}}'
|
||||
rclone rc options/set --json '{"main": {"LogLevel": "NOTICE"}}'
|
||||
`,
|
||||
})
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package fs
|
||||
|
||||
// SizeSuffix is parsed by flag with k/M/G suffixes
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
@ -143,3 +144,30 @@ func (l SizeSuffixList) Less(i, j int) bool { return l[i] < l[j] }
|
||||
func (l SizeSuffixList) Sort() {
|
||||
sort.Sort(l)
|
||||
}
|
||||
|
||||
// UnmarshalJSONFlag unmarshals a JSON input for a flag. If the input
|
||||
// is a string then it calls the Set method on the flag otherwise it
|
||||
// calls the setInt function with a parsed int64.
|
||||
func UnmarshalJSONFlag(in []byte, x interface{ Set(string) error }, setInt func(int64) error) error {
|
||||
// Try to parse as string first
|
||||
var s string
|
||||
err := json.Unmarshal(in, &s)
|
||||
if err == nil {
|
||||
return x.Set(s)
|
||||
}
|
||||
// If that fails parse as integer
|
||||
var i int64
|
||||
err = json.Unmarshal(in, &i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return setInt(i)
|
||||
}
|
||||
|
||||
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
|
||||
func (x *SizeSuffix) UnmarshalJSON(in []byte) error {
|
||||
return UnmarshalJSONFlag(in, x, func(i int64) error {
|
||||
*x = SizeSuffix(i)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@ -9,8 +10,15 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Interface which flags must satisfy - only defined for _test.go
|
||||
// since we don't want to pull in pflag here
|
||||
type flagger interface {
|
||||
pflag.Value
|
||||
json.Unmarshaler
|
||||
}
|
||||
|
||||
// Check it satisfies the interface
|
||||
var _ pflag.Value = (*SizeSuffix)(nil)
|
||||
var _ flagger = (*SizeSuffix)(nil)
|
||||
|
||||
func TestSizeSuffixString(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
@ -102,3 +110,37 @@ func TestSizeSuffixScan(t *testing.T) {
|
||||
assert.Equal(t, 1, n)
|
||||
assert.Equal(t, SizeSuffix(17<<20), v)
|
||||
}
|
||||
|
||||
func TestSizeSuffixUnmarshalJSON(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want int64
|
||||
err bool
|
||||
}{
|
||||
{`"0"`, 0, false},
|
||||
{`"102B"`, 102, false},
|
||||
{`"1K"`, 1024, false},
|
||||
{`"2.5"`, 1024 * 2.5, false},
|
||||
{`"1M"`, 1024 * 1024, false},
|
||||
{`"1.g"`, 1024 * 1024 * 1024, false},
|
||||
{`"10G"`, 10 * 1024 * 1024 * 1024, false},
|
||||
{`"off"`, -1, false},
|
||||
{`""`, 0, true},
|
||||
{`"1q"`, 0, true},
|
||||
{`"-1K"`, 0, true},
|
||||
{`0`, 0, false},
|
||||
{`102`, 102, false},
|
||||
{`1024`, 1024, false},
|
||||
{`1000000000`, 1000000000, false},
|
||||
{`1.1.1`, 0, true},
|
||||
} {
|
||||
var ss SizeSuffix
|
||||
err := json.Unmarshal([]byte(test.in), &ss)
|
||||
if test.err {
|
||||
require.Error(t, err, test.in)
|
||||
} else {
|
||||
require.NoError(t, err, test.in)
|
||||
}
|
||||
assert.Equal(t, test.want, int64(ss))
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package vfscommon
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/lib/errors"
|
||||
)
|
||||
|
||||
@ -47,3 +48,14 @@ func (l *CacheMode) Set(s string) error {
|
||||
func (l *CacheMode) Type() string {
|
||||
return "CacheMode"
|
||||
}
|
||||
|
||||
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
|
||||
func (l *CacheMode) UnmarshalJSON(in []byte) error {
|
||||
return fs.UnmarshalJSONFlag(in, l, func(i int64) error {
|
||||
if i < 0 || i >= int64(len(cacheModeToString)) {
|
||||
return errors.Errorf("Unknown cache mode level %d", i)
|
||||
}
|
||||
*l = CacheMode(i)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package vfscommon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
@ -10,6 +12,9 @@ import (
|
||||
// Check CacheMode it satisfies the pflag interface
|
||||
var _ pflag.Value = (*CacheMode)(nil)
|
||||
|
||||
// Check CacheMode it satisfies the json.Unmarshaller interface
|
||||
var _ json.Unmarshaler = (*CacheMode)(nil)
|
||||
|
||||
func TestCacheModeString(t *testing.T) {
|
||||
assert.Equal(t, "off", CacheModeOff.String())
|
||||
assert.Equal(t, "full", CacheModeFull.String())
|
||||
@ -34,3 +39,27 @@ func TestCacheModeType(t *testing.T) {
|
||||
var m CacheMode
|
||||
assert.Equal(t, "CacheMode", m.Type())
|
||||
}
|
||||
|
||||
func TestCacheModeUnmarshalJSON(t *testing.T) {
|
||||
var m CacheMode
|
||||
|
||||
err := json.Unmarshal([]byte(`"full"`), &m)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, CacheModeFull, m)
|
||||
|
||||
err = json.Unmarshal([]byte(`"potato"`), &m)
|
||||
assert.Error(t, err, "Unknown cache mode level")
|
||||
|
||||
err = json.Unmarshal([]byte(`""`), &m)
|
||||
assert.Error(t, err, "Unknown cache mode level")
|
||||
|
||||
err = json.Unmarshal([]byte(strconv.Itoa(int(CacheModeFull))), &m)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, CacheModeFull, m)
|
||||
|
||||
err = json.Unmarshal([]byte("-1"), &m)
|
||||
assert.Error(t, err, "Unknown cache mode level")
|
||||
|
||||
err = json.Unmarshal([]byte("99"), &m)
|
||||
assert.Error(t, err, "Unknown cache mode level")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user