github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+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 }