github.com/liamawhite/cli-with-i18n@v6.32.1-0.20171122084555-dede0a5c3448+incompatible/cf/terminal/ui.go (about)

     1  package terminal
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"strings"
     7  
     8  	. "github.com/liamawhite/cli-with-i18n/cf/i18n"
     9  
    10  	"bytes"
    11  
    12  	"bufio"
    13  
    14  	"github.com/liamawhite/cli-with-i18n/cf"
    15  	"github.com/liamawhite/cli-with-i18n/cf/configuration/coreconfig"
    16  	"github.com/liamawhite/cli-with-i18n/cf/trace"
    17  )
    18  
    19  type ColoringFunction func(value string, row int, col int) string
    20  
    21  func NotLoggedInText() string {
    22  	return fmt.Sprintf(T("Not logged in. Use '{{.CFLoginCommand}}' to log in.", map[string]interface{}{"CFLoginCommand": CommandColor(cf.Name + " " + "login")}))
    23  }
    24  
    25  //go:generate counterfeiter . UI
    26  type UI interface {
    27  	PrintPaginator(rows []string, err error)
    28  	Say(message string, args ...interface{})
    29  
    30  	// ProgressReader
    31  	PrintCapturingNoOutput(message string, args ...interface{})
    32  	Warn(message string, args ...interface{})
    33  	Ask(prompt string) (answer string)
    34  	AskForPassword(prompt string) (answer string)
    35  	Confirm(message string) bool
    36  	ConfirmDelete(modelType, modelName string) bool
    37  	ConfirmDeleteWithAssociations(modelType, modelName string) bool
    38  	Ok()
    39  	Failed(message string, args ...interface{})
    40  	ShowConfiguration(coreconfig.Reader) error
    41  	LoadingIndication()
    42  	Table(headers []string) *UITable
    43  	NotifyUpdateIfNeeded(coreconfig.Reader)
    44  
    45  	Writer() io.Writer
    46  }
    47  
    48  type Printer interface {
    49  	Print(a ...interface{}) (n int, err error)
    50  	Printf(format string, a ...interface{}) (n int, err error)
    51  	Println(a ...interface{}) (n int, err error)
    52  }
    53  
    54  type terminalUI struct {
    55  	stdin   io.Reader
    56  	stdout  io.Writer
    57  	printer Printer
    58  	logger  trace.Printer
    59  }
    60  
    61  func NewUI(r io.Reader, w io.Writer, printer Printer, logger trace.Printer) UI {
    62  	return &terminalUI{
    63  		stdin:   r,
    64  		stdout:  w,
    65  		printer: printer,
    66  		logger:  logger,
    67  	}
    68  }
    69  
    70  func (ui terminalUI) Writer() io.Writer {
    71  	return ui.stdout
    72  }
    73  
    74  func (ui *terminalUI) PrintPaginator(rows []string, err error) {
    75  	if err != nil {
    76  		ui.Failed(err.Error())
    77  		return
    78  	}
    79  
    80  	for _, row := range rows {
    81  		ui.Say(row)
    82  	}
    83  }
    84  
    85  func (ui *terminalUI) PrintCapturingNoOutput(message string, args ...interface{}) {
    86  	if len(args) == 0 {
    87  		fmt.Fprintf(ui.stdout, "%s", message)
    88  	} else {
    89  		fmt.Fprintf(ui.stdout, message, args...)
    90  	}
    91  }
    92  
    93  func (ui *terminalUI) Say(message string, args ...interface{}) {
    94  	if len(args) == 0 {
    95  		_, _ = ui.printer.Printf("%s\n", message)
    96  	} else {
    97  		_, _ = ui.printer.Printf(message+"\n", args...)
    98  	}
    99  }
   100  
   101  func (ui *terminalUI) Warn(message string, args ...interface{}) {
   102  	message = fmt.Sprintf(message, args...)
   103  	ui.Say(WarningColor(message))
   104  	return
   105  }
   106  
   107  func (ui *terminalUI) Ask(prompt string) string {
   108  	fmt.Fprintf(ui.stdout, "\n%s%s ", prompt, PromptColor(">"))
   109  
   110  	rd := bufio.NewReader(ui.stdin)
   111  	line, err := rd.ReadString('\n')
   112  	if err == nil {
   113  		return strings.TrimSpace(line)
   114  	}
   115  	return ""
   116  }
   117  
   118  func (ui *terminalUI) ConfirmDeleteWithAssociations(modelType, modelName string) bool {
   119  	return ui.confirmDelete(T("Really delete the {{.ModelType}} {{.ModelName}} and everything associated with it?",
   120  		map[string]interface{}{
   121  			"ModelType": modelType,
   122  			"ModelName": EntityNameColor(modelName),
   123  		}))
   124  }
   125  
   126  func (ui *terminalUI) ConfirmDelete(modelType, modelName string) bool {
   127  	return ui.confirmDelete(T("Really delete the {{.ModelType}} {{.ModelName}}?",
   128  		map[string]interface{}{
   129  			"ModelType": modelType,
   130  			"ModelName": EntityNameColor(modelName),
   131  		}))
   132  }
   133  
   134  func (ui *terminalUI) confirmDelete(message string) bool {
   135  	result := ui.Confirm(message)
   136  
   137  	if !result {
   138  		ui.Warn(T("Delete cancelled"))
   139  	}
   140  
   141  	return result
   142  }
   143  
   144  func (ui *terminalUI) Confirm(message string) bool {
   145  	response := ui.Ask(message)
   146  	switch strings.ToLower(response) {
   147  	case "y", "yes", T("yes"):
   148  		return true
   149  	}
   150  	return false
   151  }
   152  
   153  func (ui *terminalUI) Ok() {
   154  	ui.Say(SuccessColor(T("OK")))
   155  }
   156  
   157  func (ui *terminalUI) Failed(message string, args ...interface{}) {
   158  	message = fmt.Sprintf(message, args...)
   159  
   160  	failed := "FAILED"
   161  	if T != nil {
   162  		failed = T("FAILED")
   163  	}
   164  
   165  	ui.logger.Print(failed)
   166  	ui.logger.Print(message)
   167  
   168  	if !ui.logger.WritesToConsole() {
   169  		ui.Say(FailureColor(failed))
   170  		ui.Say(message)
   171  	}
   172  }
   173  
   174  func (ui *terminalUI) ShowConfiguration(config coreconfig.Reader) error {
   175  	var err error
   176  	table := ui.Table([]string{"", ""})
   177  
   178  	if config.HasAPIEndpoint() {
   179  		table.Add(
   180  			T("API endpoint:"),
   181  			T("{{.APIEndpoint}} (API version: {{.APIVersionString}})",
   182  				map[string]interface{}{
   183  					"APIEndpoint":      EntityNameColor(config.APIEndpoint()),
   184  					"APIVersionString": EntityNameColor(config.APIVersion()),
   185  				}),
   186  		)
   187  	}
   188  
   189  	if !config.IsLoggedIn() {
   190  		err = table.Print()
   191  		if err != nil {
   192  			return err
   193  		}
   194  		ui.Say(NotLoggedInText())
   195  		return nil
   196  	}
   197  
   198  	table.Add(T("User:"), EntityNameColor(config.UserEmail()))
   199  
   200  	if !config.HasOrganization() && !config.HasSpace() {
   201  		err = table.Print()
   202  		if err != nil {
   203  			return err
   204  		}
   205  		command := fmt.Sprintf("%s target -o ORG -s SPACE", cf.Name)
   206  		ui.Say(T("No org or space targeted, use '{{.CFTargetCommand}}'",
   207  			map[string]interface{}{
   208  				"CFTargetCommand": CommandColor(command),
   209  			}))
   210  		return nil
   211  	}
   212  
   213  	if config.HasOrganization() {
   214  		table.Add(
   215  			T("Org:"),
   216  			EntityNameColor(config.OrganizationFields().Name),
   217  		)
   218  	} else {
   219  		command := fmt.Sprintf("%s target -o Org", cf.Name)
   220  		table.Add(
   221  			T("Org:"),
   222  			T("No org targeted, use '{{.CFTargetCommand}}'",
   223  				map[string]interface{}{
   224  					"CFTargetCommand": CommandColor(command),
   225  				}),
   226  		)
   227  	}
   228  
   229  	if config.HasSpace() {
   230  		table.Add(
   231  			T("Space:"),
   232  			EntityNameColor(config.SpaceFields().Name),
   233  		)
   234  	} else {
   235  		command := fmt.Sprintf("%s target -s SPACE", cf.Name)
   236  		table.Add(
   237  			T("Space:"),
   238  			T("No space targeted, use '{{.CFTargetCommand}}'", map[string]interface{}{"CFTargetCommand": CommandColor(command)}),
   239  		)
   240  	}
   241  
   242  	err = table.Print()
   243  	if err != nil {
   244  		return err
   245  	}
   246  	return nil
   247  }
   248  
   249  func (ui *terminalUI) LoadingIndication() {
   250  	_, _ = ui.printer.Print(".")
   251  }
   252  
   253  func (ui *terminalUI) Table(headers []string) *UITable {
   254  	return &UITable{
   255  		UI:    ui,
   256  		Table: NewTable(headers),
   257  	}
   258  }
   259  
   260  type UITable struct {
   261  	UI    UI
   262  	Table *Table
   263  }
   264  
   265  func (u *UITable) Add(row ...string) {
   266  	u.Table.Add(row...)
   267  }
   268  
   269  // Print formats the table and then prints it to the UI specified at
   270  // the time of the construction. Afterwards the table is cleared,
   271  // becoming ready for another round of rows and printing.
   272  func (u *UITable) Print() error {
   273  	result := &bytes.Buffer{}
   274  	t := u.Table
   275  
   276  	err := t.PrintTo(result)
   277  	if err != nil {
   278  		return err
   279  	}
   280  
   281  	// DevNote. With the change to printing into a buffer all
   282  	// lines now come with a terminating \n. The t.ui.Say() below
   283  	// will then add another \n to that. To avoid this additional
   284  	// line we chop off the last \n from the output (if there is
   285  	// any). Operating on the slice avoids string copying.
   286  	//
   287  	// WIBNI if the terminal API had a variant of Say not assuming
   288  	// that each output is a single line.
   289  
   290  	r := result.Bytes()
   291  	if len(r) > 0 {
   292  		r = r[0 : len(r)-1]
   293  	}
   294  
   295  	// Only generate output for a non-empty table.
   296  	if len(r) > 0 {
   297  		u.UI.Say("%s", string(r))
   298  	}
   299  	return nil
   300  }
   301  
   302  func (ui *terminalUI) NotifyUpdateIfNeeded(config coreconfig.Reader) {
   303  	if !config.IsMinCLIVersion(config.CLIVersion()) {
   304  		ui.Say("")
   305  		ui.Say(T("Cloud Foundry API version {{.APIVer}} requires CLI version {{.CLIMin}}.  You are currently on version {{.CLIVer}}. To upgrade your CLI, please visit: https://github.com/cloudfoundry/cli#downloads",
   306  			map[string]interface{}{
   307  				"APIVer": config.APIVersion(),
   308  				"CLIMin": config.MinCLIVersion(),
   309  				"CLIVer": config.CLIVersion(),
   310  			}))
   311  	}
   312  }