storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/console/console.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package console implements console printing helpers 18 package console 19 20 import ( 21 "fmt" 22 "os" 23 "path/filepath" 24 "strings" 25 "sync" 26 27 "github.com/fatih/color" 28 "github.com/mattn/go-colorable" 29 "github.com/mattn/go-isatty" 30 ) 31 32 var ( 33 // Used by the caller to print multiple lines atomically. Exposed by Lock/Unlock methods. 34 publicMutex sync.Mutex 35 36 // Used internally by console. 37 privateMutex sync.Mutex 38 39 stderrColoredOutput = colorable.NewColorableStderr() 40 41 // Print prints a message. 42 Print = func(data ...interface{}) { 43 consolePrint("Print", Theme["Print"], data...) 44 } 45 46 // PrintC prints a message with color. 47 PrintC = func(data ...interface{}) { 48 consolePrint("PrintC", Theme["PrintC"], data...) 49 } 50 51 // Printf prints a formatted message. 52 Printf = func(format string, data ...interface{}) { 53 consolePrintf("Print", Theme["Print"], format, data...) 54 } 55 56 // Println prints a message with a newline. 57 Println = func(data ...interface{}) { 58 consolePrintln("Print", Theme["Print"], data...) 59 } 60 61 // Fatal print a error message and exit. 62 Fatal = func(data ...interface{}) { 63 consolePrint("Fatal", Theme["Fatal"], data...) 64 os.Exit(1) 65 } 66 67 // Fatalf print a error message with a format specified and exit. 68 Fatalf = func(format string, data ...interface{}) { 69 consolePrintf("Fatal", Theme["Fatal"], format, data...) 70 os.Exit(1) 71 } 72 73 // Fatalln print a error message with a new line and exit. 74 Fatalln = func(data ...interface{}) { 75 consolePrintln("Fatal", Theme["Fatal"], data...) 76 os.Exit(1) 77 } 78 79 // Error prints a error message. 80 Error = func(data ...interface{}) { 81 consolePrint("Error", Theme["Error"], data...) 82 } 83 84 // Errorf print a error message with a format specified. 85 Errorf = func(format string, data ...interface{}) { 86 consolePrintf("Error", Theme["Error"], format, data...) 87 } 88 89 // Errorln prints a error message with a new line. 90 Errorln = func(data ...interface{}) { 91 consolePrintln("Error", Theme["Error"], data...) 92 } 93 94 // Info prints a informational message. 95 Info = func(data ...interface{}) { 96 consolePrint("Info", Theme["Info"], data...) 97 } 98 99 // Infof prints a informational message in custom format. 100 Infof = func(format string, data ...interface{}) { 101 consolePrintf("Info", Theme["Info"], format, data...) 102 } 103 104 // Infoln prints a informational message with a new line. 105 Infoln = func(data ...interface{}) { 106 consolePrintln("Info", Theme["Info"], data...) 107 } 108 109 // Debug prints a debug message without a new line 110 // Debug prints a debug message. 111 Debug = func(data ...interface{}) { 112 consolePrint("Debug", Theme["Debug"], data...) 113 } 114 115 // Debugf prints a debug message with a new line. 116 Debugf = func(format string, data ...interface{}) { 117 consolePrintf("Debug", Theme["Debug"], format, data...) 118 } 119 120 // Debugln prints a debug message with a new line. 121 Debugln = func(data ...interface{}) { 122 consolePrintln("Debug", Theme["Debug"], data...) 123 } 124 125 // Colorize prints message in a colorized form, dictated by the corresponding tag argument. 126 Colorize = func(tag string, data interface{}) string { 127 if isatty.IsTerminal(os.Stdout.Fd()) { 128 colorized, ok := Theme[tag] 129 if ok { 130 return colorized.SprintFunc()(data) 131 } // else: No theme found. Return as string. 132 } 133 return fmt.Sprint(data) 134 } 135 136 // Eraseline Print in new line and adjust to top so that we don't print over the ongoing progress bar. 137 Eraseline = func() { 138 consolePrintf("Print", Theme["Print"], "%c[2K\n", 27) 139 consolePrintf("Print", Theme["Print"], "%c[A", 27) 140 } 141 ) 142 143 // wrap around standard fmt functions. 144 // consolePrint prints a message prefixed with message type and program name. 145 func consolePrint(tag string, c *color.Color, a ...interface{}) { 146 privateMutex.Lock() 147 defer privateMutex.Unlock() 148 149 switch tag { 150 case "Debug": 151 // if no arguments are given do not invoke debug printer. 152 if len(a) == 0 { 153 return 154 } 155 output := color.Output 156 color.Output = stderrColoredOutput 157 if isatty.IsTerminal(os.Stderr.Fd()) { 158 c.Print(ProgramName() + ": <DEBUG> ") 159 c.Print(a...) 160 } else { 161 fmt.Fprint(color.Output, ProgramName()+": <DEBUG> ") 162 fmt.Fprint(color.Output, a...) 163 } 164 color.Output = output 165 case "Fatal": 166 fallthrough 167 case "Error": 168 // if no arguments are given do not invoke fatal and error printer. 169 if len(a) == 0 { 170 return 171 } 172 output := color.Output 173 color.Output = stderrColoredOutput 174 if isatty.IsTerminal(os.Stderr.Fd()) { 175 c.Print(ProgramName() + ": <ERROR> ") 176 c.Print(a...) 177 } else { 178 fmt.Fprint(color.Output, ProgramName()+": <ERROR> ") 179 fmt.Fprint(color.Output, a...) 180 } 181 color.Output = output 182 case "Info": 183 // if no arguments are given do not invoke info printer. 184 if len(a) == 0 { 185 return 186 } 187 if isatty.IsTerminal(os.Stdout.Fd()) { 188 c.Print(ProgramName() + ": ") 189 c.Print(a...) 190 } else { 191 fmt.Fprint(color.Output, ProgramName()+": ") 192 fmt.Fprint(color.Output, a...) 193 } 194 default: 195 if isatty.IsTerminal(os.Stdout.Fd()) { 196 c.Print(a...) 197 } else { 198 fmt.Fprint(color.Output, a...) 199 } 200 } 201 } 202 203 // consolePrintf - same as print with a new line. 204 func consolePrintf(tag string, c *color.Color, format string, a ...interface{}) { 205 privateMutex.Lock() 206 defer privateMutex.Unlock() 207 208 switch tag { 209 case "Debug": 210 // if no arguments are given do not invoke debug printer. 211 if len(a) == 0 { 212 return 213 } 214 output := color.Output 215 color.Output = stderrColoredOutput 216 if isatty.IsTerminal(os.Stderr.Fd()) { 217 c.Print(ProgramName() + ": <DEBUG> ") 218 c.Printf(format, a...) 219 } else { 220 fmt.Fprint(color.Output, ProgramName()+": <DEBUG> ") 221 fmt.Fprintf(color.Output, format, a...) 222 } 223 color.Output = output 224 case "Fatal": 225 fallthrough 226 case "Error": 227 // if no arguments are given do not invoke fatal and error printer. 228 if len(a) == 0 { 229 return 230 } 231 output := color.Output 232 color.Output = stderrColoredOutput 233 if isatty.IsTerminal(os.Stderr.Fd()) { 234 c.Print(ProgramName() + ": <ERROR> ") 235 c.Printf(format, a...) 236 } else { 237 fmt.Fprint(color.Output, ProgramName()+": <ERROR> ") 238 fmt.Fprintf(color.Output, format, a...) 239 } 240 color.Output = output 241 case "Info": 242 // if no arguments are given do not invoke info printer. 243 if len(a) == 0 { 244 return 245 } 246 if isatty.IsTerminal(os.Stdout.Fd()) { 247 c.Print(ProgramName() + ": ") 248 c.Printf(format, a...) 249 } else { 250 fmt.Fprint(color.Output, ProgramName()+": ") 251 fmt.Fprintf(color.Output, format, a...) 252 } 253 default: 254 if isatty.IsTerminal(os.Stdout.Fd()) { 255 c.Printf(format, a...) 256 } else { 257 fmt.Fprintf(color.Output, format, a...) 258 } 259 } 260 } 261 262 // consolePrintln - same as print with a new line. 263 func consolePrintln(tag string, c *color.Color, a ...interface{}) { 264 privateMutex.Lock() 265 defer privateMutex.Unlock() 266 267 switch tag { 268 case "Debug": 269 // if no arguments are given do not invoke debug printer. 270 if len(a) == 0 { 271 return 272 } 273 output := color.Output 274 color.Output = stderrColoredOutput 275 if isatty.IsTerminal(os.Stderr.Fd()) { 276 c.Print(ProgramName() + ": <DEBUG> ") 277 c.Println(a...) 278 } else { 279 fmt.Fprint(color.Output, ProgramName()+": <DEBUG> ") 280 fmt.Fprintln(color.Output, a...) 281 } 282 color.Output = output 283 case "Fatal": 284 fallthrough 285 case "Error": 286 // if no arguments are given do not invoke fatal and error printer. 287 if len(a) == 0 { 288 return 289 } 290 output := color.Output 291 color.Output = stderrColoredOutput 292 if isatty.IsTerminal(os.Stderr.Fd()) { 293 c.Print(ProgramName() + ": <ERROR> ") 294 c.Println(a...) 295 } else { 296 fmt.Fprint(color.Output, ProgramName()+": <ERROR> ") 297 fmt.Fprintln(color.Output, a...) 298 } 299 color.Output = output 300 case "Info": 301 // if no arguments are given do not invoke info printer. 302 if len(a) == 0 { 303 return 304 } 305 if isatty.IsTerminal(os.Stdout.Fd()) { 306 c.Print(ProgramName() + ": ") 307 c.Println(a...) 308 } else { 309 fmt.Fprint(color.Output, ProgramName()+": ") 310 fmt.Fprintln(color.Output, a...) 311 } 312 default: 313 if isatty.IsTerminal(os.Stdout.Fd()) { 314 c.Println(a...) 315 } else { 316 fmt.Fprintln(color.Output, a...) 317 } 318 } 319 } 320 321 // Lock console. 322 func Lock() { 323 publicMutex.Lock() 324 } 325 326 // Unlock locked console. 327 func Unlock() { 328 publicMutex.Unlock() 329 } 330 331 // ProgramName - return the name of the executable program. 332 func ProgramName() string { 333 _, progName := filepath.Split(os.Args[0]) 334 return progName 335 } 336 337 // Table - data to print in table format with fixed row widths. 338 type Table struct { 339 // per-row colors 340 RowColors []*color.Color 341 342 // per-column align-right flag (aligns left by default) 343 AlignRight []bool 344 345 // Left margin width for table 346 TableIndentWidth int 347 348 // Flag to print separator under heading. Row 0 is considered heading 349 HeaderRowSeparator bool 350 } 351 352 // NewTable - create a new Table instance. Takes per-row colors and 353 // per-column right-align flags and table indentation width (i.e. left 354 // margin width) 355 func NewTable(rowColors []*color.Color, alignRight []bool, indentWidth int) *Table { 356 return &Table{rowColors, alignRight, indentWidth, false} 357 } 358 359 // DisplayTable - prints the table 360 func (t *Table) DisplayTable(rows [][]string) error { 361 numRows := len(rows) 362 numCols := len(rows[0]) 363 if numRows != len(t.RowColors) { 364 return fmt.Errorf("row count and row-colors mismatch") 365 } 366 367 // Compute max. column widths 368 maxColWidths := make([]int, numCols) 369 for _, row := range rows { 370 if len(row) != len(t.AlignRight) { 371 return fmt.Errorf("col count and align-right mismatch") 372 } 373 for i, v := range row { 374 if len([]rune(v)) > maxColWidths[i] { 375 maxColWidths[i] = len([]rune(v)) 376 } 377 } 378 } 379 380 // Compute per-cell text with padding and alignment applied. 381 paddedText := make([][]string, numRows) 382 for r, row := range rows { 383 paddedText[r] = make([]string, numCols) 384 for c, cell := range row { 385 if t.AlignRight[c] { 386 fmtStr := fmt.Sprintf("%%%ds", maxColWidths[c]) 387 paddedText[r][c] = fmt.Sprintf(fmtStr, cell) 388 } else { 389 extraWidth := maxColWidths[c] - len([]rune(cell)) 390 fmtStr := fmt.Sprintf("%%s%%%ds", extraWidth) 391 paddedText[r][c] = fmt.Sprintf(fmtStr, cell, "") 392 } 393 } 394 } 395 396 // Draw table top border 397 segments := make([]string, numCols) 398 for i, c := range maxColWidths { 399 segments[i] = strings.Repeat("─", c+2) 400 } 401 indentText := strings.Repeat(" ", t.TableIndentWidth) 402 border := fmt.Sprintf("%s┌%s┐", indentText, strings.Join(segments, "┬")) 403 fmt.Println(border) 404 405 // Print the table with colors 406 for r, row := range paddedText { 407 if t.HeaderRowSeparator && r == 1 { 408 // Draw table header-row border 409 border = fmt.Sprintf("%s├%s┤", indentText, strings.Join(segments, "┼")) 410 fmt.Println(border) 411 } 412 fmt.Print(indentText + "│ ") 413 for c, text := range row { 414 t.RowColors[r].Print(text) 415 if c != numCols-1 { 416 fmt.Print(" │ ") 417 } 418 } 419 fmt.Println(" │") 420 } 421 422 // Draw table bottom border 423 border = fmt.Sprintf("%s└%s┘", indentText, strings.Join(segments, "┴")) 424 fmt.Println(border) 425 426 return nil 427 } 428 429 // RewindLines - uses terminal escape symbols to clear and rewind 430 // upwards on the console for `n` lines. 431 func RewindLines(n int) { 432 for i := 0; i < n; i++ { 433 fmt.Printf("\033[1A\033[K") 434 } 435 }