utils/table_writer.go (76 lines of code) (raw):
// Copyright (c) 2017-2018 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"bytes"
"fmt"
)
// TableDataSource defines the interface a data source need to implement so that we can render
// a tabular representation from the data source. We get number of columns from the length of
// column header. the data source itself should ensure that for each get value call with row and
// col within [0,numRows) and [0, numCols), it should return valid value and should not panic.
type TableDataSource interface {
NumRows() int
GetValue(row, col int) interface{}
ColumnHeaders() []string
}
func getFormatModifier(value interface{}) string {
switch value.(type) {
case string:
return "s"
case float32, float64:
return ".2f"
case int8, int16, int32, int64, int, uint8, uint16, uint32, uint64, uint:
return "d"
default:
return "v"
}
}
func expandColumnWidth(columnWidths []int, value interface{}, idx int) {
valueWidth := len(fmt.Sprintf("%"+getFormatModifier(value), value))
if valueWidth > columnWidths[idx] {
columnWidths[idx] = valueWidth
}
}
func sprintfStrings(format string, strs []string) string {
values := make([]interface{}, len(strs))
for i, v := range strs {
values[i] = v
}
return fmt.Sprintf(format, values...)
}
// WriteTable renders a tabular representation from underlying data source.
// If there is no column for this data source, it will return an empty string.
// All elements of the table will be right justify (left padding). Column
// splitter is "|" for now.
func WriteTable(dataSource TableDataSource) string {
columnHeaders := dataSource.ColumnHeaders()
numCols := len(columnHeaders)
// Return empty string if no columns.
if numCols == 0 {
return ""
}
// Compute column widths.
columnWidths := make([]int, numCols)
// Then compare with the length of each value.
numRows := dataSource.NumRows()
for c := 0; c < numCols; c++ {
header := columnHeaders[c]
columnWidths[c] = len(header)
for r := 0; r < numRows; r++ {
value := dataSource.GetValue(r, c)
expandColumnWidth(columnWidths, value, c)
}
}
// string buffer for final result.
var buffer bytes.Buffer
// Prepare format for header.
headerFormat := "|"
for _, columnWidth := range columnWidths {
headerFormat += fmt.Sprintf("%%%ds|", columnWidth)
}
headerFormat += "\n"
// Write column header.
buffer.WriteString(sprintfStrings(headerFormat, columnHeaders))
if numRows > 0 {
// Prepare format for rows.
rowFormat := "|"
for c := 0; c < numCols; c++ {
// get formatter of first row.
value := dataSource.GetValue(0, c)
modifier := getFormatModifier(value)
rowFormat += fmt.Sprintf("%%%d%s|", columnWidths[c], modifier)
}
rowFormat += "\n"
// Write rows.
for r := 0; r < numRows; r++ {
row := make([]interface{}, numCols)
for c := 0; c < numCols; c++ {
row[c] = dataSource.GetValue(r, c)
}
buffer.WriteString(fmt.Sprintf(rowFormat, row...))
}
}
return buffer.String()
}