mirror of
https://github.com/rclone/rclone
synced 2024-11-21 22:50:16 +01:00
rc: add options/info call to enumerate options
This also makes some fields in the Options block optional - these are documented in rc.md
This commit is contained in:
parent
4d2bc190cc
commit
8fbb259091
@ -400,6 +400,76 @@ call and taken by the [options/set](#options-set) calls as well as the
|
|||||||
- `BandwidthSpec` - this will be set and returned as a string, eg
|
- `BandwidthSpec` - this will be set and returned as a string, eg
|
||||||
"1M".
|
"1M".
|
||||||
|
|
||||||
|
### Option blocks {#option-blocks}
|
||||||
|
|
||||||
|
The calls [options/info](#options-info) (for the main config) and
|
||||||
|
[config/providers](#config-providers) (for the backend config) may be
|
||||||
|
used to get information on the rclone configuration options. This can
|
||||||
|
be used to build user interfaces for displaying and setting any rclone
|
||||||
|
option.
|
||||||
|
|
||||||
|
These consist of arrays of `Option` blocks. These have the following
|
||||||
|
format. Each block describes a single option.
|
||||||
|
|
||||||
|
| Field | Type | Optional | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| Name | string | N | name of the option in snake_case |
|
||||||
|
| FieldName | string | N | name of the field used in the rc - if blank use Name |
|
||||||
|
| Help | string | N | help, started with a single sentence on a single line |
|
||||||
|
| Groups | string | Y | groups this option belongs to - comma separated string for options classification |
|
||||||
|
| Provider | string | Y | set to filter on provider |
|
||||||
|
| Default | any | N | default value, if set (and not to nil or "") then Required does nothing |
|
||||||
|
| Value | any | N | value to be set by flags |
|
||||||
|
| Examples | Examples | Y | predefined values that can be selected from list (multiple-choice option) |
|
||||||
|
| ShortOpt | string | Y | the short command line option for this |
|
||||||
|
| Hide | Visibility | N | if non zero, this option is hidden from the configurator or the command line |
|
||||||
|
| Required | bool | N | this option is required, meaning value cannot be empty unless there is a default |
|
||||||
|
| IsPassword | bool | N | set if the option is a password |
|
||||||
|
| NoPrefix | bool | N | set if the option for this should not use the backend prefix |
|
||||||
|
| Advanced | bool | N | set if this is an advanced config option |
|
||||||
|
| Exclusive | bool | N | set if the answer can only be one of the examples (empty string allowed unless Required or Default is set) |
|
||||||
|
| Sensitive | bool | N | set if this option should be redacted when using `rclone config redacted` |
|
||||||
|
|
||||||
|
An example of this might be the `--log-level` flag. Note that the
|
||||||
|
`Name` of the option becomes the command line flag with `_` replaced
|
||||||
|
with `-`.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"Advanced": false,
|
||||||
|
"Default": 5,
|
||||||
|
"DefaultStr": "NOTICE",
|
||||||
|
"Examples": [
|
||||||
|
{
|
||||||
|
"Help": "",
|
||||||
|
"Value": "EMERGENCY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Help": "",
|
||||||
|
"Value": "ALERT"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"Exclusive": true,
|
||||||
|
"FieldName": "LogLevel",
|
||||||
|
"Groups": "Logging",
|
||||||
|
"Help": "Log level DEBUG|INFO|NOTICE|ERROR",
|
||||||
|
"Hide": 0,
|
||||||
|
"IsPassword": false,
|
||||||
|
"Name": "log_level",
|
||||||
|
"NoPrefix": true,
|
||||||
|
"Required": true,
|
||||||
|
"Sensitive": false,
|
||||||
|
"Type": "LogLevel",
|
||||||
|
"Value": null,
|
||||||
|
"ValueStr": "NOTICE"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the `Help` may be multiple lines separated by `\n`. The
|
||||||
|
first line will always be a short sentence and this is the sentence
|
||||||
|
shown when running `rclone help flags`.
|
||||||
|
|
||||||
## Specifying remotes to work on
|
## Specifying remotes to work on
|
||||||
|
|
||||||
Remotes are specified with the `fs=`, `srcFs=`, `dstFs=`
|
Remotes are specified with the `fs=`, `srcFs=`, `dstFs=`
|
||||||
@ -638,7 +708,12 @@ See the [config paths](/commands/rclone_config_paths/) command for more informat
|
|||||||
Returns a JSON object:
|
Returns a JSON object:
|
||||||
- providers - array of objects
|
- providers - array of objects
|
||||||
|
|
||||||
See the [config providers](/commands/rclone_config_providers/) command for more information on the above.
|
See the [config providers](/commands/rclone_config_providers/) command
|
||||||
|
for more information on the above.
|
||||||
|
|
||||||
|
Note that the Options blocks are in the same format as returned by
|
||||||
|
"options/info". They are described in the
|
||||||
|
[option blocks](#option-blocks) section.
|
||||||
|
|
||||||
**Authentication is required for this call.**
|
**Authentication is required for this call.**
|
||||||
|
|
||||||
@ -1647,6 +1722,14 @@ set in _config then use options/config and for _filter use options/filter.
|
|||||||
This shows the internal names of the option within rclone which should
|
This shows the internal names of the option within rclone which should
|
||||||
map to the external options very easily with a few exceptions.
|
map to the external options very easily with a few exceptions.
|
||||||
|
|
||||||
|
### options/info: Get info about all the global options {#options-info}
|
||||||
|
|
||||||
|
Returns an object where keys are option block names and values are an
|
||||||
|
array of objects with info about each options.
|
||||||
|
|
||||||
|
These objects are in the same format as returned by "config/providers". They are
|
||||||
|
described in the [option blocks](#option-blocks) section.
|
||||||
|
|
||||||
### options/local: Get the currently active config for this call {#options-local}
|
### options/local: Get the currently active config for this call {#options-local}
|
||||||
|
|
||||||
Returns an object with the keys "config" and "filter".
|
Returns an object with the keys "config" and "filter".
|
||||||
|
@ -91,7 +91,12 @@ func init() {
|
|||||||
Returns a JSON object:
|
Returns a JSON object:
|
||||||
- providers - array of objects
|
- providers - array of objects
|
||||||
|
|
||||||
See the [config providers](/commands/rclone_config_providers/) command for more information on the above.
|
See the [config providers](/commands/rclone_config_providers/) command
|
||||||
|
for more information on the above.
|
||||||
|
|
||||||
|
Note that the Options blocks are in the same format as returned by
|
||||||
|
"options/info". They are described in the
|
||||||
|
[option blocks](#option-blocks) section.
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -234,10 +234,8 @@ func TestOptionMarshalJSON(t *testing.T) {
|
|||||||
"Name": "case_insensitive",
|
"Name": "case_insensitive",
|
||||||
"FieldName": "",
|
"FieldName": "",
|
||||||
"Help": "",
|
"Help": "",
|
||||||
"Provider": "",
|
|
||||||
"Default": false,
|
"Default": false,
|
||||||
"Value": true,
|
"Value": true,
|
||||||
"ShortOpt": "",
|
|
||||||
"Hide": 0,
|
"Hide": 0,
|
||||||
"Required": false,
|
"Required": false,
|
||||||
"IsPassword": false,
|
"IsPassword": false,
|
||||||
|
@ -12,19 +12,6 @@ import (
|
|||||||
"github.com/rclone/rclone/fs/filter"
|
"github.com/rclone/rclone/fs/filter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddOption adds an option set
|
|
||||||
func AddOption(name string, option interface{}) {
|
|
||||||
// FIXME remove this function when conversion to options is complete
|
|
||||||
fs.RegisterGlobalOptions(fs.OptionsInfo{Name: name, Opt: option})
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddOptionReload adds an option set with a reload function to be
|
|
||||||
// called when options are changed
|
|
||||||
func AddOptionReload(name string, option interface{}, reload func(context.Context) error) {
|
|
||||||
// FIXME remove this function when conversion to options is complete
|
|
||||||
fs.RegisterGlobalOptions(fs.OptionsInfo{Name: name, Opt: option, Reload: reload})
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Add(Call{
|
Add(Call{
|
||||||
Path: "options/blocks",
|
Path: "options/blocks",
|
||||||
@ -73,6 +60,29 @@ func rcOptionsGet(ctx context.Context, in Params) (out Params, err error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Add(Call{
|
||||||
|
Path: "options/info",
|
||||||
|
Fn: rcOptionsInfo,
|
||||||
|
Title: "Get info about all the global options",
|
||||||
|
Help: `Returns an object where keys are option block names and values are an
|
||||||
|
array of objects with info about each options.
|
||||||
|
|
||||||
|
These objects are in the same format as returned by "config/providers". They are
|
||||||
|
described in the [option blocks](#option-blocks) section.
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the info of all the option blocks
|
||||||
|
func rcOptionsInfo(ctx context.Context, in Params) (out Params, err error) {
|
||||||
|
out = make(Params)
|
||||||
|
for _, opt := range fs.OptionsRegistry {
|
||||||
|
out[opt.Name] = opt.Options
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Add(Call{
|
Add(Call{
|
||||||
Path: "options/local",
|
Path: "options/local",
|
||||||
|
@ -20,6 +20,16 @@ func clearOptionBlock() func() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var testInfo = fs.Options{{
|
||||||
|
Name: "string",
|
||||||
|
Default: "str",
|
||||||
|
Help: "It is a string",
|
||||||
|
}, {
|
||||||
|
Name: "int",
|
||||||
|
Default: 17,
|
||||||
|
Help: "It is an int",
|
||||||
|
}}
|
||||||
|
|
||||||
var testOptions = struct {
|
var testOptions = struct {
|
||||||
String string
|
String string
|
||||||
Int int
|
Int int
|
||||||
@ -28,10 +38,18 @@ var testOptions = struct {
|
|||||||
Int: 42,
|
Int: 42,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerTestOptions() {
|
||||||
|
fs.RegisterGlobalOptions(fs.OptionsInfo{Name: "potato", Opt: &testOptions, Options: testInfo})
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerTestOptionsReload(reload func(context.Context) error) {
|
||||||
|
fs.RegisterGlobalOptions(fs.OptionsInfo{Name: "potato", Opt: &testOptions, Options: testInfo, Reload: reload})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddOption(t *testing.T) {
|
func TestAddOption(t *testing.T) {
|
||||||
defer clearOptionBlock()()
|
defer clearOptionBlock()()
|
||||||
assert.Equal(t, len(fs.OptionsRegistry), 0)
|
assert.Equal(t, len(fs.OptionsRegistry), 0)
|
||||||
AddOption("potato", &testOptions)
|
registerTestOptions()
|
||||||
assert.Equal(t, len(fs.OptionsRegistry), 1)
|
assert.Equal(t, len(fs.OptionsRegistry), 1)
|
||||||
assert.Equal(t, &testOptions, fs.OptionsRegistry["potato"].Opt)
|
assert.Equal(t, &testOptions, fs.OptionsRegistry["potato"].Opt)
|
||||||
}
|
}
|
||||||
@ -40,7 +58,7 @@ func TestAddOptionReload(t *testing.T) {
|
|||||||
defer clearOptionBlock()()
|
defer clearOptionBlock()()
|
||||||
assert.Equal(t, len(fs.OptionsRegistry), 0)
|
assert.Equal(t, len(fs.OptionsRegistry), 0)
|
||||||
reload := func(ctx context.Context) error { return nil }
|
reload := func(ctx context.Context) error { return nil }
|
||||||
AddOptionReload("potato", &testOptions, reload)
|
registerTestOptionsReload(reload)
|
||||||
assert.Equal(t, len(fs.OptionsRegistry), 1)
|
assert.Equal(t, len(fs.OptionsRegistry), 1)
|
||||||
assert.Equal(t, &testOptions, fs.OptionsRegistry["potato"].Opt)
|
assert.Equal(t, &testOptions, fs.OptionsRegistry["potato"].Opt)
|
||||||
assert.Equal(t, fmt.Sprintf("%p", reload), fmt.Sprintf("%p", fs.OptionsRegistry["potato"].Reload))
|
assert.Equal(t, fmt.Sprintf("%p", reload), fmt.Sprintf("%p", fs.OptionsRegistry["potato"].Reload))
|
||||||
@ -48,7 +66,7 @@ func TestAddOptionReload(t *testing.T) {
|
|||||||
|
|
||||||
func TestOptionsBlocks(t *testing.T) {
|
func TestOptionsBlocks(t *testing.T) {
|
||||||
defer clearOptionBlock()()
|
defer clearOptionBlock()()
|
||||||
AddOption("potato", &testOptions)
|
registerTestOptions()
|
||||||
call := Calls.Get("options/blocks")
|
call := Calls.Get("options/blocks")
|
||||||
require.NotNil(t, call)
|
require.NotNil(t, call)
|
||||||
in := Params{}
|
in := Params{}
|
||||||
@ -60,7 +78,7 @@ func TestOptionsBlocks(t *testing.T) {
|
|||||||
|
|
||||||
func TestOptionsGet(t *testing.T) {
|
func TestOptionsGet(t *testing.T) {
|
||||||
defer clearOptionBlock()()
|
defer clearOptionBlock()()
|
||||||
AddOption("potato", &testOptions)
|
registerTestOptions()
|
||||||
call := Calls.Get("options/get")
|
call := Calls.Get("options/get")
|
||||||
require.NotNil(t, call)
|
require.NotNil(t, call)
|
||||||
in := Params{}
|
in := Params{}
|
||||||
@ -76,8 +94,8 @@ func TestOptionsGetMarshal(t *testing.T) {
|
|||||||
ci := fs.GetConfig(ctx)
|
ci := fs.GetConfig(ctx)
|
||||||
|
|
||||||
// Add some real options
|
// Add some real options
|
||||||
AddOption("main", ci)
|
fs.RegisterGlobalOptions(fs.OptionsInfo{Name: "main", Opt: ci, Options: nil})
|
||||||
AddOption("rc", &Opt)
|
fs.RegisterGlobalOptions(fs.OptionsInfo{Name: "rc", Opt: &Opt, Options: nil})
|
||||||
|
|
||||||
// get them
|
// get them
|
||||||
call := Calls.Get("options/get")
|
call := Calls.Get("options/get")
|
||||||
@ -92,11 +110,23 @@ func TestOptionsGetMarshal(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOptionsInfo(t *testing.T) {
|
||||||
|
defer clearOptionBlock()()
|
||||||
|
registerTestOptions()
|
||||||
|
call := Calls.Get("options/info")
|
||||||
|
require.NotNil(t, call)
|
||||||
|
in := Params{}
|
||||||
|
out, err := call.Fn(context.Background(), in)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, out)
|
||||||
|
assert.Equal(t, Params{"potato": testInfo}, out)
|
||||||
|
}
|
||||||
|
|
||||||
func TestOptionsSet(t *testing.T) {
|
func TestOptionsSet(t *testing.T) {
|
||||||
defer clearOptionBlock()()
|
defer clearOptionBlock()()
|
||||||
var reloaded int
|
var reloaded int
|
||||||
AddOptionReload("potato", &testOptions, func(ctx context.Context) error {
|
registerTestOptionsReload(func(ctx context.Context) error {
|
||||||
if reloaded > 0 {
|
if reloaded > 1 {
|
||||||
return errors.New("error while reloading")
|
return errors.New("error while reloading")
|
||||||
}
|
}
|
||||||
reloaded++
|
reloaded++
|
||||||
@ -114,8 +144,8 @@ func TestOptionsSet(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Nil(t, out)
|
require.Nil(t, out)
|
||||||
assert.Equal(t, 50, testOptions.Int)
|
assert.Equal(t, 50, testOptions.Int)
|
||||||
assert.Equal(t, "hello", testOptions.String)
|
assert.Equal(t, "str", testOptions.String)
|
||||||
assert.Equal(t, 1, reloaded)
|
assert.Equal(t, 2, reloaded)
|
||||||
|
|
||||||
// error from reload
|
// error from reload
|
||||||
_, err = call.Fn(context.Background(), in)
|
_, err = call.Fn(context.Background(), in)
|
||||||
|
@ -195,11 +195,11 @@ type Option struct {
|
|||||||
FieldName string // name of the field used in the rc JSON - will be auto filled normally
|
FieldName string // name of the field used in the rc JSON - will be auto filled normally
|
||||||
Help string // help, start with a single sentence on a single line that will be extracted for command line help
|
Help string // help, start with a single sentence on a single line that will be extracted for command line help
|
||||||
Groups string `json:",omitempty"` // groups this option belongs to - comma separated string for options classification
|
Groups string `json:",omitempty"` // groups this option belongs to - comma separated string for options classification
|
||||||
Provider string // set to filter on provider
|
Provider string `json:",omitempty"` // set to filter on provider
|
||||||
Default interface{} // default value, nil => "", if set (and not to nil or "") then Required does nothing
|
Default interface{} // default value, nil => "", if set (and not to nil or "") then Required does nothing
|
||||||
Value interface{} // value to be set by flags
|
Value interface{} // value to be set by flags
|
||||||
Examples OptionExamples `json:",omitempty"` // predefined values that can be selected from list (multiple-choice option)
|
Examples OptionExamples `json:",omitempty"` // predefined values that can be selected from list (multiple-choice option)
|
||||||
ShortOpt string // the short option for this if required
|
ShortOpt string `json:",omitempty"` // the short option for this if required
|
||||||
Hide OptionVisibility // set this to hide the config from the configurator or the command line
|
Hide OptionVisibility // set this to hide the config from the configurator or the command line
|
||||||
Required bool // this option is required, meaning value cannot be empty unless there is a default
|
Required bool // this option is required, meaning value cannot be empty unless there is a default
|
||||||
IsPassword bool // set if the option is a password
|
IsPassword bool // set if the option is a password
|
||||||
@ -348,7 +348,7 @@ func (os OptionExamples) Sort() { sort.Sort(os) }
|
|||||||
type OptionExample struct {
|
type OptionExample struct {
|
||||||
Value string
|
Value string
|
||||||
Help string
|
Help string
|
||||||
Provider string
|
Provider string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register a filesystem
|
// Register a filesystem
|
||||||
@ -417,6 +417,7 @@ var OptionsRegistry = map[string]OptionsInfo{}
|
|||||||
//
|
//
|
||||||
// Packages which need global options should use this in an init() function
|
// Packages which need global options should use this in an init() function
|
||||||
func RegisterGlobalOptions(oi OptionsInfo) {
|
func RegisterGlobalOptions(oi OptionsInfo) {
|
||||||
|
oi.Options.setValues()
|
||||||
OptionsRegistry[oi.Name] = oi
|
OptionsRegistry[oi.Name] = oi
|
||||||
if oi.Opt != nil && oi.Options != nil {
|
if oi.Opt != nil && oi.Options != nil {
|
||||||
err := oi.Check()
|
err := oi.Check()
|
||||||
@ -429,7 +430,10 @@ func RegisterGlobalOptions(oi OptionsInfo) {
|
|||||||
var optionName = regexp.MustCompile(`^[a-z0-9_]+$`)
|
var optionName = regexp.MustCompile(`^[a-z0-9_]+$`)
|
||||||
|
|
||||||
// Check ensures that for every element of oi.Options there is a field
|
// Check ensures that for every element of oi.Options there is a field
|
||||||
// in oi.Opt that matches it
|
// in oi.Opt that matches it.
|
||||||
|
//
|
||||||
|
// It also sets Option.FieldName to be the name of the field for use
|
||||||
|
// in JSON.
|
||||||
func (oi *OptionsInfo) Check() error {
|
func (oi *OptionsInfo) Check() error {
|
||||||
errCount := errcount.New()
|
errCount := errcount.New()
|
||||||
items, err := configstruct.Items(oi.Opt)
|
items, err := configstruct.Items(oi.Opt)
|
||||||
@ -471,6 +475,8 @@ func (oi *OptionsInfo) Check() error {
|
|||||||
//errCount.Add(err)
|
//errCount.Add(err)
|
||||||
Errorf(nil, "%s", err)
|
Errorf(nil, "%s", err)
|
||||||
}
|
}
|
||||||
|
// Set FieldName
|
||||||
|
option.FieldName = item.Field
|
||||||
}
|
}
|
||||||
return errCount.Err(fmt.Sprintf("internal error: options block %q", oi.Name))
|
return errCount.Err(fmt.Sprintf("internal error: options block %q", oi.Name))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user