github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/flexibletable/cells.go (about) 1 // Copyright 2016 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package flexibletable 5 6 import ( 7 "math" 8 "strconv" 9 "strings" 10 ) 11 12 // Alignment defines how the content of a cell should align 13 type Alignment int 14 15 // Possible Alignment values 16 const ( 17 Left Alignment = iota // default 18 Right 19 Center 20 ) 21 22 // Cell defines a cell (column) in a row 23 type Cell struct { 24 // Content is the content of the cell. It can be either a SingleCell or a 25 // MultiCell 26 Content cellContent 27 28 // Alignment specifies how the content of a cell should align 29 Alignment Alignment 30 31 // Frame defines a "frame" around the content. The array defines two strings, 32 // which are added before and after the content, before any paddings are 33 // inserted. 34 Frame [2]string 35 } 36 37 func (c Cell) full() string { 38 return c.Content.full() 39 } 40 41 func (c Cell) render(widthConstraint int) (string, error) { 42 frameWidth := len(c.Frame[0]) + len(c.Frame[1]) 43 minWidth := c.Content.minWidth() + frameWidth 44 if minWidth > widthConstraint { 45 return "", WidthTooSmallError{} 46 } 47 return c.Frame[0] + c.Content.render(widthConstraint-frameWidth) + c.Frame[1], nil 48 } 49 50 func (c Cell) renderWithPadding(width int) (string, error) { 51 str, err := c.render(width) 52 if err != nil { 53 return "", err 54 } 55 return c.addPadding(str, width) 56 } 57 58 func (c Cell) addPadding(str string, width int) (string, error) { 59 padding := width - len(str) 60 if padding == 0 { 61 return str, nil 62 } 63 switch c.Alignment { 64 case Left: 65 return str + strings.Repeat(" ", padding), nil 66 case Right: 67 return strings.Repeat(" ", padding) + str, nil 68 case Center: 69 return strings.Repeat(" ", padding/2) + str + strings.Repeat(" ", padding-padding/2), nil 70 default: 71 return "", BadOptionError{optionName: "Alignment"} 72 } 73 } 74 75 type cellContent interface { 76 render(maxWidth int) string 77 minWidth() int 78 full() string 79 } 80 81 // emptyCell is an implementation of cellContent that is only used internally 82 // in flexibletable package during rendering. 83 type emptyCell struct{} 84 85 func (emptyCell) render(int) string { return "" } 86 func (emptyCell) minWidth() int { return 0 } 87 func (emptyCell) full() string { return "" } 88 89 // SingleCell defines cell content with a single string. If being truncated, 90 // the truncated part is replaced with "..." 91 type SingleCell struct { 92 // Item is, well, the content. 93 Item string 94 } 95 96 func (c SingleCell) full() string { 97 return c.Item 98 } 99 100 func (c SingleCell) render(maxWidth int) string { 101 if len(c.Item) <= maxWidth { 102 return c.Item 103 } 104 return c.Item[:maxWidth-3] + "..." 105 } 106 107 func (c SingleCell) minWidth() int { 108 if len(c.Item) < 3 { 109 return len(c.Item) 110 } 111 return 3 // "..." 112 } 113 114 // MultiCell defines cell content with multiple strings. If being truncated, it 115 // looks like this: "item1,item2,+4..." 116 type MultiCell struct { 117 // Sep is the separator between different items 118 Sep string 119 // Items are the content 120 Items []string 121 } 122 123 func (c MultiCell) full() string { 124 return strings.Join(c.Items, c.Sep) 125 } 126 127 func (c MultiCell) render(maxWidth int) (ret string) { 128 retIfFull := "+" + strconv.Itoa(len(c.Items)) + "..." 129 130 for i, item := range c.Items { 131 var plus string 132 if len(ret) > 0 { 133 plus = c.Sep + item 134 } else { 135 plus = item 136 } 137 138 if len(plus)+len(ret) <= maxWidth { 139 ret += plus 140 } else { 141 return retIfFull 142 } 143 144 newRetIfFull := ret + c.Sep + "+" + strconv.Itoa(len(c.Items)-i-1) + "..." 145 if len(newRetIfFull) <= maxWidth { 146 retIfFull = newRetIfFull 147 } 148 } 149 150 return ret 151 } 152 153 func (c MultiCell) minWidth() int { 154 simpleLen := len(strings.Join(c.Items, c.Sep)) 155 digestMin := int(math.Ceil(math.Log10(float64(len(c.Items)+1)))) + 4 // "+9..." 156 if simpleLen < digestMin { 157 return simpleLen 158 } 159 return digestMin 160 }