You've already forked postgres_exporter
mirror of
https://github.com/prometheus-community/postgres_exporter.git
synced 2025-08-08 04:42:07 +03:00
472 lines
9.3 KiB
Go
472 lines
9.3 KiB
Go
// Copyright 2013-2015 Bowery, Inc.
|
|
|
|
package prompt
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
// ErrCTRLC is returned when CTRL+C is pressed stopping the prompt.
|
|
ErrCTRLC = errors.New("Interrupted (CTRL+C)")
|
|
// ErrEOF is returned when CTRL+D is pressed stopping the prompt.
|
|
ErrEOF = errors.New("EOF (CTRL+D)")
|
|
)
|
|
|
|
// Possible events that may occur when reading from input.
|
|
const (
|
|
evChar = iota
|
|
evSkip
|
|
evReturn
|
|
evEOF
|
|
evCtrlC
|
|
evBack
|
|
evClear
|
|
evHome
|
|
evEnd
|
|
evUp
|
|
evDown
|
|
evRight
|
|
evLeft
|
|
evDel
|
|
)
|
|
|
|
// IsNotTerminal checks if an error is related to the input not being a terminal.
|
|
func IsNotTerminal(err error) bool {
|
|
return isNotTerminal(err)
|
|
}
|
|
|
|
// TerminalSize retrieves the columns/rows for the terminal connected to out.
|
|
func TerminalSize(out *os.File) (int, int, error) {
|
|
return terminalSize(out)
|
|
}
|
|
|
|
// Terminal contains the state for raw terminal input.
|
|
type Terminal struct {
|
|
In *os.File
|
|
Out *os.File
|
|
History []string
|
|
histIdx int
|
|
simpleReader *bufio.Reader
|
|
t *terminal
|
|
}
|
|
|
|
// NewTerminal creates a terminal and sets it to raw input mode.
|
|
func NewTerminal() (*Terminal, error) {
|
|
in := os.Stdin
|
|
|
|
term, err := newTerminal(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Terminal{
|
|
In: in,
|
|
Out: os.Stdout,
|
|
History: make([]string, 0, 10),
|
|
histIdx: -1,
|
|
t: term,
|
|
}, nil
|
|
}
|
|
|
|
// Basic gets input and if required tests to ensure input was given.
|
|
func (term *Terminal) Basic(prefix string, required bool) (string, error) {
|
|
return term.Custom(prefix, func(input string) (string, bool) {
|
|
if required && input == "" {
|
|
return "", false
|
|
}
|
|
|
|
return input, true
|
|
})
|
|
}
|
|
|
|
// BasicDefault gets input and if empty uses the given default.
|
|
func (term *Terminal) BasicDefault(prefix, def string) (string, error) {
|
|
return term.Custom(prefix+"(Default: "+def+")", func(input string) (string, bool) {
|
|
if input == "" {
|
|
input = def
|
|
}
|
|
|
|
return input, true
|
|
})
|
|
}
|
|
|
|
// Ask gets input and checks if it's truthy or not, and returns that
|
|
// in a boolean fashion.
|
|
func (term *Terminal) Ask(question string) (bool, error) {
|
|
input, err := term.Custom(question+"?(y/n)", func(input string) (string, bool) {
|
|
if input == "" {
|
|
return "", false
|
|
}
|
|
input = strings.ToLower(input)
|
|
|
|
if input == "y" || input == "yes" {
|
|
return "yes", true
|
|
}
|
|
|
|
return "", true
|
|
})
|
|
|
|
var ok bool
|
|
if input != "" {
|
|
ok = true
|
|
}
|
|
|
|
return ok, err
|
|
}
|
|
|
|
// Custom gets input and calls the given test function with the input to
|
|
// check if the input is valid, a true return will return the string.
|
|
func (term *Terminal) Custom(prefix string, test func(string) (string, bool)) (string, error) {
|
|
var err error
|
|
var input string
|
|
var ok bool
|
|
|
|
for !ok {
|
|
input, err = term.GetPrompt(prefix)
|
|
if err != nil && err != io.EOF {
|
|
return "", err
|
|
}
|
|
|
|
input, ok = test(input)
|
|
}
|
|
|
|
return input, nil
|
|
}
|
|
|
|
// Password retrieves a password from stdin without echoing it.
|
|
func (term *Terminal) Password(prefix string) (string, error) {
|
|
var err error
|
|
var input string
|
|
|
|
for input == "" {
|
|
input, err = term.GetPassword(prefix)
|
|
if err != nil && err != io.EOF {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
return input, nil
|
|
}
|
|
|
|
// GetPrompt gets a line with the prefix and echos input.
|
|
func (term *Terminal) GetPrompt(prefix string) (string, error) {
|
|
if !term.t.supportsEditing {
|
|
return term.simplePrompt(prefix)
|
|
}
|
|
|
|
buf := NewBuffer(prefix, term.Out, true)
|
|
return term.prompt(buf, NewAnsiReader(term.In))
|
|
}
|
|
|
|
// GetPassword gets a line with the prefix and doesn't echo input.
|
|
func (term *Terminal) GetPassword(prefix string) (string, error) {
|
|
if !term.t.supportsEditing {
|
|
return term.simplePrompt(prefix)
|
|
}
|
|
|
|
buf := NewBuffer(prefix, term.Out, false)
|
|
return term.password(buf, NewAnsiReader(term.In))
|
|
}
|
|
|
|
func (term *Terminal) Close() error {
|
|
return term.t.Close()
|
|
}
|
|
|
|
// simplePrompt is a fallback prompt without line editing support.
|
|
func (term *Terminal) simplePrompt(prefix string) (string, error) {
|
|
if term.simpleReader == nil {
|
|
term.simpleReader = bufio.NewReader(term.In)
|
|
}
|
|
|
|
_, err := term.Out.Write([]byte(prefix))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
line, err := term.simpleReader.ReadString('\n')
|
|
line = strings.TrimRight(line, "\r\n ")
|
|
line = strings.TrimLeft(line, " ")
|
|
|
|
return line, err
|
|
}
|
|
|
|
// setup initializes a prompt.
|
|
func (term *Terminal) setup(buf *Buffer, in io.Reader) (*bufio.Reader, error) {
|
|
cols, _, err := TerminalSize(buf.Out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buf.Cols = cols
|
|
input := bufio.NewReader(in)
|
|
|
|
err = buf.Refresh()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return input, nil
|
|
}
|
|
|
|
// read reads a rune and parses ANSI escape sequences found
|
|
func (term *Terminal) read(in *bufio.Reader) (int, rune, error) {
|
|
char, _, err := in.ReadRune()
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
switch char {
|
|
default:
|
|
// Standard chars.
|
|
return evChar, char, nil
|
|
case tabKey, ctrlA, ctrlB, ctrlE, ctrlF, ctrlG, ctrlH, ctrlJ, ctrlK, ctrlN,
|
|
ctrlO, ctrlP, ctrlQ, ctrlR, ctrlS, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX,
|
|
ctrlY, ctrlZ:
|
|
// Skip.
|
|
return evSkip, char, nil
|
|
case returnKey:
|
|
// End of line.
|
|
return evReturn, char, nil
|
|
case ctrlD:
|
|
// End of file.
|
|
return evEOF, char, nil
|
|
case ctrlC:
|
|
// End of line, interrupted.
|
|
return evCtrlC, char, nil
|
|
case backKey:
|
|
// Backspace.
|
|
return evBack, char, nil
|
|
case ctrlL:
|
|
// Clear screen.
|
|
return evClear, char, nil
|
|
case escKey:
|
|
// Functions like arrows, home, etc.
|
|
esc := make([]byte, 2)
|
|
_, err = in.Read(esc)
|
|
if err != nil {
|
|
return -1, char, err
|
|
}
|
|
|
|
// Home, end.
|
|
if esc[0] == 'O' {
|
|
switch esc[1] {
|
|
case 'H':
|
|
// Home.
|
|
return evHome, char, nil
|
|
case 'F':
|
|
// End.
|
|
return evEnd, char, nil
|
|
}
|
|
|
|
return evSkip, char, nil
|
|
}
|
|
|
|
// Arrows, delete, pgup, pgdown, insert.
|
|
if esc[0] == '[' {
|
|
switch esc[1] {
|
|
case 'A':
|
|
// Up.
|
|
return evUp, char, nil
|
|
case 'B':
|
|
// Down.
|
|
return evDown, char, nil
|
|
case 'C':
|
|
// Right.
|
|
return evRight, char, nil
|
|
case 'D':
|
|
// Left.
|
|
return evLeft, char, nil
|
|
}
|
|
|
|
// Delete, pgup, pgdown, insert.
|
|
if esc[1] > '0' && esc[1] < '7' {
|
|
extEsc := make([]byte, 3)
|
|
_, err = in.Read(extEsc)
|
|
if err != nil {
|
|
return -1, char, err
|
|
}
|
|
|
|
if extEsc[0] == '~' {
|
|
switch esc[1] {
|
|
case '2', '5', '6':
|
|
// Insert, pgup, pgdown.
|
|
return evSkip, char, err
|
|
case '3':
|
|
// Delete.
|
|
return evDel, char, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return evSkip, char, nil
|
|
}
|
|
|
|
// prompt reads from in and parses ANSI escapes writing to buf.
|
|
func (term *Terminal) prompt(buf *Buffer, in io.Reader) (string, error) {
|
|
input, err := term.setup(buf, in)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
term.History = append(term.History, "")
|
|
term.histIdx = len(term.History) - 1
|
|
curHistIdx := term.histIdx
|
|
|
|
for {
|
|
typ, char, err := term.read(input)
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
|
|
switch typ {
|
|
case evChar:
|
|
err = buf.Insert(char)
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
|
|
term.History[curHistIdx] = buf.String()
|
|
case evSkip:
|
|
continue
|
|
case evReturn:
|
|
err = buf.EndLine()
|
|
return buf.String(), err
|
|
case evEOF:
|
|
err = buf.EndLine()
|
|
if err == nil {
|
|
err = ErrEOF
|
|
}
|
|
|
|
return buf.String(), err
|
|
case evCtrlC:
|
|
err = buf.EndLine()
|
|
if err == nil {
|
|
err = ErrCTRLC
|
|
}
|
|
|
|
return buf.String(), err
|
|
case evBack:
|
|
err = buf.DelLeft()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
|
|
term.History[curHistIdx] = buf.String()
|
|
case evClear:
|
|
err = buf.ClsScreen()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
case evHome:
|
|
err = buf.Start()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
case evEnd:
|
|
err = buf.End()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
case evUp:
|
|
idx := term.histIdx
|
|
if term.histIdx > 0 {
|
|
idx--
|
|
}
|
|
|
|
err = buf.Set([]rune(term.History[idx])...)
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
|
|
term.histIdx = idx
|
|
case evDown:
|
|
idx := term.histIdx
|
|
if term.histIdx < len(term.History)-1 {
|
|
idx++
|
|
}
|
|
|
|
err = buf.Set([]rune(term.History[idx])...)
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
|
|
term.histIdx = idx
|
|
case evRight:
|
|
err = buf.Right()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
case evLeft:
|
|
err = buf.Left()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
case evDel:
|
|
err = buf.Del()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
|
|
term.History[curHistIdx] = buf.String()
|
|
}
|
|
}
|
|
}
|
|
|
|
// password reads from in and parses restricted ANSI escapes writing to buf.
|
|
func (term *Terminal) password(buf *Buffer, in io.Reader) (string, error) {
|
|
input, err := term.setup(buf, in)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for {
|
|
typ, char, err := term.read(input)
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
|
|
switch typ {
|
|
case evChar:
|
|
err = buf.Insert(char)
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
case evSkip, evHome, evEnd, evUp, evDown, evRight, evLeft, evDel:
|
|
continue
|
|
case evReturn:
|
|
err = buf.EndLine()
|
|
return buf.String(), err
|
|
case evEOF:
|
|
err = buf.EndLine()
|
|
if err == nil {
|
|
err = ErrEOF
|
|
}
|
|
|
|
return buf.String(), err
|
|
case evCtrlC:
|
|
err = buf.EndLine()
|
|
if err == nil {
|
|
err = ErrCTRLC
|
|
}
|
|
|
|
return buf.String(), err
|
|
case evBack:
|
|
err = buf.DelLeft()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
case evClear:
|
|
err = buf.ClsScreen()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
}
|
|
}
|
|
}
|