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

Commit

Permalink
Added multiline input (#157)
Browse files Browse the repository at this point in the history
* Added multiline input

* Test for multiline input

* Use of existing API to clear lines instead of escape sequence

* Multiline input example added to README

* multiline-input: change message to finish

* multiline-input: show the response in a new line

* MultilineInput renamed to Multiline

* README updated
  • Loading branch information
Daniel Pecos Martinez authored and AlecAivazis committed Nov 16, 2018
1 parent 1fa814d commit fb519f8
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func main() {
1. [Examples](#examples)
1. [Prompts](#prompts)
1. [Input](#input)
1. [Multiline](#multiline)
1. [Password](#password)
1. [Confirm](#confirm)
1. [Select](#select)
Expand Down Expand Up @@ -103,6 +104,18 @@ prompt := &survey.Input{
survey.AskOne(prompt, &name, nil)
```

### Multiline

<img src="https://thumbs.gfycat.com/ImperfectShimmeringBeagle-size_restricted.gif" width="400px"/>

```golang
text := ""
prompt := &survey.Multiline{
Message: "ping",
}
survey.AskOne(prompt, &text, nil)
```

### Password

<img src="https://thumbs.gfycat.com/CompassionateSevereHypacrosaurus-size_restricted.gif" width="400px" />
Expand Down
103 changes: 103 additions & 0 deletions multiline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package survey

import (
"fmt"
"strings"

"gopkg.in/AlecAivazis/survey.v1/core"
"gopkg.in/AlecAivazis/survey.v1/terminal"
)

type Multiline struct {
core.Renderer
Message string
Default string
Help string
}

// data available to the templates when processing
type MultilineTemplateData struct {
Multiline
Answer string
ShowAnswer bool
ShowHelp bool
}

// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
var MultilineQuestionTemplate = `
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
{{- if .ShowAnswer}}
{{- "\n"}}{{color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
{{- else }}
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
{{- color "cyan"}}[Enter 2 empty lines to finish]{{color "reset"}}
{{- end}}`

func (i *Multiline) Prompt() (interface{}, error) {
// render the template
err := i.Render(
MultilineQuestionTemplate,
MultilineTemplateData{Multiline: *i},
)
if err != nil {
return "", err
}
fmt.Println()

// start reading runes from the standard in
rr := i.NewRuneReader()
rr.SetTermMode()
defer rr.RestoreTermMode()

cursor := i.NewCursor()

multiline := make([]string, 0)

emptyOnce := false
// get the next line
for {
line := []rune{}
line, err = rr.ReadLine(0)
if err != nil {
return string(line), err
}

if string(line) == "" {
if emptyOnce {
numLines := len(multiline) + 2
cursor.PreviousLine(numLines)
for j := 0; j < numLines; j++ {
terminal.EraseLine(i.Stdio().Out, terminal.ERASE_LINE_ALL)
cursor.NextLine(1)
}
cursor.PreviousLine(numLines)
break
}
emptyOnce = true
} else {
emptyOnce = false
}
multiline = append(multiline, string(line))
}

val := strings.Join(multiline, "\n")
val = strings.TrimSpace(val)

// if the line is empty
if len(val) == 0 {
// use the default value
return i.Default, err
}

// we're done
return val, err
}

func (i *Multiline) Cleanup(val interface{}) error {
return i.Render(
MultilineQuestionTemplate,
MultilineTemplateData{Multiline: *i, Answer: val.(string), ShowAnswer: true},
)
}
154 changes: 154 additions & 0 deletions multiline_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package survey

import (
"bytes"
"fmt"
"io"
"os"
"testing"

expect "github.com/Netflix/go-expect"
"github.com/stretchr/testify/assert"
"gopkg.in/AlecAivazis/survey.v1/core"
"gopkg.in/AlecAivazis/survey.v1/terminal"
)

func init() {
// disable color output for all prompts to simplify testing
core.DisableColor = true
}

func TestMultilineRender(t *testing.T) {

tests := []struct {
title string
prompt Multiline
data MultilineTemplateData
expected string
}{
{
"Test Multiline question output without default",
Multiline{Message: "What is your favorite month:"},
MultilineTemplateData{},
fmt.Sprintf("%s What is your favorite month: [Enter 2 empty lines to finish]", core.QuestionIcon),
},
{
"Test Multiline question output with default",
Multiline{Message: "What is your favorite month:", Default: "April"},
MultilineTemplateData{},
fmt.Sprintf("%s What is your favorite month: (April) [Enter 2 empty lines to finish]", core.QuestionIcon),
},
{
"Test Multiline answer output",
Multiline{Message: "What is your favorite month:"},
MultilineTemplateData{Answer: "October", ShowAnswer: true},
fmt.Sprintf("%s What is your favorite month: \nOctober\n", core.QuestionIcon),
},
{
"Test Multiline question output without default but with help hidden",
Multiline{Message: "What is your favorite month:", Help: "This is helpful"},
MultilineTemplateData{},
fmt.Sprintf("%s What is your favorite month: [Enter 2 empty lines to finish]", string(core.HelpInputRune)),
},
{
"Test Multiline question output with default and with help hidden",
Multiline{Message: "What is your favorite month:", Default: "April", Help: "This is helpful"},
MultilineTemplateData{},
fmt.Sprintf("%s What is your favorite month: (April) [Enter 2 empty lines to finish]", string(core.HelpInputRune)),
},
{
"Test Multiline question output without default but with help shown",
Multiline{Message: "What is your favorite month:", Help: "This is helpful"},
MultilineTemplateData{ShowHelp: true},
fmt.Sprintf("%s This is helpful\n%s What is your favorite month: [Enter 2 empty lines to finish]", core.HelpIcon, core.QuestionIcon),
},
{
"Test Multiline question output with default and with help shown",
Multiline{Message: "What is your favorite month:", Default: "April", Help: "This is helpful"},
MultilineTemplateData{ShowHelp: true},
fmt.Sprintf("%s This is helpful\n%s What is your favorite month: (April) [Enter 2 empty lines to finish]", core.HelpIcon, core.QuestionIcon),
},
}

for _, test := range tests {
r, w, err := os.Pipe()
assert.Nil(t, err, test.title)

test.prompt.WithStdio(terminal.Stdio{Out: w})
test.data.Multiline = test.prompt
err = test.prompt.Render(
MultilineQuestionTemplate,
test.data,
)
assert.Nil(t, err, test.title)

w.Close()
var buf bytes.Buffer
io.Copy(&buf, r)

assert.Contains(t, buf.String(), test.expected, test.title)
}
}

func TestMultilinePrompt(t *testing.T) {
tests := []PromptTest{
{
"Test Multiline prompt interaction",
&Multiline{
Message: "What is your name?",
},
func(c *expect.Console) {
c.ExpectString("What is your name?")
c.SendLine("Larry Bird\nI guess...\nnot sure\n\n")
c.ExpectEOF()
},
"Larry Bird\nI guess...\nnot sure",
},
{
"Test Multiline prompt interaction with default",
&Multiline{
Message: "What is your name?",
Default: "Johnny Appleseed",
},
func(c *expect.Console) {
c.ExpectString("What is your name?")
c.SendLine("\n\n")
c.ExpectEOF()
},
"Johnny Appleseed",
},
{
"Test Multiline prompt interaction overriding default",
&Multiline{
Message: "What is your name?",
Default: "Johnny Appleseed",
},
func(c *expect.Console) {
c.ExpectString("What is your name?")
c.SendLine("Larry Bird\n\n")
c.ExpectEOF()
},
"Larry Bird",
},
{
"Test Multiline does not implement help interaction",
&Multiline{
Message: "What is your name?",
Help: "It might be Satoshi Nakamoto",
},
func(c *expect.Console) {
c.ExpectString("What is your name?")
c.SendLine("?")
c.SendLine("Satoshi Nakamoto\n\n")
c.ExpectEOF()
},
"?\nSatoshi Nakamoto",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
RunPromptTest(t, test)
})
}
}

0 comments on commit fb519f8

Please sign in to comment.