github.com/alex123012/deckhouse-controller-tools@v0.0.0-20230510090815-d594daf1af8c/pkg/genall/help/pretty/print.go (about) 1 package pretty 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 8 "github.com/fatih/color" 9 ) 10 11 // NB(directxman12): this isn't particularly elegant, but it's also 12 // sufficiently simple as to be maintained here. Man (roff) would've 13 // probably worked, but it's not necessarily on Windows by default. 14 15 // Span is a chunk of content that is writable to an output, but knows how to 16 // calculate its apparent visual "width" on the terminal (not to be confused 17 // with the raw length, which may include zero-width coloring sequences). 18 type Span interface { 19 // VisualLength reports the "width" as perceived by the user on the terminal 20 // (i.e. widest line, ignoring ANSI escape characters). 21 VisualLength() int 22 // WriteTo writes the full span contents to the given writer. 23 WriteTo(io.Writer) error 24 } 25 26 // Table is a Span that writes its data in table form, with sizing controlled 27 // by the given table calculator. Rows are started with StartRow, followed by 28 // some calls to Column, followed by a call to EndRow. Once all rows are 29 // added, the table can be used as a Span. 30 type Table struct { 31 Sizing *TableCalculator 32 33 cellsByRow [][]Span 34 colSizes []int 35 } 36 37 // StartRow starts a new row. 38 // It must eventually be followed by EndRow. 39 func (t *Table) StartRow() { 40 t.cellsByRow = append(t.cellsByRow, []Span(nil)) 41 } 42 43 // EndRow ends the currently started row. 44 func (t *Table) EndRow() { 45 lastRow := t.cellsByRow[len(t.cellsByRow)-1] 46 sizes := make([]int, len(lastRow)) 47 for i, cell := range lastRow { 48 sizes[i] = cell.VisualLength() 49 } 50 t.Sizing.AddRowSizes(sizes...) 51 } 52 53 // Column adds the given span as a new column to the current row. 54 func (t *Table) Column(contents Span) { 55 currentRowInd := len(t.cellsByRow) - 1 56 t.cellsByRow[currentRowInd] = append(t.cellsByRow[currentRowInd], contents) 57 } 58 59 // SkipRow prints a span without having it contribute to the table calculation. 60 func (t *Table) SkipRow(contents Span) { 61 t.cellsByRow = append(t.cellsByRow, []Span{contents}) 62 } 63 64 func (t *Table) WriteTo(out io.Writer) error { 65 if t.colSizes == nil { 66 t.colSizes = t.Sizing.ColumnWidths() 67 } 68 69 for _, cells := range t.cellsByRow { 70 currentPosition := 0 71 for colInd, cell := range cells { 72 colSize := t.colSizes[colInd] 73 diff := colSize - cell.VisualLength() 74 75 if err := cell.WriteTo(out); err != nil { 76 return err 77 } 78 79 if diff > 0 { 80 if err := writePadding(out, columnPadding, diff); err != nil { 81 return err 82 } 83 } 84 currentPosition += colSize 85 } 86 87 if _, err := fmt.Fprint(out, "\n"); err != nil { 88 return err 89 } 90 } 91 92 return nil 93 } 94 95 func (t *Table) VisualLength() int { 96 if t.colSizes == nil { 97 t.colSizes = t.Sizing.ColumnWidths() 98 } 99 100 res := 0 101 for _, colSize := range t.colSizes { 102 res += colSize 103 } 104 return res 105 } 106 107 // Text is a span that simply contains raw text. It's a good starting point. 108 type Text string 109 110 func (t Text) VisualLength() int { return len(t) } 111 func (t Text) WriteTo(w io.Writer) error { 112 _, err := w.Write([]byte(t)) 113 return err 114 } 115 116 // indented is a span that indents all lines by the given number of tabs. 117 type indented struct { 118 Amount int 119 Content Span 120 } 121 122 func (i *indented) VisualLength() int { return i.Content.VisualLength() } 123 func (i *indented) WriteTo(w io.Writer) error { 124 var out bytes.Buffer 125 if err := i.Content.WriteTo(&out); err != nil { 126 return err 127 } 128 129 lines := bytes.Split(out.Bytes(), []byte("\n")) 130 for lineInd, line := range lines { 131 if lineInd != 0 { 132 if _, err := w.Write([]byte("\n")); err != nil { 133 return err 134 } 135 } 136 if len(line) == 0 { 137 continue 138 } 139 140 if err := writePadding(w, indentPadding, i.Amount); err != nil { 141 return err 142 } 143 if _, err := w.Write(line); err != nil { 144 return err 145 } 146 } 147 return nil 148 } 149 150 // Indented returns a span that indents all lines by the given number of tabs. 151 func Indented(amt int, content Span) Span { 152 return &indented{Amount: amt, Content: content} 153 } 154 155 // fromWriter is a span that takes content from a function expecting a Writer. 156 type fromWriter struct { 157 cache []byte 158 cacheError error 159 run func(io.Writer) error 160 } 161 162 func (f *fromWriter) VisualLength() int { 163 if f.cache == nil { 164 var buf bytes.Buffer 165 if err := f.run(&buf); err != nil { 166 f.cacheError = err 167 } 168 f.cache = buf.Bytes() 169 } 170 return len(f.cache) 171 } 172 func (f *fromWriter) WriteTo(w io.Writer) error { 173 if f.cache != nil { 174 if f.cacheError != nil { 175 return f.cacheError 176 } 177 _, err := w.Write(f.cache) 178 return err 179 } 180 return f.run(w) 181 } 182 183 // FromWriter returns a span that takes content from a function expecting a Writer. 184 func FromWriter(run func(io.Writer) error) Span { 185 return &fromWriter{run: run} 186 } 187 188 // Decoration represents a terminal decoration. 189 type Decoration color.Color 190 191 // Containing returns a Span that has the given decoration applied. 192 func (d Decoration) Containing(contents Span) Span { 193 return &decorated{ 194 Contents: contents, 195 Attributes: color.Color(d), 196 } 197 } 198 199 // decorated is a span that has some terminal decoration applied. 200 type decorated struct { 201 Contents Span 202 Attributes color.Color 203 } 204 205 func (d *decorated) VisualLength() int { return d.Contents.VisualLength() } 206 func (d *decorated) WriteTo(w io.Writer) error { 207 oldOut := color.Output 208 color.Output = w 209 defer func() { color.Output = oldOut }() 210 211 d.Attributes.Set() 212 defer color.Unset() 213 214 return d.Contents.WriteTo(w) 215 } 216 217 // SpanWriter is a span that contains multiple sub-spans. 218 type SpanWriter struct { 219 contents []Span 220 } 221 222 func (m *SpanWriter) VisualLength() int { 223 res := 0 224 for _, span := range m.contents { 225 res += span.VisualLength() 226 } 227 return res 228 } 229 func (m *SpanWriter) WriteTo(w io.Writer) error { 230 for _, span := range m.contents { 231 if err := span.WriteTo(w); err != nil { 232 return err 233 } 234 } 235 return nil 236 } 237 238 // Print adds a new span to this SpanWriter. 239 func (m *SpanWriter) Print(s Span) { 240 m.contents = append(m.contents, s) 241 } 242 243 // lines is a span that adds some newlines, optionally followed by some content. 244 type lines struct { 245 content Span 246 amountBefore int 247 } 248 249 func (l *lines) VisualLength() int { 250 if l.content == nil { 251 return 0 252 } 253 return l.content.VisualLength() 254 } 255 func (l *lines) WriteTo(w io.Writer) error { 256 if err := writePadding(w, linesPadding, l.amountBefore); err != nil { 257 return err 258 } 259 if l.content != nil { 260 if err := l.content.WriteTo(w); err != nil { 261 return err 262 } 263 } 264 return nil 265 } 266 267 // Newlines returns a span just containing some newlines. 268 func Newlines(amt int) Span { 269 return &lines{amountBefore: amt} 270 } 271 272 // Line returns a span that emits a newline, followed by the given content. 273 func Line(content Span) Span { 274 return &lines{amountBefore: 1, content: content} 275 } 276 277 var ( 278 columnPadding = []byte(" ") 279 indentPadding = []byte("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t") 280 linesPadding = []byte("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n") 281 ) 282 283 // writePadding writes out padding of the given type in the given amount to the writer. 284 // Each byte in the padding buffer contributes 1 to the amount -- the padding being 285 // a buffer is just for efficiency. 286 func writePadding(out io.Writer, typ []byte, amt int) error { 287 if amt <= len(typ) { 288 _, err := out.Write(typ[:amt]) 289 return err 290 } 291 292 num := amt / len(typ) 293 rem := amt % len(typ) 294 for i := 0; i < num; i++ { 295 if _, err := out.Write(typ); err != nil { 296 return err 297 } 298 } 299 300 if _, err := out.Write(typ[:rem]); err != nil { 301 return err 302 } 303 return nil 304 }