Skip to content

Commit

Permalink
Implemented the Transform idea (AlecAivazis#104)
Browse files Browse the repository at this point in the history
* fix some grammar and misspells

* Implement AlecAivazis#4 and AlecAivazis#98

* fix README.md -> survey.Title typo
  • Loading branch information
kataras authored and AlecAivazis committed Oct 21, 2017
1 parent fddc032 commit 05a1967
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var qs = []*survey.Question{
Name: "name",
Prompt: &survey.Input{Message: "What is your name?"},
Validate: survey.Required,
Transform: survey.Title,
},
{
Name: "color",
Expand Down
3 changes: 2 additions & 1 deletion examples/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ var simpleQs = []*survey.Question{
Prompt: &survey.Input{
Message: "What is your name?",
},
Validate: survey.Required,
Validate: survey.Required,
Transform: survey.Title,
},
{
Name: "color",
Expand Down
26 changes: 22 additions & 4 deletions survey.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@ var PageSize = 7
// Validator is a function passed to a Question after a user has provided a response.
// If the function returns an error, then the user will be prompted again for another
// response.
type Validator func(interface{}) error
type Validator func(ans interface{}) error

// Transformer is a function passed to a Question after a user has provided a response.
// The function can be used to implement a custom logic that will result to return
// a different representation of the given answer.
//
// Look `TransformString`, `ToLower` `Title` and `ComposeTransformers` for more.
type Transformer func(ans interface{}) (newAns interface{})

// Question is the core data structure for a survey questionnaire.
type Question struct {
Name string
Prompt Prompt
Validate Validator
Name string
Prompt Prompt
Validate Validator
Transform Transformer
}

// Prompt is the primary interface for the objects that can take user input
Expand Down Expand Up @@ -65,6 +73,7 @@ matching name. For example:
Name: "name",
Prompt: &survey.Input{Message: "What is your name?"},
Validate: survey.Required,
Transform: survey.Title,
},
}
Expand Down Expand Up @@ -109,6 +118,15 @@ func Ask(qs []*Question, response interface{}) error {
}
}

if q.Transform != nil {
// check if we have a transformer available, if so
// then try to acquire the new representation of the
// answer, if the resulting answer is not nil.
if newAns := q.Transform(ans); newAns != nil {
ans = newAns
}
}

// tell the prompt to cleanup with the validated value
q.Prompt.Cleanup(ans)

Expand Down
76 changes: 76 additions & 0 deletions transform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package survey

import (
"reflect"
"strings"
)

// TransformString returns a `Transformer` based on the "f"
// function which accepts a string representation of the answer
// and returns a new one, transformed, answer.
// Take for example the functions inside the std `strings` package,
// they can be converted to a compatible `Transformer` by using this function,
// i.e: `TransformString(strings.Title)`, `TransformString(strings.ToUpper)`.
//
// Note that `TransformString` is just a helper, `Transformer` can be used
// to transform any type of answer.
func TransformString(f func(s string) string) Transformer {
return func(ans interface{}) interface{} {
// if the answer value passed in is the zero value of the appropriate type
if isZero(reflect.ValueOf(ans)) {
// skip this `Transformer` by returning a nil value.
// The original answer will be not affected,
// see survey.go#L125.
return nil
}

// "ans" is never nil here, so we don't have to check that
// see survey.go#L97 for more.
// Make sure that the the answer's value was a typeof string.
s, ok := ans.(string)
if !ok {
return nil
}

return f(s)
}
}

// ToLower is a `Transformer`.
// It receives an answer value
// and returns a copy of the "ans"
// with all Unicode letters mapped to their lower case.
//
// Note that if "ans" is not a string then it will
// return a nil value, meaning that the above answer
// will not be affected by this call at all.
func ToLower(ans interface{}) interface{} {
transformer := TransformString(strings.ToLower)
return transformer(ans)
}

// Title is a `Transformer`.
// It receives an answer value
// and returns a copy of the "ans"
// with all Unicode letters that begin words
// mapped to their title case.
//
// Note that if "ans" is not a string then it will
// return a nil value, meaning that the above answer
// will not be affected by this call at all.
func Title(ans interface{}) interface{} {
transformer := TransformString(strings.Title)
return transformer(ans)
}

// ComposeTransformers is a variadic function used to create one transformer from many.
func ComposeTransformers(transformers ...Transformer) Transformer {
// return a transformer that calls each one sequentially
return func(ans interface{}) interface{} {
// execute each transformer
for _, t := range transformers {
ans = t(ans)
}
return ans
}
}
44 changes: 44 additions & 0 deletions transform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package survey

import (
"strings"
"testing"
)

func testStringTransformer(t *testing.T, f func(string) string) {
transformer := TransformString(f)

tests := []string{
"hello my name is",
"where are you from",
"does that matter?",
}

for _, tt := range tests {
if expected, got := f(tt), transformer(tt); expected != got {
t.Errorf("TransformString transformer failed to transform the answer, expected '%s' but got '%s'.", expected, got)
}
}
}

func TestTransformString(t *testing.T) {
testStringTransformer(t, strings.ToTitle) // all letters titled
testStringTransformer(t, strings.ToLower) // all letters lowercase
}

func TestComposeTransformers(t *testing.T) {
// create a transformer which makes no sense,
// remember: transformer can be used for any type
// we just test the built'n functions that
// happens to be for strings only.
transformer := ComposeTransformers(
Title,
ToLower,
)

ans := "my name is"
if expected, got := strings.ToLower(ans), transformer(ans); expected != got {
// the result should be lowercase.
t.Errorf("TestComposeTransformers transformer failed to transform the answer to title->lowercase, expected '%s' but got '%s'.", expected, got)
}
}
4 changes: 2 additions & 2 deletions validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ func ComposeValidators(validators ...Validator) Validator {
return func(val interface{}) error {
// execute each validator
for _, validator := range validators {
// if the string is not valid
// if the answer's value is not valid
if err := validator(val); err != nil {
// return the error
return err
}
}
// we passed all validators, the string is valid
// we passed all validators, the answer is valid
return nil
}
}
Expand Down

0 comments on commit 05a1967

Please sign in to comment.