github.com/ricardclau/terraform@v0.6.17-0.20160519222547-283e3ae6b5a9/command/ui_input.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/terraform/terraform"
    16  	"github.com/mitchellh/colorstring"
    17  )
    18  
    19  var defaultInputReader io.Reader
    20  var defaultInputWriter io.Writer
    21  
    22  // UIInput is an implementation of terraform.UIInput that asks the CLI
    23  // for input stdin.
    24  type UIInput struct {
    25  	// Colorize will color the output.
    26  	Colorize *colorstring.Colorize
    27  
    28  	// Reader and Writer for IO. If these aren't set, they will default to
    29  	// Stdout and Stderr respectively.
    30  	Reader io.Reader
    31  	Writer io.Writer
    32  
    33  	interrupted bool
    34  	l           sync.Mutex
    35  	once        sync.Once
    36  }
    37  
    38  func (i *UIInput) Input(opts *terraform.InputOpts) (string, error) {
    39  	i.once.Do(i.init)
    40  
    41  	r := i.Reader
    42  	w := i.Writer
    43  	if r == nil {
    44  		r = defaultInputReader
    45  	}
    46  	if w == nil {
    47  		w = defaultInputWriter
    48  	}
    49  	if r == nil {
    50  		r = os.Stdin
    51  	}
    52  	if w == nil {
    53  		w = os.Stdout
    54  	}
    55  
    56  	// Make sure we only ask for input once at a time. Terraform
    57  	// should enforce this, but it doesn't hurt to verify.
    58  	i.l.Lock()
    59  	defer i.l.Unlock()
    60  
    61  	// If we're interrupted, then don't ask for input
    62  	if i.interrupted {
    63  		return "", errors.New("interrupted")
    64  	}
    65  
    66  	// Listen for interrupts so we can cancel the input ask
    67  	sigCh := make(chan os.Signal, 1)
    68  	signal.Notify(sigCh, os.Interrupt)
    69  	defer signal.Stop(sigCh)
    70  
    71  	// Build the output format for asking
    72  	var buf bytes.Buffer
    73  	buf.WriteString("[reset]")
    74  	buf.WriteString(fmt.Sprintf("[bold]%s[reset]\n", opts.Query))
    75  	if opts.Description != "" {
    76  		s := bufio.NewScanner(strings.NewReader(opts.Description))
    77  		for s.Scan() {
    78  			buf.WriteString(fmt.Sprintf("  %s\n", s.Text()))
    79  		}
    80  		buf.WriteString("\n")
    81  	}
    82  	if opts.Default != "" {
    83  		buf.WriteString("  [bold]Default:[reset] ")
    84  		buf.WriteString(opts.Default)
    85  		buf.WriteString("\n")
    86  	}
    87  	buf.WriteString("  [bold]Enter a value:[reset] ")
    88  
    89  	// Ask the user for their input
    90  	if _, err := fmt.Fprint(w, i.Colorize.Color(buf.String())); err != nil {
    91  		return "", err
    92  	}
    93  
    94  	// Listen for the input in a goroutine. This will allow us to
    95  	// interrupt this if we are interrupted (SIGINT)
    96  	result := make(chan string, 1)
    97  	go func() {
    98  		var line string
    99  		if _, err := fmt.Fscanln(r, &line); err != nil {
   100  			log.Printf("[ERR] UIInput scan err: %s", err)
   101  		}
   102  
   103  		result <- line
   104  	}()
   105  
   106  	select {
   107  	case line := <-result:
   108  		fmt.Fprint(w, "\n")
   109  
   110  		if line == "" {
   111  			line = opts.Default
   112  		}
   113  
   114  		return line, nil
   115  	case <-sigCh:
   116  		// Print a newline so that any further output starts properly
   117  		// on a new line.
   118  		fmt.Fprintln(w)
   119  
   120  		// Mark that we were interrupted so future Ask calls fail.
   121  		i.interrupted = true
   122  
   123  		return "", errors.New("interrupted")
   124  	}
   125  }
   126  
   127  func (i *UIInput) init() {
   128  	if i.Colorize == nil {
   129  		i.Colorize = &colorstring.Colorize{
   130  			Colors:  colorstring.DefaultColors,
   131  			Disable: true,
   132  		}
   133  	}
   134  }