package stario import ( "context" "errors" "os" "golang.org/x/term" ) // ErrTerminalNotTTY reports that terminal-only input was requested from a // non-terminal stdin. var ErrTerminalNotTTY = errors.New("terminal input requires a tty") var terminalCheckFunc = term.IsTerminal // IsTerminal reports whether os.Stdin is attached to a terminal. func IsTerminal() bool { return IsTerminalFile(os.Stdin) } // IsTerminalFD reports whether fd is attached to a terminal. func IsTerminalFD(fd int) bool { return terminalCheckFunc(fd) } // IsTerminalFile reports whether file is attached to a terminal. func IsTerminalFile(file *os.File) bool { if file == nil { return false } return IsTerminalFD(int(file.Fd())) } // ReadPasswordContext reads one password-style line from the terminal. // // It returns ErrTerminalNotTTY when stdin is not a terminal. If ctx is canceled // while waiting for input, this function returns ctx.Err() without waiting for // the underlying terminal read to finish. func ReadPasswordContext(ctx context.Context, hint string, defaultVal string) (string, error) { return ReadPasswordContextWithMask(ctx, hint, defaultVal, "") } // ReadPasswordContextWithMask is like ReadPasswordContext but echoes the given // mask string while the user types. func ReadPasswordContextWithMask(ctx context.Context, hint string, defaultVal string, mask string) (string, error) { if ctx == nil { ctx = context.Background() } if err := ctx.Err(); err != nil { return "", err } if !IsTerminal() { return "", ErrTerminalNotTTY } session, err := rawTerminalSessionFactory(hint, true) if err != nil { return "", err } resultCh := make(chan InputMsg, 1) go func() { defer session.Close() resultCh <- rawLineInputSession(session, defaultVal, mask, rawInputSignalReturnError) }() select { case result := <-resultCh: return result.String() case <-ctx.Done(): session.Abort() <-resultCh return "", ctx.Err() } }