github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/console/prompter.go (about)

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