github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/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  }