config: add more human readable configmap.Simple output

Before this, String() quoted every part of the config map even if it
wasn't necessary.

The new Human() method removes the quoting and adds the special case
for "true" values.
This commit is contained in:
Nick Craig-Wood
2025-09-30 11:59:40 +01:00
parent 8ed55c61e1
commit 72437a9ca2
3 changed files with 155 additions and 35 deletions

View File

@@ -136,9 +136,11 @@ func (c Simple) Set(key, value string) {
c[key] = value
}
// String the map value the same way the config parser does, but with
// string the map value the same way the config parser does, but with
// sorted keys for reproducibility.
func (c Simple) String() string {
//
// If human is set then use fewer quotes.
func (c Simple) string(human bool) string {
var ks = make([]string, 0, len(c))
for k := range c {
ks = append(ks, k)
@@ -150,9 +152,14 @@ func (c Simple) String() string {
out.WriteRune(',')
}
out.WriteString(k)
v := c[k]
if human && v == "true" {
continue
}
out.WriteRune('=')
if !human || strings.ContainsAny(v, `'":=,`) {
out.WriteRune('\'')
for _, ch := range c[k] {
for _, ch := range v {
out.WriteRune(ch)
// Escape ' as ''
if ch == '\'' {
@@ -160,10 +167,26 @@ func (c Simple) String() string {
}
}
out.WriteRune('\'')
} else {
out.WriteString(v)
}
}
return out.String()
}
// Human converts the map value the same way the config parser does,
// but with sorted keys for reproducibility. This does it in human
// readable form with fewer quotes.
func (c Simple) Human() string {
return c.string(true)
}
// String the map value the same way the config parser does, but with
// sorted keys for reproducibility.
func (c Simple) String() string {
return c.string(false)
}
// Encode from c into a string suitable for putting on the command line
func (c Simple) Encode() (string, error) {
if len(c) == 0 {

View File

@@ -0,0 +1,121 @@
package configmap_test
import (
"fmt"
"testing"
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/fspath"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSimpleString(t *testing.T) {
for _, tt := range []struct {
name string
want string
in configmap.Simple
}{
{name: "Nil", want: "", in: configmap.Simple(nil)},
{name: "Empty", want: "", in: configmap.Simple{}},
{name: "Basic", want: "config1='one'", in: configmap.Simple{
"config1": "one",
}},
{name: "Truthy", want: "config1='true',config2='true'", in: configmap.Simple{
"config1": "true",
"config2": "true",
}},
{name: "Quotable", want: `config1='"one"',config2=':two:',config3='''three''',config4='=four=',config5=',five,'`, in: configmap.Simple{
"config1": `"one"`,
"config2": `:two:`,
"config3": `'three'`,
"config4": `=four=`,
"config5": `,five,`,
}},
{name: "Order", want: "config1='one',config2='two',config3='three',config4='four',config5='five'", in: configmap.Simple{
"config5": "five",
"config4": "four",
"config3": "three",
"config2": "two",
"config1": "one",
}},
{name: "Escaping", want: "apple='',config1='o''n''e'", in: configmap.Simple{
"config1": "o'n'e",
"apple": "",
}},
} {
t.Run(tt.name, func(t *testing.T) {
// Check forwards
params := tt.in.String()
assert.Equal(t, tt.want, params)
// Check config round trips through config parser
remote := ":local," + params + ":"
if params == "" {
remote = ":local:"
}
what := fmt.Sprintf("remote = %q", remote)
parsed, err := fspath.Parse(remote)
require.NoError(t, err, what)
if len(parsed.Config) != 0 || len(tt.in) != 0 {
assert.Equal(t, tt.in, parsed.Config, what)
}
})
}
}
func TestSimpleHuman(t *testing.T) {
for _, tt := range []struct {
name string
want string
in configmap.Simple
}{
{name: "Nil", want: "", in: configmap.Simple(nil)},
{name: "Empty", want: "", in: configmap.Simple{}},
{name: "Basic", want: "config1=one", in: configmap.Simple{
"config1": "one",
}},
{name: "Truthy", want: "config1,config2", in: configmap.Simple{
"config1": "true",
"config2": "true",
}},
{name: "Quotable", want: `config1='"one"',config2=':two:',config3='''three''',config4='=four=',config5=',five,'`, in: configmap.Simple{
"config1": `"one"`,
"config2": `:two:`,
"config3": `'three'`,
"config4": `=four=`,
"config5": `,five,`,
}},
{name: "Order", want: "config1=one,config2=two,config3=three,config4=four,config5=five", in: configmap.Simple{
"config5": "five",
"config4": "four",
"config3": "three",
"config2": "two",
"config1": "one",
}},
{name: "Escaping", want: "apple=,config1='o''n''e'", in: configmap.Simple{
"config1": "o'n'e",
"apple": "",
}},
} {
t.Run(tt.name, func(t *testing.T) {
// Check forwards
params := tt.in.Human()
assert.Equal(t, tt.want, params)
// Check config round trips through config parser
remote := ":local," + params + ":"
if params == "" {
remote = ":local:"
}
what := fmt.Sprintf("remote = %q", remote)
parsed, err := fspath.Parse(remote)
require.NoError(t, err, what)
if len(parsed.Config) != 0 || len(tt.in) != 0 {
assert.Equal(t, tt.in, parsed.Config, what)
}
})
}
}

View File

@@ -246,30 +246,6 @@ func TestConfigMapClearSetters(t *testing.T) {
assert.Equal(t, []Setter(nil), m.setters)
}
func TestSimpleString(t *testing.T) {
// Basic
assert.Equal(t, "", Simple(nil).String())
assert.Equal(t, "", Simple{}.String())
assert.Equal(t, "config1='one'", Simple{
"config1": "one",
}.String())
// Check ordering
assert.Equal(t, "config1='one',config2='two',config3='three',config4='four',config5='five'", Simple{
"config5": "five",
"config4": "four",
"config3": "three",
"config2": "two",
"config1": "one",
}.String())
// Check escaping
assert.Equal(t, "apple='',config1='o''n''e'", Simple{
"config1": "o'n'e",
"apple": "",
}.String())
}
func TestSimpleEncode(t *testing.T) {
for _, test := range []struct {
in Simple