Skip to content

Commit

Permalink
Add flagvalue for key=value flags
Browse files Browse the repository at this point in the history
  • Loading branch information
dadgar committed Feb 22, 2024
1 parent 5e65f51 commit 2a49042
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 0 deletions.
66 changes: 66 additions & 0 deletions internal/pkg/flagvalue/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package flagvalue

import (
"fmt"
"reflect"
"strings"
)

type simpleMapValue[K, V SimpleValue] struct {
value *map[K]V
changed bool
}

// SimpleMap returns a pflags.Value that sets the map at p with the default
// val or the value(s) provided via a flag.
func SimpleMap[K, V SimpleValue](val map[K]V, p *map[K]V) *simpleMapValue[K, V] {
isv := new(simpleMapValue[K, V])
isv.value = p
*isv.value = val
return isv
}

func (m *simpleMapValue[K, V]) Set(s string) error {
// Split the string, KEY=VALUE, into its parts
parts := strings.SplitN(s, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("expected key=value, got %q", s)
}

// Parse the key
var key K
_, err := fmt.Sscanf(parts[0], "%v", &key)
if err != nil {
return fmt.Errorf("failed to parse key: %w", err)
}

// Parse the value
var value V
_, err = fmt.Sscanf(parts[1], "%v", &value)
if err != nil {
return fmt.Errorf("failed to parse value: %w", err)
}

if !m.changed {
*m.value = map[K]V{
key: value,
}
} else {
(*m.value)[key] = value
}

m.changed = true
return nil
}

func (m *simpleMapValue[K, V]) Type() string {
return reflect.TypeOf(*m.value).String()
}

func (m *simpleMapValue[K, V]) String() string {
out := make([]string, 0, len(*m.value))
for k, v := range *m.value {
out = append(out, fmt.Sprintf("%v=%v", k, v))
}
return "[" + strings.Join(out, ",") + "]"
}
134 changes: 134 additions & 0 deletions internal/pkg/flagvalue/map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package flagvalue_test

import (
"testing"

"github.com/hashicorp/hcp/internal/pkg/flagvalue"
"github.com/spf13/pflag"
"github.com/stretchr/testify/require"
)

func ExampleSimpleMap() {
var headers map[string]string
f := pflag.NewFlagSet("example", pflag.ContinueOnError)
f.AddFlag(&pflag.Flag{
Name: "headers",
Usage: "headers is a set of headers to send with the request. May be specified multiple times in the form of KEY=VALUE.",
Value: flagvalue.SimpleMap(nil, &headers),
})

// Make the request
}

func TestSimpleMap_StringToString(t *testing.T) {
t.Parallel()
r := require.New(t)

var m map[string]string
f := pflag.NewFlagSet("test", pflag.ContinueOnError)
f.AddFlag(&pflag.Flag{
Name: "values",
Value: flagvalue.SimpleMap(map[string]string{"test": "value"}, &m),
})

// Parse an empty set of args
r.NoError(f.Parse([]string{}))

// Expect the default
r.Equal(map[string]string{"test": "value"}, m)

// Parse with the flag set
r.NoError(f.Parse([]string{"--values", "hello=world", "--values", "false=123"}))
r.EqualValues(map[string]string{
"hello": "world",
"false": "123",
}, m)
}

func TestSimpleMap_StringToInt(t *testing.T) {
t.Parallel()
r := require.New(t)

var m map[string]int
f := pflag.NewFlagSet("test", pflag.ContinueOnError)
f.AddFlag(&pflag.Flag{
Name: "values",
Value: flagvalue.SimpleMap(map[string]int{"test": 22}, &m),
})

// Parse an empty set of args
r.NoError(f.Parse([]string{}))

// Expect the default
r.Equal(map[string]int{"test": 22}, m)

// Parse with the flag set
r.NoError(f.Parse([]string{"--values", "hello=49", "--values", "123=123"}))
r.EqualValues(map[string]int{
"hello": 49,
"123": 123,
}, m)
}

func TestSimpleMap_StringToBool(t *testing.T) {
t.Parallel()
r := require.New(t)

var m map[string]bool
f := pflag.NewFlagSet("test", pflag.ContinueOnError)
f.AddFlag(&pflag.Flag{
Name: "values",
Value: flagvalue.SimpleMap(map[string]bool{"test": true}, &m),
})

// Parse an empty set of args
r.NoError(f.Parse([]string{}))

// Expect the default
r.Equal(map[string]bool{"test": true}, m)

// Parse with the flag set
r.NoError(f.Parse([]string{"--values", "hello=true", "--values", "test=false"}))
r.EqualValues(map[string]bool{
"hello": true,
"test": false,
}, m)
}

func TestSimpleMap_IntToString(t *testing.T) {
t.Parallel()
r := require.New(t)

var m map[int]string
f := pflag.NewFlagSet("test", pflag.ContinueOnError)
f.AddFlag(&pflag.Flag{
Name: "values",
Value: flagvalue.SimpleMap(map[int]string{49: "test"}, &m),
})

// Parse an empty set of args
r.NoError(f.Parse([]string{}))

// Expect the default
r.Equal(map[int]string{49: "test"}, m)

// Parse with the flag set
r.NoError(f.Parse([]string{"--values", "49=other", "--values", "123=123"}))
r.EqualValues(map[int]string{
49: "other",
123: "123",
}, m)
}

func TestSimpleMap(t *testing.T) {
t.Parallel()
r := require.New(t)

var stringToString map[string]string
var stringToInt map[string]int
var i8Tof64 map[int8]float64

r.Equal("map[string]string", flagvalue.SimpleMap(nil, &stringToString).Type())
r.Equal("map[string]int", flagvalue.SimpleMap(nil, &stringToInt).Type())
r.Equal("map[int8]float64", flagvalue.SimpleMap(nil, &i8Tof64).Type())
}

0 comments on commit 2a49042

Please sign in to comment.