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