Enhance the map flag to support parsing input value contains entry delimiters

Enhance the map flag to support parsing input value contains entry delimiters

Signed-off-by: Wenkai Yin(尹文开) <yinw@vmware.com>
pull/4920/head
Wenkai Yin(尹文开) 2022-05-17 10:21:35 +08:00
parent 15851ac9aa
commit 44199db79d
5 changed files with 101 additions and 15 deletions

View File

@ -0,0 +1 @@
Enhance the map flag to support parsing input value contains entry delimiters

View File

@ -98,7 +98,7 @@ func NewCreateOptions() *CreateOptions {
return &CreateOptions{
Labels: flag.NewMap(),
IncludeNamespaces: flag.NewStringArray("*"),
NamespaceMappings: flag.NewMap().WithEntryDelimiter(",").WithKeyValueDelimiter(":"),
NamespaceMappings: flag.NewMap().WithEntryDelimiter(',').WithKeyValueDelimiter(':'),
RestoreVolumes: flag.NewOptionalBool(nil),
PreserveNodePorts: flag.NewOptionalBool(nil),
IncludeClusterResources: flag.NewOptionalBool(nil),

View File

@ -132,7 +132,7 @@ type controllerRunInfo struct {
func NewCommand(f client.Factory) *cobra.Command {
var (
volumeSnapshotLocations = flag.NewMap().WithKeyValueDelimiter(":")
volumeSnapshotLocations = flag.NewMap().WithKeyValueDelimiter(':')
logLevelFlag = logging.LogLevelFlag(logrus.InfoLevel)
config = serverConfig{
pluginDir: "/plugins",

View File

@ -17,6 +17,7 @@ limitations under the License.
package flag
import (
"encoding/csv"
"fmt"
"strings"
@ -27,25 +28,25 @@ import (
// map data (i.e. a collection of key-value pairs).
type Map struct {
data map[string]string
entryDelimiter string
keyValueDelimiter string
entryDelimiter rune
keyValueDelimiter rune
}
// NewMap returns a Map using the default delimiters ("=" between keys and
// values, and "," between map entries, e.g. k1=v1,k2=v2)
// NewMap returns a Map using the default delimiters ('=' between keys and
// values, and ',' between map entries, e.g. k1=v1,k2=v2)
func NewMap() Map {
m := Map{
data: make(map[string]string),
}
return m.WithEntryDelimiter(",").WithKeyValueDelimiter("=")
return m.WithEntryDelimiter(',').WithKeyValueDelimiter('=')
}
// WithEntryDelimiter sets the delimiter to be used between map
// entries.
//
// For example, in "k1=v1&k2=v2", the entry delimiter is "&"
func (m Map) WithEntryDelimiter(delimiter string) Map {
// For example, in "k1=v1&k2=v2", the entry delimiter is '&'
func (m Map) WithEntryDelimiter(delimiter rune) Map {
m.entryDelimiter = delimiter
return m
}
@ -53,8 +54,8 @@ func (m Map) WithEntryDelimiter(delimiter string) Map {
// WithKeyValueDelimiter sets the delimiter to be used between
// keys and values.
//
// For example, in "k1=v1&k2=v2", the key-value delimiter is "="
func (m Map) WithKeyValueDelimiter(delimiter string) Map {
// For example, in "k1=v1&k2=v2", the key-value delimiter is '='
func (m Map) WithKeyValueDelimiter(delimiter rune) Map {
m.keyValueDelimiter = delimiter
return m
}
@ -63,17 +64,26 @@ func (m Map) WithKeyValueDelimiter(delimiter string) Map {
func (m *Map) String() string {
var a []string
for k, v := range m.data {
a = append(a, fmt.Sprintf("%s%s%s", k, m.keyValueDelimiter, v))
a = append(a, fmt.Sprintf("%s%s%s", k, string(m.keyValueDelimiter), v))
}
return strings.Join(a, m.entryDelimiter)
return strings.Join(a, string(m.entryDelimiter))
}
// Set parses the provided string according to the delimiters and
// assigns the result to the Map receiver. It returns an error if
// the string is not parseable.
func (m *Map) Set(s string) error {
for _, part := range strings.Split(s, m.entryDelimiter) {
kvs := strings.SplitN(part, m.keyValueDelimiter, 2)
// use csv.Reader to support parsing input string contains entry delimiters.
// e.g. `"k1=a=b,c=d",k2=v2` will be parsed into two parts: `k1=a=b,c=d` and `k2=v2`
r := csv.NewReader(strings.NewReader(s))
r.Comma = m.entryDelimiter
parts, err := r.Read()
if err != nil {
return errors.Wrapf(err, "error parsing %q", s)
}
for _, part := range parts {
kvs := strings.SplitN(part, string(m.keyValueDelimiter), 2)
if len(kvs) != 2 {
return errors.Errorf("error parsing %q", part)
}

View File

@ -0,0 +1,75 @@
/*
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package flag
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSetOfMap(t *testing.T) {
cases := []struct {
name string
input string
error bool
expected map[string]string
}{
{
name: "invalid_input_missing_quote",
input: `"k=v`,
error: true,
},
{
name: "invalid_input_contains_no_key_value_delimiter",
input: `k`,
error: true,
},
{
name: "valid input",
input: `k1=v1,k2=v2`,
error: false,
expected: map[string]string{
"k1": "v1",
"k2": "v2",
},
},
{
name: "valid input whose value contains entry delimiter",
input: `k1=v1,"k2=a=b,c=d"`,
error: false,
expected: map[string]string{
"k1": "v1",
"k2": "a=b,c=d",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
m := NewMap()
err := m.Set(c.input)
if c.error {
require.NotNil(t, err)
return
}
assert.EqualValues(t, c.expected, m.data)
})
}
}