Skip to content
This repository has been archived by the owner on Apr 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request #80 from AlecAivazis/refactor/one-write-interface
Browse files Browse the repository at this point in the history
Refactor/one write interface
  • Loading branch information
AlecAivazis authored Jul 19, 2017
2 parents 2ad8367 + 8a112f0 commit 76fb8e6
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 110 deletions.
81 changes: 30 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ func main() {
1. [Confirm](#confirm)
1. [Select](#select)
1. [MultiSelect](#multiselect)
1. [Custom Types](#custom-types)
1. [Validation](#validation)
1. [Built-in Validators](#built-in-validators)
1. [Help Text](#help-text)
1. [Changing the input rune](#changing-the-input-run)
1. [Custom Types](#custom-types)
1. [Customizing Output](#customizing-output)
1. [Versioning](#versioning)

Expand Down Expand Up @@ -168,56 +168,6 @@ change the global `survey.PageCount`, or set the `PageSize` field on the prompt:
prompt := &survey.MultiSelect{..., PageSize: 10}
```

## Custom Types

survey will assign prompt answers to your custom types if they implement one of these interfaces:
```golang
type settable interface {
WriteAnswer(value interface{}) error
}
```
```golang
type fieldsettable interface {
WriteAnswerField(field string, value interface{}) error
}
```

Here is an example how to use them:
```golang
type MyValue struct {
value string
}
func (my *MyValue) WriteAnswer(value interface{}) error {
my.value = value.(string)
}

myval := MyValue{}
survey.AskOne(
&survey.Input{
Message: "Enter something:",
},
&myval,
nil,
)
// myval.value should be populated with the prompt result now.
```

If you want to capture the name associated with the prompt you can use this form:
```golang
type MyMapValue struct {
value map[string]string
}
func (my *MyMapValue) WriteAnswerField(name string, value interface{}) error {
my.value[name] = value.(string)
}

mymap := MyMapValue{value: make(map[string]string)}
// qs defined in previous examples
survey.Ask(qs, &mymap)
// mymap.value["name"] and mymap.value["color"] should be populated with
// the corresponding prompt results now.
```

## Validation

Validating individual responses for a particular question can be done by defining a
Expand Down Expand Up @@ -284,6 +234,35 @@ surveyCore.HelpInputRune = '^'
survey.AskOne(prompt, &number, nil)
```

## Custom Types

survey will assign prompt answers to your custom types if they implement one of these interfaces:

```golang
type settable interface {
WriteAnswer(field string, value interface{}) error
}
```

Here is an example how to use them:

```golang
type MyValue struct {
value string
}
func (my *MyValue) WriteAnswer(name string, value interface{}) error {
my.value = value.(string)
}

myval := MyValue{}
survey.AskOne(
&survey.Input{
Message: "Enter something:",
},
&myval,
nil,
)
```

## Customizing Output

Expand Down
33 changes: 11 additions & 22 deletions core/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,14 @@ const tagName = "survey"

// add a few interfaces so users can configure how the prompt values are set
type settable interface {
WriteAnswer(value interface{}) error
}

type fieldsettable interface {
WriteAnswerField(field string, value interface{}) error
}

func writeByInterfaces(t interface{}, name string, v interface{}) (handled bool, err error) {
if s, ok := t.(settable); ok {
return true, s.WriteAnswer(v)
}
if fs, ok := t.(fieldsettable); ok {
return true, fs.WriteAnswerField(name, v)
}
return false, nil
WriteAnswer(field string, value interface{}) error
}

func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
if handled, err := writeByInterfaces(t, name, v); handled {
return err
// if the field is a custom type
if s, ok := t.(settable); ok {
// use the interface method
return s.WriteAnswer(name, v)
}

// the target to write to
Expand All @@ -59,12 +47,13 @@ func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
return err
}
field := elem.Field(fieldIndex)
if field.CanAddr() {
if handled, err := writeByInterfaces(field.Addr().Interface(), name, v); handled {
return err
}
// handle references to the settable interface aswell
if s, ok := t.(settable); ok && field.CanAddr() {
// use the interface method
return s.WriteAnswer(name, v)
}
// copy the value over to the field

// copy the value over to the normal struct
return copy(field, value)
case reflect.Map:
mapType := reflect.TypeOf(t).Elem()
Expand Down
50 changes: 13 additions & 37 deletions core/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,44 +252,15 @@ func TestFindFieldIndex_tagOverwriteFieldName(t *testing.T) {
}
}

type testSettable struct {
Value string
}

type testSettableStruct struct {
Settable testSettable `survey:"settable"`
}

func (t *testSettable) WriteAnswer(value interface{}) error {
if v, ok := value.(string); ok {
t.Value = v
return nil
}
return fmt.Errorf("Incompatible type %T", value)
}

func TestWriteWithSettable(t *testing.T) {
testSet1 := testSettable{}
err := WriteAnswer(&testSet1, "prompt", "stringVal")
assert.Nil(t, err)
assert.Equal(t, "stringVal", testSet1.Value)

testSet2 := testSettable{}
err = WriteAnswer(&testSet2, "prompt", 123)
assert.Error(t, fmt.Errorf("Incompatible type int64"), err)
assert.Equal(t, "", testSet2.Value)

testSetStruct := testSettableStruct{}
err = WriteAnswer(&testSetStruct, "settable", "stringVal1")
assert.Nil(t, err)
assert.Equal(t, testSetStruct.Settable.Value, "stringVal1")
}

type testFieldSettable struct {
Values map[string]string
}

func (t *testFieldSettable) WriteAnswerField(name string, value interface{}) error {
type testTaggedStruct struct {
TaggedValue string `survey:"tagged"`
}

func (t *testFieldSettable) WriteAnswer(name string, value interface{}) error {
if t.Values == nil {
t.Values = map[string]string{}
}
Expand All @@ -302,14 +273,19 @@ func (t *testFieldSettable) WriteAnswerField(name string, value interface{}) err

func TestWriteWithFieldSettable(t *testing.T) {
testSet1 := testFieldSettable{}
err := WriteAnswer(&testSet1, "prompt", "stringVal")
err := WriteAnswer(&testSet1, "values", "stringVal")
assert.Nil(t, err)
assert.Equal(t, map[string]string{"prompt": "stringVal"}, testSet1.Values)
assert.Equal(t, map[string]string{"values": "stringVal"}, testSet1.Values)

testSet2 := testFieldSettable{}
err = WriteAnswer(&testSet2, "prompt", 123)
err = WriteAnswer(&testSet2, "values", 123)
assert.Error(t, fmt.Errorf("Incompatible type int64"), err)
assert.Equal(t, map[string]string{}, testSet2.Values)

testSetStruct := testTaggedStruct{}
err = WriteAnswer(&testSetStruct, "tagged", "stringVal1")
assert.Nil(t, err)
assert.Equal(t, testSetStruct.TaggedValue, "stringVal1")
}

// CONVERSION TESTS
Expand Down

0 comments on commit 76fb8e6

Please sign in to comment.