blob: 16e88eaf1f60e136a5cddec2a0011e9caf9d35f9 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package utils
import (
"bufio"
"fmt"
"io"
"strings"
"go.chromium.org/luci/common/errors"
)
// PromptFunc obtains consent from the user for the given request string.
//
// This function is used to provide the user some context through the provided
// string and then obtain a yes/no answer from the user.
type PromptFunc func(string) bool
// CLIPrompt returns a PromptFunc to prompt user on CLI.
//
// If user set environment NO_PROMPT=true, it will returns a nil PromptFunc
// In case of erroneous input from user, the returned PromptFunc prompts the
// user again.
// defaultResponse is returned on empty response from the user.
// In case of other system errors, the returned promptFunc returns false.
func CLIPrompt(w io.Writer, r io.Reader, defaultResponse bool) PromptFunc {
if noPrompt() {
return nil
}
return func(reason string) bool {
if err := prompt(w, reason, defaultResponse); err != nil {
return escapeHatchResponse
}
for {
i, err := getPromptResponse(r)
if err != nil {
return escapeHatchResponse
}
switch i {
case "":
return defaultResponse
case "y", "yes":
return true
case "n", "no":
fmt.Fprintln(w, "User aborted session.")
return false
default:
if err := reprompt(w, i); err != nil {
return escapeHatchResponse
}
}
}
}
}
// escapeHatchResponse is the response from user prompt on system errors.
//
// In case of such errors, we're unable to interact with the user entirely, so
// it's best to abort the userinput session.
const escapeHatchResponse = false
func prompt(w io.Writer, reason string, defaultResponse bool) error {
b := bufio.NewWriter(w)
fmt.Fprintf(b, "%s\n", reason)
fmt.Fprintf(b, "\tContinue?")
if defaultResponse {
fmt.Fprintf(b, " [Y/n] ")
} else {
fmt.Fprintf(b, " [y/N] ")
}
return b.Flush()
}
func getPromptResponse(r io.Reader) (string, error) {
b := bufio.NewReader(r)
i, err := b.ReadString('\n')
if err != nil {
return "", errors.Annotate(err, "get prompt response").Err()
}
return strings.Trim(strings.ToLower(i), " \n\t"), nil
}
func reprompt(w io.Writer, response string) error {
b := bufio.NewWriter(w)
fmt.Fprintf(b, "\n\tInvalid response %s. Please enter 'y' or 'n': ", response)
return b.Flush()
}