github.com/swisscom/cloudfoundry-cli@v7.1.0+incompatible/cf/terminal/ui.go (about)

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