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 }