diff --git a/cmd/vendor/github.com/olekukonko/tablewriter/table.go b/cmd/vendor/github.com/olekukonko/tablewriter/table.go index d2db5190997..3314bfba5cf 100644 --- a/cmd/vendor/github.com/olekukonko/tablewriter/table.go +++ b/cmd/vendor/github.com/olekukonko/tablewriter/table.go @@ -9,6 +9,7 @@ package tablewriter import ( + "bytes" "fmt" "io" "regexp" @@ -20,45 +21,57 @@ const ( ) const ( - CENTRE = "+" - ROW = "-" - COLUMN = "|" - SPACE = " " + CENTER = "+" + ROW = "-" + COLUMN = "|" + SPACE = " " + NEWLINE = "\n" ) const ( ALIGN_DEFAULT = iota - ALIGN_CENTRE + ALIGN_CENTER ALIGN_RIGHT ALIGN_LEFT ) var ( - decimal = regexp.MustCompile(`^\d*\.?\d*$`) - percent = regexp.MustCompile(`^\d*\.?\d*$%$`) + decimal = regexp.MustCompile(`^-*\d*\.?\d*$`) + percent = regexp.MustCompile(`^-*\d*\.?\d*$%$`) ) +type Border struct { + Left bool + Right bool + Top bool + Bottom bool +} + type Table struct { - out io.Writer - rows [][]string - lines [][][]string - cs map[int]int - rs map[int]int - headers []string - footers []string - autoFmt bool - autoWrap bool - mW int - pCenter string - pRow string - pColumn string - tColumn int - tRow int - align int - rowLine bool - hdrLine bool - border bool - colSize int + out io.Writer + rows [][]string + lines [][][]string + cs map[int]int + rs map[int]int + headers []string + footers []string + autoFmt bool + autoWrap bool + mW int + pCenter string + pRow string + pColumn string + tColumn int + tRow int + hAlign int + fAlign int + align int + newLine string + rowLine bool + autoMergeCells bool + hdrLine bool + borders Border + colSize int } // Start New Table @@ -75,28 +88,35 @@ func NewWriter(writer io.Writer) *Table { autoFmt: true, autoWrap: true, mW: MAX_ROW_WIDTH, - pCenter: CENTRE, + pCenter: CENTER, pRow: ROW, pColumn: COLUMN, tColumn: -1, tRow: -1, + hAlign: ALIGN_DEFAULT, + fAlign: ALIGN_DEFAULT, align: ALIGN_DEFAULT, + newLine: NEWLINE, rowLine: false, hdrLine: true, - border: true, + borders: Border{Left: true, Right: true, Bottom: true, Top: true}, colSize: -1} return t } // Render table output func (t Table) Render() { - if t.border { + if t.borders.Top { t.printLine(true) } t.printHeading() - t.printRows() + if t.autoMergeCells { + t.printRowsMergeCells() + } else { + t.printRows() + } - if !t.rowLine && t.border { + if !t.rowLine && t.borders.Bottom { t.printLine(true) } t.printFooter() @@ -151,11 +171,26 @@ func (t *Table) SetCenterSeparator(sep string) { t.pCenter = sep } +// Set Header Alignment +func (t *Table) SetHeaderAlignment(hAlign int) { + t.hAlign = hAlign +} + +// Set Footer Alignment +func (t *Table) SetFooterAlignment(fAlign int) { + t.fAlign = fAlign +} + // Set Table Alignment func (t *Table) SetAlignment(align int) { t.align = align } +// Set New Line +func (t *Table) SetNewLine(nl string) { + t.newLine = nl +} + // Set Header Line // This would enable / disable a line after the header func (t *Table) SetHeaderLine(line bool) { @@ -168,10 +203,20 @@ func (t *Table) SetRowLine(line bool) { t.rowLine = line } +// Set Auto Merge Cells +// This would enable / disable the merge of cells with identical values +func (t *Table) SetAutoMergeCells(auto bool) { + t.autoMergeCells = auto +} + // Set Table Border // This would enable / disable line around the table func (t *Table) SetBorder(border bool) { - t.border = border + t.SetBorders(Border{border, border, border, border}) +} + +func (t *Table) SetBorders(border Border) { + t.borders = border } // Append row to table @@ -216,8 +261,45 @@ func (t Table) printLine(nl bool) { t.pCenter) } if nl { - fmt.Fprintln(t.out) + fmt.Fprint(t.out, t.newLine) + } +} + +// Print line based on row width with our without cell separator +func (t Table) printLineOptionalCellSeparators(nl bool, displayCellSeparator []bool) { + fmt.Fprint(t.out, t.pCenter) + for i := 0; i < len(t.cs); i++ { + v := t.cs[i] + if i > len(displayCellSeparator) || displayCellSeparator[i] { + // Display the cell separator + fmt.Fprintf(t.out, "%s%s%s%s", + t.pRow, + strings.Repeat(string(t.pRow), v), + t.pRow, + t.pCenter) + } else { + // Don't display the cell separator for this cell + fmt.Fprintf(t.out, "%s%s", + strings.Repeat(" ", v+2), + t.pCenter) + } + } + if nl { + fmt.Fprint(t.out, t.newLine) + } +} + +// Return the PadRight function if align is left, PadLeft if align is right, +// and Pad by default +func pad(align int) func(string, string, int) string { + padFunc := Pad + switch align { + case ALIGN_LEFT: + padFunc = PadRight + case ALIGN_RIGHT: + padFunc = PadLeft } + return padFunc } // Print heading information @@ -229,11 +311,14 @@ func (t Table) printHeading() { // Check if border is set // Replace with space if not set - fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE)) + fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE)) // Identify last column end := len(t.cs) - 1 + // Get pad function + padFunc := pad(t.hAlign) + // Print Heading column for i := 0; i <= end; i++ { v := t.cs[i] @@ -241,13 +326,13 @@ func (t Table) printHeading() { if t.autoFmt { h = Title(h) } - pad := ConditionString((i == end && !t.border), SPACE, t.pColumn) + pad := ConditionString((i == end && !t.borders.Left), SPACE, t.pColumn) fmt.Fprintf(t.out, " %s %s", - Pad(h, SPACE, v), + padFunc(h, SPACE, v), pad) } // Next line - fmt.Fprintln(t.out) + fmt.Fprint(t.out, t.newLine) if t.hdrLine { t.printLine(true) } @@ -261,16 +346,19 @@ func (t Table) printFooter() { } // Only print line if border is not set - if !t.border { + if !t.borders.Bottom { t.printLine(true) } // Check if border is set // Replace with space if not set - fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE)) + fmt.Fprint(t.out, ConditionString(t.borders.Bottom, t.pColumn, SPACE)) // Identify last column end := len(t.cs) - 1 + // Get pad function + padFunc := pad(t.fAlign) + // Print Heading column for i := 0; i <= end; i++ { v := t.cs[i] @@ -278,17 +366,17 @@ func (t Table) printFooter() { if t.autoFmt { f = Title(f) } - pad := ConditionString((i == end && !t.border), SPACE, t.pColumn) + pad := ConditionString((i == end && !t.borders.Top), SPACE, t.pColumn) if len(t.footers[i]) == 0 { pad = SPACE } fmt.Fprintf(t.out, " %s %s", - Pad(f, SPACE, v), + padFunc(f, SPACE, v), pad) } // Next line - fmt.Fprintln(t.out) + fmt.Fprint(t.out, t.newLine) //t.printLine(true) hasPrinted := false @@ -304,7 +392,7 @@ func (t Table) printFooter() { } // Set center to be space if length is 0 - if length == 0 && !t.border { + if length == 0 && !t.borders.Right { center = SPACE } @@ -318,7 +406,7 @@ func (t Table) printFooter() { pad = SPACE } // Ignore left space of it has printed before - if hasPrinted || t.border { + if hasPrinted || t.borders.Left { pad = t.pRow center = t.pCenter } @@ -339,7 +427,7 @@ func (t Table) printFooter() { } - fmt.Fprintln(t.out) + fmt.Fprint(t.out, t.newLine) } @@ -383,7 +471,7 @@ func (t Table) printRow(columns [][]string, colKey int) { for y := 0; y < total; y++ { // Check if border is set - fmt.Fprint(t.out, ConditionString((!t.border && y == 0), SPACE, t.pColumn)) + fmt.Fprint(t.out, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn)) fmt.Fprintf(t.out, SPACE) str := columns[y][x] @@ -391,7 +479,7 @@ func (t Table) printRow(columns [][]string, colKey int) { // This would print alignment // Default alignment would use multiple configuration switch t.align { - case ALIGN_CENTRE: // + case ALIGN_CENTER: // fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y])) case ALIGN_RIGHT: fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y])) @@ -416,14 +504,111 @@ func (t Table) printRow(columns [][]string, colKey int) { } // Check if border is set // Replace with space if not set - fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE)) - fmt.Fprintln(t.out) + fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE)) + fmt.Fprint(t.out, t.newLine) } if t.rowLine { t.printLine(true) } +} + +// Print the rows of the table and merge the cells that are identical +func (t Table) printRowsMergeCells() { + var previousLine []string + var displayCellBorder []bool + var tmpWriter bytes.Buffer + for i, lines := range t.lines { + // We store the display of the current line in a tmp writer, as we need to know which border needs to be print above + previousLine, displayCellBorder = t.printRowMergeCells(&tmpWriter, lines, i, previousLine) + if i > 0 { //We don't need to print borders above first line + if t.rowLine { + t.printLineOptionalCellSeparators(true, displayCellBorder) + } + } + tmpWriter.WriteTo(t.out) + } + //Print the end of the table + if t.rowLine { + t.printLine(true) + } +} + +// Print Row Information to a writer and merge identical cells. +// Adjust column alignment based on type + +func (t Table) printRowMergeCells(writer io.Writer, columns [][]string, colKey int, previousLine []string) ([]string, []bool) { + // Get Maximum Height + max := t.rs[colKey] + total := len(columns) + + // Pad Each Height + pads := []int{} + + for i, line := range columns { + length := len(line) + pad := max - length + pads = append(pads, pad) + for n := 0; n < pad; n++ { + columns[i] = append(columns[i], " ") + } + } + + var displayCellBorder []bool + for x := 0; x < max; x++ { + for y := 0; y < total; y++ { + + // Check if border is set + fmt.Fprint(writer, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn)) + + fmt.Fprintf(writer, SPACE) + + str := columns[y][x] + + if t.autoMergeCells { + //Store the full line to merge mutli-lines cells + fullLine := strings.Join(columns[y], " ") + if len(previousLine) > y && fullLine == previousLine[y] && fullLine != "" { + // If this cell is identical to the one above but not empty, we don't display the border and keep the cell empty. + displayCellBorder = append(displayCellBorder, false) + str = "" + } else { + // First line or different content, keep the content and print the cell border + displayCellBorder = append(displayCellBorder, true) + } + } + + // This would print alignment + // Default alignment would use multiple configuration + switch t.align { + case ALIGN_CENTER: // + fmt.Fprintf(writer, "%s", Pad(str, SPACE, t.cs[y])) + case ALIGN_RIGHT: + fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y])) + case ALIGN_LEFT: + fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y])) + default: + if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) { + fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y])) + } else { + fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y])) + } + } + fmt.Fprintf(writer, SPACE) + } + // Check if border is set + // Replace with space if not set + fmt.Fprint(writer, ConditionString(t.borders.Left, t.pColumn, SPACE)) + fmt.Fprint(writer, t.newLine) + } + //The new previous line is the current one + previousLine = make([]string, total) + for y := 0; y < total; y++ { + previousLine[y] = strings.Join(columns[y], " ") //Store the full line for multi-lines cells + } + //Returns the newly added line and wether or not a border should be displayed above. + return previousLine, displayCellBorder } func (t *Table) parseDimension(str string, colKey, rowKey int) []string { diff --git a/cmd/vendor/github.com/olekukonko/tablewriter/wrap.go b/cmd/vendor/github.com/olekukonko/tablewriter/wrap.go index f3747d9f32c..5290fb65a27 100644 --- a/cmd/vendor/github.com/olekukonko/tablewriter/wrap.go +++ b/cmd/vendor/github.com/olekukonko/tablewriter/wrap.go @@ -23,7 +23,7 @@ const defaultPenalty = 1e5 // Wrap wraps s into a paragraph of lines of length lim, with minimal // raggedness. func WrapString(s string, lim int) ([]string, int) { - words := strings.Split(strings.Replace(strings.TrimSpace(s), nl, sp, -1), sp) + words := strings.Split(strings.Replace(s, nl, sp, -1), sp) var lines []string max := 0 for _, v := range words { @@ -96,7 +96,7 @@ func WrapWords(words []string, spc, lim, pen int) [][]string { func getLines(s string) []string { var lines []string - for _, line := range strings.Split(strings.TrimSpace(s), nl) { + for _, line := range strings.Split(s, nl) { lines = append(lines, line) } return lines diff --git a/etcdctl/ctlv3/command/printer_table.go b/etcdctl/ctlv3/command/printer_table.go index 5bcf8763d0c..fb85c5846d3 100644 --- a/etcdctl/ctlv3/command/printer_table.go +++ b/etcdctl/ctlv3/command/printer_table.go @@ -31,6 +31,7 @@ func (tp *tablePrinter) MemberList(r v3.MemberListResponse) { for _, row := range rows { table.Append(row) } + table.SetAlignment(tablewriter.ALIGN_RIGHT) table.Render() } func (tp *tablePrinter) EndpointStatus(r []epStatus) { @@ -40,6 +41,7 @@ func (tp *tablePrinter) EndpointStatus(r []epStatus) { for _, row := range rows { table.Append(row) } + table.SetAlignment(tablewriter.ALIGN_RIGHT) table.Render() } func (tp *tablePrinter) DBStatus(r dbstatus) { @@ -49,5 +51,6 @@ func (tp *tablePrinter) DBStatus(r dbstatus) { for _, row := range rows { table.Append(row) } + table.SetAlignment(tablewriter.ALIGN_RIGHT) table.Render() } diff --git a/glide.lock b/glide.lock index c24990306d5..c9cf1b42c6a 100644 --- a/glide.lock +++ b/glide.lock @@ -68,7 +68,7 @@ imports: subpackages: - pbutil - name: github.com/olekukonko/tablewriter - version: cca8bbc0798408af109aaaa239cbd2634846b340 + version: a0225b3f23b5ce0cbec6d7a66a968f8a59eca9c4 - name: github.com/prometheus/client_golang version: c5b7fccd204277076155f10851dad72b76a49317 subpackages: diff --git a/glide.yaml b/glide.yaml index 7483bef40d6..071c461f4fd 100644 --- a/glide.yaml +++ b/glide.yaml @@ -46,7 +46,7 @@ import: - package: github.com/kr/pty version: f7ee69f31298ecbe5d2b349c711e2547a617d398 - package: github.com/olekukonko/tablewriter - version: cca8bbc0798408af109aaaa239cbd2634846b340 + version: a0225b3f23b5ce0cbec6d7a66a968f8a59eca9c4 - package: github.com/prometheus/client_golang version: v0.8.0 subpackages: