config: fix root-relative markdown links in interactive config help - fixes #8239

Option help strings are also used to generate the website documentation,
so some contain markdown links with root-relative targets such as
[encoding section in the overview](/overview/#encoding). These render
correctly on rclone.org but are confusing in the interactive config
prompt, where the user sees the raw markdown and the link has no
reachable root.

Rewrite such links to text (https://rclone.org/path) when showing an
option's help in the interactive config. The raw help is left unchanged
so documentation generation is unaffected.
This commit is contained in:
yashanil98
2026-06-25 02:20:14 -07:00
committed by GitHub
parent d1e85a7d9c
commit c7c6646ea3
2 changed files with 61 additions and 1 deletions

View File

@@ -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)
}

View File

@@ -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))
})
}
}