1
0
mirror of https://github.com/rclone/rclone.git synced 2026-01-06 10:33:34 +00:00

config: fix handling of default, exclusive and required properties of multiple-choice options

Previously an empty input (just pressing enter) was only allowed for multiple-choice
options that did not have the Exclusive property set. With this change the existing
Required property is introduced into the multiple choice handling, so that one can have
Exclusive and Required options where only a value from the list is allowed, and one can
have Exclusive but not Required options where an empty value is accepted but any
non-empty value must still be matching an item from the list.

Fixes #5549

See #5551
This commit is contained in:
albertony
2021-08-22 22:43:51 +02:00
parent bd4bbed592
commit 38e2f835ed
4 changed files with 470 additions and 109 deletions

View File

@@ -61,15 +61,19 @@ func CommandDefault(commands []string, defaultIndex int) byte {
for {
fmt.Printf("%s> ", optHelp)
result := strings.ToLower(ReadLine())
if len(result) == 0 && defaultIndex >= 0 {
return optString[defaultIndex]
}
if len(result) != 1 {
continue
}
i := strings.Index(optString, string(result[0]))
if i >= 0 {
return result[0]
if len(result) == 0 {
if defaultIndex >= 0 {
return optString[defaultIndex]
}
fmt.Printf("This value is required and it has no default.\n")
} else if len(result) == 1 {
i := strings.Index(optString, string(result[0]))
if i >= 0 {
return result[0]
}
fmt.Printf("This value must be one of the following characters: %s.\n", strings.Join(opts, ", "))
} else {
fmt.Printf("This value must be a single character, one of the following: %s.\n", strings.Join(opts, ", "))
}
}
}
@@ -90,15 +94,20 @@ func Confirm(Default bool) bool {
return CommandDefault([]string{"yYes", "nNo"}, defaultIndex) == 'y'
}
// Choose one of the defaults or type a new string if newOk is set
func Choose(what string, defaults, help []string, newOk bool) string {
// Choose one of the choices, or default, or type a new string if newOk is set
func Choose(what string, kind string, choices, help []string, defaultValue string, required bool, newOk bool) string {
valueDescription := "an existing"
if newOk {
valueDescription = "your own"
}
fmt.Printf("Choose a number from below, or type in %s value.\n", valueDescription)
fmt.Printf("Choose a number from below, or type in %s %s.\n", valueDescription, kind)
if !required || defaultValue != "" {
// Empty input is allowed if not required is set, or if
// required is set but there is a default value to use.
fmt.Printf("Press Enter for the default (%q).\n", defaultValue)
}
attributes := []string{terminal.HiRedFg, terminal.HiGreenFg}
for i, text := range defaults {
for i, text := range choices {
var lines []string
if help != nil {
parts := strings.Split(help[i], "\n")
@@ -135,22 +144,101 @@ func Choose(what string, defaults, help []string, newOk bool) string {
result := ReadLine()
i, err := strconv.Atoi(result)
if err != nil {
if newOk {
return result
}
for _, v := range defaults {
for _, v := range choices {
if result == v {
return result
}
}
continue
}
if i >= 1 && i <= len(defaults) {
return defaults[i-1]
if result == "" {
// If empty string is in the predefined list of choices it has already been returned above.
// If parameter required is not set, then empty string is always a valid value.
if !required {
return result
}
// If parameter required is set, but there is a default, then empty input means default.
if defaultValue != "" {
return defaultValue
}
// If parameter required is set, and there is no default, then an input value is required.
fmt.Printf("This value is required and it has no default.\n")
} else if newOk {
// If legal input is not restricted to defined choices, then any nonzero input string is accepted.
return result
} else {
// A nonzero input string was specified but it did not match any of the strictly defined choices.
fmt.Printf("This value must match %s value.\n", valueDescription)
}
} else {
if i >= 1 && i <= len(choices) {
return choices[i-1]
}
fmt.Printf("No choices with this number.\n")
}
}
}
// Enter prompts for an input value of a specified type
func Enter(what string, kind string, defaultValue string, required bool) string {
if !required || defaultValue != "" {
// Empty input is allowed if not required is set, or if
// required is set but there is a default value to use.
fmt.Printf("Enter a %s. Press Enter for the default (%q).\n", kind, defaultValue)
} else {
fmt.Printf("Enter a %s.\n", kind)
}
for {
fmt.Printf("%s> ", what)
result := ReadLine()
if !required || result != "" {
return result
}
if defaultValue != "" {
return defaultValue
}
fmt.Printf("This value is required and it has no default.\n")
}
}
// ChoosePassword asks the user for a password
func ChoosePassword(required bool) string {
fmt.Printf("Choose an alternative below.")
actions := []string{"yYes type in my own password", "gGenerate random password"}
defaultAction := -1
if !required {
defaultAction = len(actions)
actions = append(actions, "nNo leave this optional password blank")
fmt.Printf(" Press Enter for the default (%s).", string(actions[defaultAction][0]))
}
fmt.Println()
var password string
var err error
switch i := CommandDefault(actions, defaultAction); i {
case 'y':
password = ChangePassword("the")
case 'g':
for {
fmt.Printf("Password strength in bits.\n64 is just about memorable\n128 is secure\n1024 is the maximum\n")
bits := ChooseNumber("Bits", 64, 1024)
password, err = Password(bits)
if err != nil {
log.Fatalf("Failed to make password: %v", err)
}
fmt.Printf("Your password is: %s\n", password)
fmt.Printf("Use this password? Please note that an obscured version of this \npassword (and not the " +
"password itself) will be stored under your \nconfiguration file, so keep this generated password " +
"in a safe place.\n")
if Confirm(true) {
break
}
}
case 'n':
return ""
default:
fs.Errorf(nil, "Bad choice %c", i)
}
return obscure.MustObscure(password)
}
// ChooseNumber asks the user to enter a number between min and max
// inclusive prompting them with what.
func ChooseNumber(what string, min, max int) int {
@@ -188,7 +276,7 @@ func ShowRemotes() {
func ChooseRemote() string {
remotes := LoadedData().GetSectionList()
sort.Strings(remotes)
return Choose("remote", remotes, nil, false)
return Choose("remote", "value", remotes, nil, "", true, false)
}
// mustFindByName finds the RegInfo for the remote name passed in or
@@ -277,7 +365,7 @@ func backendConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs.
fmt.Println(out.Option.Help)
in.Result = fmt.Sprint(Confirm(Default))
} else {
value := ChooseOption(out.Option)
value := ChooseOption(out.Option, name)
if value != "" {
err := out.Option.Set(value)
if err != nil {
@@ -316,51 +404,18 @@ func RemoteConfig(ctx context.Context, name string) error {
}
// ChooseOption asks the user to choose an option
func ChooseOption(o *fs.Option) string {
func ChooseOption(o *fs.Option, name string) string {
fmt.Printf("Option %s.\n", o.Name)
if o.Help != "" {
// Show help string without empty lines.
help := strings.Replace(strings.TrimSpace(o.Help), "\n\n", "\n", -1)
fmt.Println(help)
}
if o.IsPassword {
fmt.Printf("Choose an alternative below.")
actions := []string{"yYes type in my own password", "gGenerate random password"}
defaultAction := -1
if !o.Required {
defaultAction = len(actions)
actions = append(actions, "nNo leave this optional password blank")
fmt.Printf(" Press Enter for the default (%s).", string(actions[defaultAction][0]))
}
fmt.Println()
var password string
var err error
switch i := CommandDefault(actions, defaultAction); i {
case 'y':
password = ChangePassword("the")
case 'g':
for {
fmt.Printf("Password strength in bits.\n64 is just about memorable\n128 is secure\n1024 is the maximum\n")
bits := ChooseNumber("Bits", 64, 1024)
password, err = Password(bits)
if err != nil {
log.Fatalf("Failed to make password: %v", err)
}
fmt.Printf("Your password is: %s\n", password)
fmt.Printf("Use this password? Please note that an obscured version of this \npassword (and not the " +
"password itself) will be stored under your \nconfiguration file, so keep this generated password " +
"in a safe place.\n")
if Confirm(true) {
break
}
}
case 'n':
return ""
default:
fs.Errorf(nil, "Bad choice %c", i)
}
return obscure.MustObscure(password)
return ChoosePassword(o.Required)
}
what := fmt.Sprintf("%T value", o.Default)
switch o.Default.(type) {
case bool:
@@ -375,8 +430,14 @@ func ChooseOption(o *fs.Option) string {
what = "unsigned integer"
}
var in string
var defaultValue string
if o.Default == nil {
defaultValue = ""
} else {
defaultValue = fmt.Sprint(o.Default)
}
for {
fmt.Printf("Enter a %s. Press Enter for the default (%q).\n", what, fmt.Sprint(o.Default))
if len(o.Examples) > 0 {
var values []string
var help []string
@@ -384,27 +445,20 @@ func ChooseOption(o *fs.Option) string {
values = append(values, example.Value)
help = append(help, example.Help)
}
in = Choose(o.Name, values, help, !o.Exclusive)
in = Choose(o.Name, what, values, help, defaultValue, o.Required, !o.Exclusive)
} else {
fmt.Printf("%s> ", o.Name)
in = ReadLine()
in = Enter(o.Name, what, defaultValue, o.Required)
}
if in == "" {
if o.Required && fmt.Sprint(o.Default) == "" {
fmt.Printf("This value is required and it has no default.\n")
if in != "" {
newIn, err := configstruct.StringToInterface(o.Default, in)
if err != nil {
fmt.Printf("Failed to parse %q: %v\n", in, err)
continue
}
break
in = fmt.Sprint(newIn) // canonicalise
}
newIn, err := configstruct.StringToInterface(o.Default, in)
if err != nil {
fmt.Printf("Failed to parse %q: %v\n", in, err)
continue
}
in = fmt.Sprint(newIn) // canonicalise
break
return in
}
return in
}
// NewRemoteName asks the user for a name for a new remote
@@ -440,7 +494,7 @@ func NewRemote(ctx context.Context, name string) error {
// Set the type first
for {
newType = ChooseOption(fsOption())
newType = ChooseOption(fsOption(), name)
ri, err = fs.Find(newType)
if err != nil {
fmt.Printf("Bad remote %q: %v\n", newType, err)