diff --git a/fs/config/ui.go b/fs/config/ui.go index 1a897aefe..ab2163e6e 100644 --- a/fs/config/ui.go +++ b/fs/config/ui.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "os" + "regexp" "slices" "sort" "strconv" @@ -441,7 +442,7 @@ func backendConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs. out.Option.Examples[1].Value == "false" && out.Option.Exclusive { // Use Confirm for Yes/No questions as it has a nicer interface= - fmt.Println(out.Option.Help) + fmt.Println(renderHelpForTerminal(out.Option.Help)) in.Result = fmt.Sprint(Confirm(Default)) } else { value := ChooseOption(out.Option, name) @@ -483,12 +484,26 @@ func RemoteConfig(ctx context.Context, name string) error { return PostConfig(ctx, name, m, ri) } +// rootRelativeMarkdownLink matches a markdown link with a root-relative +// target, e.g. [encoding section in the overview](/overview/#encoding). +var rootRelativeMarkdownLink = regexp.MustCompile(`\[([^\]]+)\]\((/[^)]*)\)`) + +// renderHelpForTerminal makes an option's help string readable on the +// terminal. The same help text is also used to generate the website +// documentation, where markdown links with root-relative targets resolve +// correctly. On the terminal those links are confusing, so rewrite them to +// "text (https://rclone.org/path)" using rclone.org as the implied root. +func renderHelpForTerminal(help string) string { + return rootRelativeMarkdownLink.ReplaceAllString(help, "$1 (https://rclone.org$2)") +} + // ChooseOption asks the user to choose an option 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.ReplaceAll(strings.TrimSpace(o.Help), "\n\n", "\n") + help = renderHelpForTerminal(help) fmt.Println(help) } diff --git a/fs/config/ui_internal_test.go b/fs/config/ui_internal_test.go new file mode 100644 index 000000000..0f125db15 --- /dev/null +++ b/fs/config/ui_internal_test.go @@ -0,0 +1,45 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRenderHelpForTerminal(t *testing.T) { + for _, test := range []struct { + name string + help string + want string + }{ + { + name: "no link", + help: "The encoding for the backend.", + want: "The encoding for the backend.", + }, + { + name: "root relative link", + help: "See the [encoding section in the overview](/overview/#encoding) for more info.", + want: "See the encoding section in the overview (https://rclone.org/overview/#encoding) for more info.", + }, + { + name: "root relative link without anchor", + help: "See [rclone serve sftp](/commands/rclone_serve_sftp) for details.", + want: "See rclone serve sftp (https://rclone.org/commands/rclone_serve_sftp) for details.", + }, + { + name: "multiple links", + help: "[the time option docs](/docs/#time-options) and [authentication docs](/azureblob#authentication).", + want: "the time option docs (https://rclone.org/docs/#time-options) and authentication docs (https://rclone.org/azureblob#authentication).", + }, + { + name: "absolute url left untouched", + help: "See [rclone forum](https://forum.rclone.org/) for help.", + want: "See [rclone forum](https://forum.rclone.org/) for help.", + }, + } { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.want, renderHelpForTerminal(test.help)) + }) + } +}