444 lines
11 KiB
Go
444 lines
11 KiB
Go
package gotabulate
|
|
|
|
import "fmt"
|
|
import "bytes"
|
|
import "math"
|
|
|
|
// Basic Structure of TableFormat
|
|
type TableFormat struct {
|
|
LineTop Line
|
|
LineBelowHeader Line
|
|
LineBetweenRows Line
|
|
LineBottom Line
|
|
HeaderRow Row
|
|
DataRow Row
|
|
Padding int
|
|
HeaderHide bool
|
|
FitScreen bool
|
|
}
|
|
|
|
// Represents a Line
|
|
type Line struct {
|
|
begin string
|
|
hline string
|
|
sep string
|
|
end string
|
|
}
|
|
|
|
// Represents a Row
|
|
type Row struct {
|
|
begin string
|
|
sep string
|
|
end string
|
|
}
|
|
|
|
// Table Formats that are available to the user
|
|
// The user can define his own format, just by addind an entry to this map
|
|
// and calling it with Render function e.g t.Render("customFormat")
|
|
var TableFormats = map[string]TableFormat{
|
|
"simple": TableFormat{
|
|
LineTop: Line{"", "-", " ", ""},
|
|
LineBelowHeader: Line{"", "-", " ", ""},
|
|
LineBottom: Line{"", "-", " ", ""},
|
|
HeaderRow: Row{"", " ", ""},
|
|
DataRow: Row{"", " ", ""},
|
|
Padding: 1,
|
|
},
|
|
"plain": TableFormat{
|
|
HeaderRow: Row{"", " ", ""},
|
|
DataRow: Row{"", " ", ""},
|
|
Padding: 1,
|
|
},
|
|
"grid": TableFormat{
|
|
LineTop: Line{"+", "-", "+", "+"},
|
|
LineBelowHeader: Line{"+", "=", "+", "+"},
|
|
LineBetweenRows: Line{"+", "-", "+", "+"},
|
|
LineBottom: Line{"+", "-", "+", "+"},
|
|
HeaderRow: Row{"|", "|", "|"},
|
|
DataRow: Row{"|", "|", "|"},
|
|
Padding: 1,
|
|
},
|
|
}
|
|
|
|
// Minimum padding that will be applied
|
|
var MIN_PADDING = 5
|
|
|
|
// Main Tabulate structure
|
|
type Tabulate struct {
|
|
Data []*TabulateRow
|
|
Headers []string
|
|
FloatFormat byte
|
|
TableFormat TableFormat
|
|
Align string
|
|
EmptyVar string
|
|
HideLines []string
|
|
MaxSize int
|
|
WrapStrings bool
|
|
}
|
|
|
|
// Represents normalized tabulate Row
|
|
type TabulateRow struct {
|
|
Elements []string
|
|
Continuos bool
|
|
Extra bool
|
|
}
|
|
|
|
// Add padding to each cell
|
|
func (t *Tabulate) padRow(arr []string, padding int) []string {
|
|
if len(arr) < 1 {
|
|
return arr
|
|
}
|
|
padded := make([]string, len(arr))
|
|
for index, el := range arr {
|
|
var buffer bytes.Buffer
|
|
// Pad left
|
|
for i := 0; i < padding; i++ {
|
|
buffer.WriteString(" ")
|
|
}
|
|
|
|
buffer.WriteString(el)
|
|
|
|
// Pad Right
|
|
for i := 0; i < padding; i++ {
|
|
buffer.WriteString(" ")
|
|
}
|
|
|
|
padded[index] = buffer.String()
|
|
}
|
|
return padded
|
|
}
|
|
|
|
// Align right (Add padding left)
|
|
func (t *Tabulate) padLeft(width int, str string) string {
|
|
var buffer bytes.Buffer
|
|
// Pad left
|
|
padding := width - len(str)
|
|
for i := 0; i < padding; i++ {
|
|
buffer.WriteString(" ")
|
|
}
|
|
buffer.WriteString(str)
|
|
return buffer.String()
|
|
}
|
|
|
|
// Align Left (Add padding right)
|
|
func (t *Tabulate) padRight(width int, str string) string {
|
|
var buffer bytes.Buffer
|
|
padding := width - len(str)
|
|
|
|
buffer.WriteString(str)
|
|
|
|
// Add Padding right
|
|
for i := 0; i < padding; i++ {
|
|
buffer.WriteString(" ")
|
|
}
|
|
return buffer.String()
|
|
}
|
|
|
|
// Center the element in the cell
|
|
func (t *Tabulate) padCenter(width int, str string) string {
|
|
var buffer bytes.Buffer
|
|
length := len(str)
|
|
|
|
padding := int(math.Ceil(float64((width - length)) / 2.0))
|
|
|
|
// Add padding left
|
|
for i := 0; i < padding; i++ {
|
|
buffer.WriteString(" ")
|
|
}
|
|
|
|
// Write string
|
|
buffer.WriteString(str)
|
|
|
|
// Calculate how much space is left
|
|
current := (width - len(buffer.String()))
|
|
|
|
// Add padding right
|
|
for i := 0; i < current; i++ {
|
|
buffer.WriteString(" ")
|
|
}
|
|
return buffer.String()
|
|
}
|
|
|
|
// Build Line based on padded_widths from t.GetWidths()
|
|
func (t *Tabulate) buildLine(padded_widths []int, padding []int, l Line) string {
|
|
cells := make([]string, len(padded_widths))
|
|
|
|
for i, _ := range cells {
|
|
var buffer bytes.Buffer
|
|
for j := 0; j < padding[i]+MIN_PADDING; j++ {
|
|
buffer.WriteString(l.hline)
|
|
}
|
|
cells[i] = buffer.String()
|
|
}
|
|
var buffer bytes.Buffer
|
|
|
|
// Print begin
|
|
buffer.WriteString(l.begin)
|
|
|
|
// Print contents
|
|
for i := 0; i < len(cells); i++ {
|
|
if i != len(cells)-1 {
|
|
buffer.WriteString(cells[i] + l.sep)
|
|
} else {
|
|
buffer.WriteString(cells[i])
|
|
}
|
|
}
|
|
|
|
// Print end
|
|
buffer.WriteString(l.end)
|
|
return buffer.String()
|
|
}
|
|
|
|
// Build Row based on padded_widths from t.GetWidths()
|
|
func (t *Tabulate) buildRow(elements []string, padded_widths []int, paddings []int, d Row) string {
|
|
|
|
var buffer bytes.Buffer
|
|
buffer.WriteString(d.begin)
|
|
padFunc := t.getAlignFunc()
|
|
// Print contents
|
|
for i := 0; i < len(padded_widths); i++ {
|
|
output := ""
|
|
if len(elements) > i {
|
|
output = padFunc(padded_widths[i], elements[i])
|
|
} else {
|
|
output = padFunc(padded_widths[i], t.EmptyVar)
|
|
}
|
|
buffer.WriteString(output)
|
|
if i != len(padded_widths)-1 {
|
|
buffer.WriteString(d.sep)
|
|
}
|
|
}
|
|
// Print end
|
|
buffer.WriteString(d.end)
|
|
|
|
return buffer.String()
|
|
}
|
|
|
|
// Render the data table
|
|
func (t *Tabulate) Render(format ...interface{}) string {
|
|
var lines []string
|
|
|
|
// If headers are set use them, otherwise pop the first row
|
|
if len(t.Headers) < 1 {
|
|
t.Headers, t.Data = t.Data[0].Elements, t.Data[1:]
|
|
}
|
|
|
|
// Use the format that was passed as parameter, otherwise
|
|
// use the format defined in the struct
|
|
if len(format) > 0 {
|
|
t.TableFormat = TableFormats[format[0].(string)]
|
|
}
|
|
|
|
// If Wrap Strings is set to True,then break up the string to multiple cells
|
|
if t.WrapStrings {
|
|
t.Data = t.wrapCellData()
|
|
}
|
|
|
|
// Check if Data is present
|
|
if len(t.Data) < 1 {
|
|
panic("No Data specified")
|
|
}
|
|
|
|
if len(t.Headers) < len(t.Data[0].Elements) {
|
|
diff := len(t.Data[0].Elements) - len(t.Headers)
|
|
padded_header := make([]string, diff)
|
|
for _, e := range t.Headers {
|
|
padded_header = append(padded_header, e)
|
|
}
|
|
t.Headers = padded_header
|
|
}
|
|
|
|
// Get Column widths for all columns
|
|
cols := t.getWidths(t.Headers, t.Data)
|
|
|
|
padded_widths := make([]int, len(cols))
|
|
for i, _ := range padded_widths {
|
|
padded_widths[i] = cols[i] + MIN_PADDING*t.TableFormat.Padding
|
|
}
|
|
|
|
// Start appending lines
|
|
|
|
// Append top line if not hidden
|
|
if !inSlice("top", t.HideLines) {
|
|
lines = append(lines, t.buildLine(padded_widths, cols, t.TableFormat.LineTop))
|
|
}
|
|
|
|
// Add Header
|
|
lines = append(lines, t.buildRow(t.padRow(t.Headers, t.TableFormat.Padding), padded_widths, cols, t.TableFormat.HeaderRow))
|
|
|
|
// Add Line Below Header if not hidden
|
|
if !inSlice("belowheader", t.HideLines) {
|
|
lines = append(lines, t.buildLine(padded_widths, cols, t.TableFormat.LineBelowHeader))
|
|
}
|
|
|
|
// Add Data Rows
|
|
for index, element := range t.Data {
|
|
lines = append(lines, t.buildRow(t.padRow(element.Elements, t.TableFormat.Padding), padded_widths, cols, t.TableFormat.DataRow))
|
|
if index < len(t.Data)-1 {
|
|
if element.Continuos != true {
|
|
lines = append(lines, t.buildLine(padded_widths, cols, t.TableFormat.LineBetweenRows))
|
|
}
|
|
}
|
|
}
|
|
|
|
if !inSlice("bottomLine", t.HideLines) {
|
|
lines = append(lines, t.buildLine(padded_widths, cols, t.TableFormat.LineBottom))
|
|
}
|
|
|
|
// Join lines
|
|
var buffer bytes.Buffer
|
|
for _, line := range lines {
|
|
buffer.WriteString(line + "\n")
|
|
}
|
|
|
|
return buffer.String()
|
|
}
|
|
|
|
// Calculate the max column width for each element
|
|
func (t *Tabulate) getWidths(headers []string, data []*TabulateRow) []int {
|
|
widths := make([]int, len(headers))
|
|
current_max := len(t.EmptyVar)
|
|
for i := 0; i < len(headers); i++ {
|
|
current_max = len(headers[i])
|
|
for _, item := range data {
|
|
if len(item.Elements) > i && len(widths) > i {
|
|
element := item.Elements[i]
|
|
if len(element) > current_max {
|
|
widths[i] = len(element)
|
|
current_max = len(element)
|
|
} else {
|
|
widths[i] = current_max
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return widths
|
|
}
|
|
|
|
// Set Headers of the table
|
|
// If Headers count is less than the data row count, the headers will be padded to the right
|
|
func (t *Tabulate) SetHeaders(headers []string) *Tabulate {
|
|
t.Headers = headers
|
|
return t
|
|
}
|
|
|
|
// Set Float Formatting
|
|
// will be used in strconv.FormatFloat(element, format, -1, 64)
|
|
func (t *Tabulate) SetFloatFormat(format byte) *Tabulate {
|
|
t.FloatFormat = format
|
|
return t
|
|
}
|
|
|
|
// Set Align Type, Available options: left, right, center
|
|
func (t *Tabulate) SetAlign(align string) {
|
|
t.Align = align
|
|
}
|
|
|
|
// Select the padding function based on the align type
|
|
func (t *Tabulate) getAlignFunc() func(int, string) string {
|
|
if len(t.Align) < 1 || t.Align == "right" {
|
|
return t.padLeft
|
|
} else if t.Align == "left" {
|
|
return t.padRight
|
|
} else {
|
|
return t.padCenter
|
|
}
|
|
}
|
|
|
|
// Set how an empty cell will be represented
|
|
func (t *Tabulate) SetEmptyString(empty string) {
|
|
t.EmptyVar = empty + " "
|
|
}
|
|
|
|
// Set which lines to hide.
|
|
// Can be:
|
|
// top - Top line of the table,
|
|
// belowheader - Line below the header,
|
|
// bottom - Bottom line of the table
|
|
func (t *Tabulate) SetHideLines(hide []string) {
|
|
t.HideLines = hide
|
|
}
|
|
|
|
func (t *Tabulate) SetWrapStrings(wrap bool) {
|
|
t.WrapStrings = wrap
|
|
}
|
|
|
|
// Sets the maximum size of cell
|
|
// If WrapStrings is set to true, then the string inside
|
|
// the cell will be split up into multiple cell
|
|
func (t *Tabulate) SetMaxCellSize(max int) {
|
|
t.MaxSize = max
|
|
}
|
|
|
|
// If string size is larger than t.MaxSize, then split it to multiple cells (downwards)
|
|
func (t *Tabulate) wrapCellData() []*TabulateRow {
|
|
var arr []*TabulateRow
|
|
next := t.Data[0]
|
|
for index := 0; index <= len(t.Data); index++ {
|
|
elements := next.Elements
|
|
new_elements := make([]string, len(elements))
|
|
|
|
for i, e := range elements {
|
|
if len(e) > t.MaxSize {
|
|
new_elements[i] = e[t.MaxSize:]
|
|
elements[i] = e[:t.MaxSize]
|
|
next.Continuos = true
|
|
}
|
|
}
|
|
|
|
if next.Continuos {
|
|
arr = append(arr, next)
|
|
next = &TabulateRow{Elements: new_elements, Extra: true}
|
|
index--
|
|
} else if next.Extra && index+1 < len(t.Data) {
|
|
arr = append(arr, next)
|
|
next = t.Data[index+1]
|
|
} else if index+1 < len(t.Data) {
|
|
arr = append(arr, next)
|
|
next = t.Data[index+1]
|
|
} else if index >= len(t.Data) {
|
|
arr = append(arr, next)
|
|
}
|
|
|
|
}
|
|
return arr
|
|
}
|
|
|
|
// Create a new Tabulate Object
|
|
// Accepts 2D String Array, 2D Int Array, 2D Int64 Array,
|
|
// 2D Bool Array, 2D Float64 Array, 2D interface{} Array,
|
|
// Map map[strig]string, Map map[string]interface{},
|
|
func Create(data interface{}) *Tabulate {
|
|
t := &Tabulate{FloatFormat: 'f', MaxSize: 30}
|
|
|
|
switch v := data.(type) {
|
|
case [][]string:
|
|
t.Data = createFromString(data.([][]string))
|
|
case [][]int32:
|
|
t.Data = createFromInt32(data.([][]int32))
|
|
case [][]int64:
|
|
t.Data = createFromInt64(data.([][]int64))
|
|
case [][]int:
|
|
t.Data = createFromInt(data.([][]int))
|
|
case [][]bool:
|
|
t.Data = createFromBool(data.([][]bool))
|
|
case [][]float64:
|
|
t.Data = createFromFloat64(data.([][]float64), t.FloatFormat)
|
|
case [][]interface{}:
|
|
t.Data = createFromMixed(data.([][]interface{}), t.FloatFormat)
|
|
case []string:
|
|
t.Data = createFromString([][]string{data.([]string)})
|
|
case []interface{}:
|
|
t.Data = createFromMixed([][]interface{}{data.([]interface{})}, t.FloatFormat)
|
|
case map[string][]interface{}:
|
|
t.Headers, t.Data = createFromMapMixed(data.(map[string][]interface{}), t.FloatFormat)
|
|
case map[string][]string:
|
|
t.Headers, t.Data = createFromMapString(data.(map[string][]string))
|
|
default:
|
|
fmt.Println(v)
|
|
}
|
|
|
|
return t
|
|
}
|