in cli/azd/pkg/input/asker.go [84:247]
func askOnePrompt(p survey.Prompt, response interface{}, isTerminal bool, stdout io.Writer, stdin io.Reader) error {
// Like (*bufio.Reader).ReadString(byte) except that it does not buffer input from the input stream.
// Instead, it reads a byte at a time until a delimiter is found or EOF is encountered,
// returning bytes read with no extra characters consumed.
readStringNoBuffer := func(r io.Reader, delim byte) (string, error) {
strBuf := bytes.Buffer{}
readBuf := make([]byte, 1)
for {
bytesRead, err := r.Read(readBuf)
if bytesRead > 0 {
// discard err, per documentation, WriteByte always succeeds.
_ = strBuf.WriteByte(readBuf[0])
}
if err != nil {
return strBuf.String(), err
}
if readBuf[0] == delim {
return strBuf.String(), nil
}
}
}
if isTerminal {
opts := []survey.AskOpt{}
// When asking a question which requires a text response, show the cursor, it helps
// users understand we need some input.
if _, ok := p.(*survey.Input); ok {
opts = append(opts, withShowCursor)
}
opts = append(opts, survey.WithIcons(func(icons *survey.IconSet) {
// use bright, bold, blue question mark for all questions
icons.Question.Format = "blue+hb"
icons.SelectFocus.Format = "blue+hb"
icons.Help.Format = "black+h"
icons.Help.Text = "Hint:"
icons.MarkedOption.Text = "[" + color.GreenString("✓") + "]"
icons.MarkedOption.Format = ""
}))
return survey.AskOne(p, response, opts...)
}
switch v := p.(type) {
case *survey.Input:
var pResponse = response.(*string)
fmt.Fprintf(stdout, "%s", v.Message[0:len(v.Message)-1])
if v.Default != "" {
fmt.Fprintf(stdout, " (or hit enter to use the default %s)", v.Default)
}
fmt.Fprintf(stdout, "%s ", v.Message[len(v.Message)-1:])
result, err := readStringNoBuffer(stdin, '\n')
if err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("reading response: %w", err)
}
result = strings.TrimSpace(result)
if result == "" && v.Default != "" {
result = v.Default
}
*pResponse = result
return nil
case *survey.Password:
var pResponse = response.(*string)
fmt.Fprintf(stdout, "%s", v.Message)
result, err := readStringNoBuffer(stdin, '\n')
if err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("reading response: %w", err)
}
result = strings.TrimSpace(result)
*pResponse = result
return nil
case *survey.MultiSelect:
// For multi-selection, azd will do a Select for each item, using the default to control the Y or N
defValue, err := v.Default.([]string)
if !err {
return fmt.Errorf("default response type is not a string list '%s'", v.Message)
}
fmt.Fprintf(stdout, "%s:", v.Message)
selection := make([]string, 0, len(v.Options))
for _, item := range v.Options {
response := slices.Contains(defValue, item)
err := askOnePrompt(&survey.Confirm{
Message: fmt.Sprintf("\n select %s?", item),
}, &response, isTerminal, stdout, stdin)
if err != nil {
return err
}
confirmation := "N"
if response {
confirmation = "Y"
selection = append(selection, item)
}
fmt.Fprintf(stdout, " %s", confirmation)
}
// assign the selection to the response
*(response.(*[]string)) = selection
return nil
case *survey.Select:
fmt.Fprintf(stdout, "%s", v.Message[0:len(v.Message)-1])
if v.Default != nil {
fmt.Fprintf(stdout, " (or hit enter to use the default %v)", v.Default)
}
fmt.Fprintf(stdout, "%s ", v.Message[len(v.Message)-1:])
result, err := readStringNoBuffer(stdin, '\n')
if err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("reading response: %w", err)
}
result = strings.TrimSpace(result)
if result == "" && v.Default != nil {
result = v.Default.(string)
}
for idx, val := range v.Options {
if val == result {
switch ptr := response.(type) {
case *string:
*ptr = val
case *int:
*ptr = idx
default:
return fmt.Errorf("bad type %T for result, should be (*int or *string)", response)
}
return nil
}
}
return fmt.Errorf(
"'%s' is not an allowed choice. allowed choices: %v",
result,
strings.Join(v.Options, ","))
case *survey.Confirm:
var pResponse = response.(*bool)
for {
fmt.Fprint(stdout, v.Message)
if *pResponse {
fmt.Fprint(stdout, " (Y/n)")
} else {
fmt.Fprintf(stdout, " (y/N)")
}
result, err := readStringNoBuffer(stdin, '\n')
if err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("reading response: %w", err)
}
switch strings.TrimSpace(result) {
case "Y", "y":
*pResponse = true
return nil
case "N", "n":
*pResponse = false
return nil
case "":
return nil
}
}
default:
panic(fmt.Sprintf("don't know how to prompt for type %T", p))
}
}