github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/console/prompter.go (about)

     1  package console
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/peterh/liner"
     8  )
     9  
    10  // Stdin holds the stdin line reader (also using stdout for printing prompts).
    11  // Only this reader may be used for input because it keeps an internal buffer.
    12  var Stdin = newTerminalPrompter()
    13  
    14  // UserPrompter defines the methods needed by the console to promt the user for
    15  // various types of inputs.
    16  type UserPrompter interface {
    17  	// PromptInput displays the given prompt to the user and requests some textual
    18  	// data to be entered, returning the input of the user.
    19  	PromptInput(prompt string) (string, error)
    20  
    21  	// PromptPassword displays the given prompt to the user and requests some textual
    22  	// data to be entered, but one which must not be echoed out into the terminal.
    23  	// The method returns the input provided by the user.
    24  	PromptPassword(prompt string) (string, error)
    25  
    26  	// PromptConfirm displays the given prompt to the user and requests a boolean
    27  	// choice to be made, returning that choice.
    28  	PromptConfirm(prompt string) (bool, error)
    29  
    30  	// SetHistory sets the the input scrollback history that the prompter will allow
    31  	// the user to scroll back to.
    32  	SetHistory(history []string)
    33  
    34  	// AppendHistory appends an entry to the scrollback history. It should be called
    35  	// if and only if the prompt to append was a valid command.
    36  	AppendHistory(command string)
    37  
    38  	// ClearHistory clears the entire history
    39  	ClearHistory()
    40  
    41  	// SetWordCompleter sets the completion function that the prompter will call to
    42  	// fetch completion candidates when the user presses tab.
    43  	SetWordCompleter(completer WordCompleter)
    44  }
    45  
    46  // WordCompleter takes the currently edited line with the cursor position and
    47  // returns the completion candidates for the partial word to be completed. If
    48  // the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello,
    49  // wo!!!", 9) is passed to the completer which may returns ("Hello, ", {"world",
    50  // "Word"}, "!!!") to have "Hello, world!!!".
    51  type WordCompleter func(line string, pos int) (string, []string, string)
    52  
    53  // terminalPrompter is a UserPrompter backed by the liner package. It supports
    54  // prompting the user for various input, among others for non-echoing password
    55  // input.
    56  type terminalPrompter struct {
    57  	*liner.State
    58  	warned     bool
    59  	supported  bool
    60  	normalMode liner.ModeApplier
    61  	rawMode    liner.ModeApplier
    62  }
    63  
    64  // newTerminalPrompter creates a liner based user input prompter working off the
    65  // standard input and output streams.
    66  func newTerminalPrompter() *terminalPrompter {
    67  	p := new(terminalPrompter)
    68  	// Get the original mode before calling NewLiner.
    69  	// This is usually regular "cooked" mode where characters echo.
    70  	normalMode, _ := liner.TerminalMode()
    71  	// Turn on liner. It switches to raw mode.
    72  	p.State = liner.NewLiner()
    73  	rawMode, err := liner.TerminalMode()
    74  	if err != nil || !liner.TerminalSupported() {
    75  		p.supported = false
    76  	} else {
    77  		p.supported = true
    78  		p.normalMode = normalMode
    79  		p.rawMode = rawMode
    80  		// Switch back to normal mode while we're not prompting.
    81  		normalMode.ApplyMode()
    82  	}
    83  	p.SetCtrlCAborts(true)
    84  	p.SetTabCompletionStyle(liner.TabPrints)
    85  	p.SetMultiLineMode(true)
    86  	return p
    87  }
    88  
    89  // PromptInput displays the given prompt to the user and requests some textual
    90  // data to be entered, returning the input of the user.
    91  func (p *terminalPrompter) PromptInput(prompt string) (string, error) {
    92  	if p.supported {
    93  		p.rawMode.ApplyMode()
    94  		defer p.normalMode.ApplyMode()
    95  	} else {
    96  		// liner tries to be smart about printing the prompt
    97  		// and doesn't print anything if input is redirected.
    98  		// Un-smart it by printing the prompt always.
    99  		fmt.Print(prompt)
   100  		prompt = ""
   101  		defer fmt.Println()
   102  	}
   103  	return p.State.Prompt(prompt)
   104  }
   105  
   106  // PromptPassword displays the given prompt to the user and requests some textual
   107  // data to be entered, but one which must not be echoed out into the terminal.
   108  // The method returns the input provided by the user.
   109  func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err error) {
   110  	if p.supported {
   111  		p.rawMode.ApplyMode()
   112  		defer p.normalMode.ApplyMode()
   113  		return p.State.PasswordPrompt(prompt)
   114  	}
   115  	if !p.warned {
   116  		fmt.Println("!! Unsupported terminal, password will be echoed.")
   117  		p.warned = true
   118  	}
   119  	// Just as in Prompt, handle printing the prompt here instead of relying on liner.
   120  	fmt.Print(prompt)
   121  	passwd, err = p.State.Prompt("")
   122  	fmt.Println()
   123  	return passwd, err
   124  }
   125  
   126  // PromptConfirm displays the given prompt to the user and requests a boolean
   127  // choice to be made, returning that choice.
   128  func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) {
   129  	input, err := p.Prompt(prompt + " [y/N] ")
   130  	if len(input) > 0 && strings.ToUpper(input[:1]) == "Y" {
   131  		return true, nil
   132  	}
   133  	return false, err
   134  }
   135  
   136  // SetHistory sets the the input scrollback history that the prompter will allow
   137  // the user to scroll back to.
   138  func (p *terminalPrompter) SetHistory(history []string) {
   139  	p.State.ReadHistory(strings.NewReader(strings.Join(history, "\n")))
   140  }
   141  
   142  // AppendHistory appends an entry to the scrollback history.
   143  func (p *terminalPrompter) AppendHistory(command string) {
   144  	p.State.AppendHistory(command)
   145  }
   146  
   147  // ClearHistory clears the entire history
   148  func (p *terminalPrompter) ClearHistory() {
   149  	p.State.ClearHistory()
   150  }
   151  
   152  // SetWordCompleter sets the completion function that the prompter will call to
   153  // fetch completion candidates when the user presses tab.
   154  func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) {
   155  	p.State.SetWordCompleter(liner.WordCompleter(completer))
   156  }