github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/command/ui.go (about) 1 package command 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "io" 9 "log" 10 "os" 11 "os/signal" 12 "strings" 13 "sync" 14 15 "github.com/hashicorp/otto/ui" 16 "github.com/hashicorp/vault/helper/password" 17 "github.com/mitchellh/cli" 18 ) 19 20 var defaultInputReader io.Reader 21 var defaultInputWriter io.Writer 22 23 // NewUi returns a new otto Ui implementation for use around 24 // the given CLI Ui implementation. 25 func NewUi(raw cli.Ui) ui.Ui { 26 return &ui.Styled{ 27 Ui: &cliUi{ 28 CliUi: raw, 29 }, 30 } 31 } 32 33 // cliUi is a wrapper around a cli.Ui that implements the otto.Ui 34 // interface. It is unexported since the NewUi method should be used 35 // instead. 36 type cliUi struct { 37 CliUi cli.Ui 38 39 // Reader and Writer are used for Input 40 Reader io.Reader 41 Writer io.Writer 42 43 interrupted bool 44 l sync.Mutex 45 } 46 47 func (u *cliUi) Header(msg string) { 48 u.CliUi.Output(ui.Colorize(msg)) 49 } 50 51 func (u *cliUi) Message(msg string) { 52 u.CliUi.Output(ui.Colorize(msg)) 53 } 54 55 func (u *cliUi) Raw(msg string) { 56 fmt.Print(msg) 57 } 58 59 func (i *cliUi) Input(opts *ui.InputOpts) (string, error) { 60 // If any of the configured EnvVars are set, we don't ask for input. 61 if value := opts.EnvVarValue(); value != "" { 62 return value, nil 63 } 64 65 r := i.Reader 66 w := i.Writer 67 if r == nil { 68 r = defaultInputReader 69 } 70 if w == nil { 71 w = defaultInputWriter 72 } 73 if r == nil { 74 r = os.Stdin 75 } 76 if w == nil { 77 w = os.Stdout 78 } 79 80 // Make sure we only ask for input once at a time. Terraform 81 // should enforce this, but it doesn't hurt to verify. 82 i.l.Lock() 83 defer i.l.Unlock() 84 85 // If we're interrupted, then don't ask for input 86 if i.interrupted { 87 return "", errors.New("interrupted") 88 } 89 90 // Listen for interrupts so we can cancel the input ask 91 sigCh := make(chan os.Signal, 1) 92 signal.Notify(sigCh, os.Interrupt) 93 defer signal.Stop(sigCh) 94 95 // Build the output format for asking 96 var buf bytes.Buffer 97 buf.WriteString("[reset]") 98 buf.WriteString(fmt.Sprintf("[bold]%s[reset]\n", opts.Query)) 99 if opts.Description != "" { 100 s := bufio.NewScanner(strings.NewReader(opts.Description)) 101 for s.Scan() { 102 buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) 103 } 104 buf.WriteString("\n") 105 } 106 if opts.Default != "" { 107 buf.WriteString(" [bold]Default:[reset] ") 108 buf.WriteString(opts.Default) 109 buf.WriteString("\n") 110 } 111 buf.WriteString(" [bold]Enter a value:[reset] ") 112 113 // Ask the user for their input 114 if _, err := fmt.Fprint(w, ui.Colorize(buf.String())); err != nil { 115 return "", err 116 } 117 118 // Listen for the input in a goroutine. This will allow us to 119 // interrupt this if we are interrupted (SIGINT) 120 result := make(chan string, 1) 121 if opts.Hide { 122 f, ok := r.(*os.File) 123 if !ok { 124 return "", fmt.Errorf("reader must be a file") 125 } 126 127 line, err := password.Read(f) 128 if err != nil { 129 return "", err 130 } 131 132 result <- line 133 } else { 134 go func() { 135 var line string 136 if _, err := fmt.Fscanln(r, &line); err != nil { 137 log.Printf("[ERR] UIInput scan err: %s", err) 138 } 139 140 result <- line 141 }() 142 } 143 144 select { 145 case line := <-result: 146 fmt.Fprint(w, "\n") 147 148 if line == "" { 149 line = opts.Default 150 } 151 152 return line, nil 153 case <-sigCh: 154 // Print a newline so that any further output starts properly 155 // on a new line. 156 fmt.Fprintln(w) 157 158 // Mark that we were interrupted so future Ask calls fail. 159 i.interrupted = true 160 161 return "", errors.New("interrupted") 162 } 163 }