mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-19 08:40:42 +00:00
As what suggested by Ginkgo migration guide, `Measure` node was deprecated and replaced with `It` node which creates `gmeasure.Experiment`. Signed-off-by: Dave Chen <dave.chen@arm.com>
371 lines
8.2 KiB
Go
371 lines
8.2 KiB
Go
package table
|
|
|
|
// This is a temporary package - Table will move to github.com/onsi/consolable once some more dust settles
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type AlignType uint
|
|
|
|
const (
|
|
AlignTypeLeft AlignType = iota
|
|
AlignTypeCenter
|
|
AlignTypeRight
|
|
)
|
|
|
|
type Divider string
|
|
|
|
type Row struct {
|
|
Cells []Cell
|
|
Divider string
|
|
Style string
|
|
}
|
|
|
|
func R(args ...interface{}) *Row {
|
|
r := &Row{
|
|
Divider: "-",
|
|
}
|
|
for _, arg := range args {
|
|
switch reflect.TypeOf(arg) {
|
|
case reflect.TypeOf(Divider("")):
|
|
r.Divider = string(arg.(Divider))
|
|
case reflect.TypeOf(r.Style):
|
|
r.Style = arg.(string)
|
|
case reflect.TypeOf(Cell{}):
|
|
r.Cells = append(r.Cells, arg.(Cell))
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *Row) AppendCell(cells ...Cell) *Row {
|
|
r.Cells = append(r.Cells, cells...)
|
|
return r
|
|
}
|
|
|
|
func (r *Row) Render(widths []int, totalWidth int, tableStyle TableStyle, isLastRow bool) string {
|
|
out := ""
|
|
if len(r.Cells) == 1 {
|
|
out += strings.Join(r.Cells[0].render(totalWidth, r.Style, tableStyle), "\n") + "\n"
|
|
} else {
|
|
if len(r.Cells) != len(widths) {
|
|
panic("row vs width mismatch")
|
|
}
|
|
renderedCells := make([][]string, len(r.Cells))
|
|
maxHeight := 0
|
|
for colIdx, cell := range r.Cells {
|
|
renderedCells[colIdx] = cell.render(widths[colIdx], r.Style, tableStyle)
|
|
if len(renderedCells[colIdx]) > maxHeight {
|
|
maxHeight = len(renderedCells[colIdx])
|
|
}
|
|
}
|
|
for colIdx := range r.Cells {
|
|
for len(renderedCells[colIdx]) < maxHeight {
|
|
renderedCells[colIdx] = append(renderedCells[colIdx], strings.Repeat(" ", widths[colIdx]))
|
|
}
|
|
}
|
|
border := strings.Repeat(" ", tableStyle.Padding)
|
|
if tableStyle.VerticalBorders {
|
|
border += "|" + border
|
|
}
|
|
for lineIdx := 0; lineIdx < maxHeight; lineIdx++ {
|
|
for colIdx := range r.Cells {
|
|
out += renderedCells[colIdx][lineIdx]
|
|
if colIdx < len(r.Cells)-1 {
|
|
out += border
|
|
}
|
|
}
|
|
out += "\n"
|
|
}
|
|
}
|
|
if tableStyle.HorizontalBorders && !isLastRow && r.Divider != "" {
|
|
out += strings.Repeat(string(r.Divider), totalWidth) + "\n"
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
type Cell struct {
|
|
Contents []string
|
|
Style string
|
|
Align AlignType
|
|
}
|
|
|
|
func C(contents string, args ...interface{}) Cell {
|
|
c := Cell{
|
|
Contents: strings.Split(contents, "\n"),
|
|
}
|
|
for _, arg := range args {
|
|
switch reflect.TypeOf(arg) {
|
|
case reflect.TypeOf(c.Style):
|
|
c.Style = arg.(string)
|
|
case reflect.TypeOf(c.Align):
|
|
c.Align = arg.(AlignType)
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (c Cell) Width() (int, int) {
|
|
w, minW := 0, 0
|
|
for _, line := range c.Contents {
|
|
lineWidth := utf8.RuneCountInString(line)
|
|
if lineWidth > w {
|
|
w = lineWidth
|
|
}
|
|
for _, word := range strings.Split(line, " ") {
|
|
wordWidth := utf8.RuneCountInString(word)
|
|
if wordWidth > minW {
|
|
minW = wordWidth
|
|
}
|
|
}
|
|
}
|
|
return w, minW
|
|
}
|
|
|
|
func (c Cell) alignLine(line string, width int) string {
|
|
lineWidth := utf8.RuneCountInString(line)
|
|
if lineWidth == width {
|
|
return line
|
|
}
|
|
if lineWidth < width {
|
|
gap := width - lineWidth
|
|
switch c.Align {
|
|
case AlignTypeLeft:
|
|
return line + strings.Repeat(" ", gap)
|
|
case AlignTypeRight:
|
|
return strings.Repeat(" ", gap) + line
|
|
case AlignTypeCenter:
|
|
leftGap := gap / 2
|
|
rightGap := gap - leftGap
|
|
return strings.Repeat(" ", leftGap) + line + strings.Repeat(" ", rightGap)
|
|
}
|
|
}
|
|
return line
|
|
}
|
|
|
|
func (c Cell) splitWordToWidth(word string, width int) []string {
|
|
out := []string{}
|
|
n, subWord := 0, ""
|
|
for _, c := range word {
|
|
subWord += string(c)
|
|
n += 1
|
|
if n == width-1 {
|
|
out = append(out, subWord+"-")
|
|
n, subWord = 0, ""
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (c Cell) splitToWidth(line string, width int) []string {
|
|
lineWidth := utf8.RuneCountInString(line)
|
|
if lineWidth <= width {
|
|
return []string{line}
|
|
}
|
|
|
|
outLines := []string{}
|
|
words := strings.Split(line, " ")
|
|
outWords := []string{words[0]}
|
|
length := utf8.RuneCountInString(words[0])
|
|
if length > width {
|
|
splitWord := c.splitWordToWidth(words[0], width)
|
|
lastIdx := len(splitWord) - 1
|
|
outLines = append(outLines, splitWord[:lastIdx]...)
|
|
outWords = []string{splitWord[lastIdx]}
|
|
length = utf8.RuneCountInString(splitWord[lastIdx])
|
|
}
|
|
|
|
for _, word := range words[1:] {
|
|
wordLength := utf8.RuneCountInString(word)
|
|
if length+wordLength+1 <= width {
|
|
length += wordLength + 1
|
|
outWords = append(outWords, word)
|
|
continue
|
|
}
|
|
outLines = append(outLines, strings.Join(outWords, " "))
|
|
|
|
outWords = []string{word}
|
|
length = wordLength
|
|
if length > width {
|
|
splitWord := c.splitWordToWidth(word, width)
|
|
lastIdx := len(splitWord) - 1
|
|
outLines = append(outLines, splitWord[:lastIdx]...)
|
|
outWords = []string{splitWord[lastIdx]}
|
|
length = utf8.RuneCountInString(splitWord[lastIdx])
|
|
}
|
|
}
|
|
if len(outWords) > 0 {
|
|
outLines = append(outLines, strings.Join(outWords, " "))
|
|
}
|
|
|
|
return outLines
|
|
}
|
|
|
|
func (c Cell) render(width int, style string, tableStyle TableStyle) []string {
|
|
out := []string{}
|
|
for _, line := range c.Contents {
|
|
out = append(out, c.splitToWidth(line, width)...)
|
|
}
|
|
for idx := range out {
|
|
out[idx] = c.alignLine(out[idx], width)
|
|
}
|
|
|
|
if tableStyle.EnableTextStyling {
|
|
style = style + c.Style
|
|
if style != "" {
|
|
for idx := range out {
|
|
out[idx] = style + out[idx] + "{{/}}"
|
|
}
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
type TableStyle struct {
|
|
Padding int
|
|
VerticalBorders bool
|
|
HorizontalBorders bool
|
|
MaxTableWidth int
|
|
MaxColWidth int
|
|
EnableTextStyling bool
|
|
}
|
|
|
|
var DefaultTableStyle = TableStyle{
|
|
Padding: 1,
|
|
VerticalBorders: true,
|
|
HorizontalBorders: true,
|
|
MaxTableWidth: 120,
|
|
MaxColWidth: 40,
|
|
EnableTextStyling: true,
|
|
}
|
|
|
|
type Table struct {
|
|
Rows []*Row
|
|
|
|
TableStyle TableStyle
|
|
}
|
|
|
|
func NewTable() *Table {
|
|
return &Table{
|
|
TableStyle: DefaultTableStyle,
|
|
}
|
|
}
|
|
|
|
func (t *Table) AppendRow(row *Row) *Table {
|
|
t.Rows = append(t.Rows, row)
|
|
return t
|
|
}
|
|
|
|
func (t *Table) Render() string {
|
|
out := ""
|
|
totalWidth, widths := t.computeWidths()
|
|
for rowIdx, row := range t.Rows {
|
|
out += row.Render(widths, totalWidth, t.TableStyle, rowIdx == len(t.Rows)-1)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (t *Table) computeWidths() (int, []int) {
|
|
nCol := 0
|
|
for _, row := range t.Rows {
|
|
if len(row.Cells) > nCol {
|
|
nCol = len(row.Cells)
|
|
}
|
|
}
|
|
|
|
// lets compute the contribution to width from the borders + padding
|
|
borderWidth := t.TableStyle.Padding
|
|
if t.TableStyle.VerticalBorders {
|
|
borderWidth += 1 + t.TableStyle.Padding
|
|
}
|
|
totalBorderWidth := borderWidth * (nCol - 1)
|
|
|
|
// lets compute the width of each column
|
|
widths := make([]int, nCol)
|
|
minWidths := make([]int, nCol)
|
|
for colIdx := range widths {
|
|
for _, row := range t.Rows {
|
|
if colIdx >= len(row.Cells) {
|
|
// ignore rows with fewer columns
|
|
continue
|
|
}
|
|
w, minWid := row.Cells[colIdx].Width()
|
|
if w > widths[colIdx] {
|
|
widths[colIdx] = w
|
|
}
|
|
if minWid > minWidths[colIdx] {
|
|
minWidths[colIdx] = minWid
|
|
}
|
|
}
|
|
}
|
|
|
|
// do we already fit?
|
|
if sum(widths)+totalBorderWidth <= t.TableStyle.MaxTableWidth {
|
|
// yes! we're done
|
|
return sum(widths) + totalBorderWidth, widths
|
|
}
|
|
|
|
// clamp the widths and minWidths to MaxColWidth
|
|
for colIdx := range widths {
|
|
widths[colIdx] = min(widths[colIdx], t.TableStyle.MaxColWidth)
|
|
minWidths[colIdx] = min(minWidths[colIdx], t.TableStyle.MaxColWidth)
|
|
}
|
|
|
|
// do we fit now?
|
|
if sum(widths)+totalBorderWidth <= t.TableStyle.MaxTableWidth {
|
|
// yes! we're done
|
|
return sum(widths) + totalBorderWidth, widths
|
|
}
|
|
|
|
// hmm... still no... can we possibly squeeze the table in without violating minWidths?
|
|
if sum(minWidths)+totalBorderWidth >= t.TableStyle.MaxTableWidth {
|
|
// nope - we're just going to have to exceed MaxTableWidth
|
|
return sum(minWidths) + totalBorderWidth, minWidths
|
|
}
|
|
|
|
// looks like we don't fit yet, but we should be able to fit without violating minWidths
|
|
// lets start scaling down
|
|
n := 0
|
|
for sum(widths)+totalBorderWidth > t.TableStyle.MaxTableWidth {
|
|
budget := t.TableStyle.MaxTableWidth - totalBorderWidth
|
|
baseline := sum(widths)
|
|
|
|
for colIdx := range widths {
|
|
widths[colIdx] = max((widths[colIdx]*budget)/baseline, minWidths[colIdx])
|
|
}
|
|
n += 1
|
|
if n > 100 {
|
|
break // in case we somehow fail to converge
|
|
}
|
|
}
|
|
|
|
return sum(widths) + totalBorderWidth, widths
|
|
}
|
|
|
|
func sum(s []int) int {
|
|
out := 0
|
|
for _, v := range s {
|
|
out += v
|
|
}
|
|
return out
|
|
}
|
|
|
|
func min(a int, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func max(a int, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|