package formatter import ( "fmt" "strings" "github.com/zemenawi/zutils/pkg/colors" ) // Table represents a formatted table with better formatting type Table struct { headers []string rows [][]string columnWidths []int showHeader bool showBorder bool padding int } // NewTable creates a new table func NewTable(headers []string) *Table { return &Table{ headers: headers, rows: make([][]string, 0), columnWidths: calculateColumnWidths(headers, make([][]string, 0)), showHeader: true, showBorder: true, padding: 2, } } // SetShowBorder sets whether to show borders func (t *Table) SetShowBorder(show bool) *Table { t.showBorder = show return t } // SetShowHeader sets whether to show header func (t *Table) SetShowHeader(show bool) *Table { t.showHeader = show return t } // SetPadding sets the padding func (t *Table) SetPadding(padding int) *Table { t.padding = padding return t } // AddRow adds a row to the table func (t *Table) AddRow(row []string) *Table { if len(row) != len(t.headers) { return t } t.rows = append(t.rows, row) t.columnWidths = calculateColumnWidths(t.headers, t.rows) return t } // Render renders the table as a string func (t *Table) Render() string { var builder strings.Builder if t.showBorder { builder.WriteString(t.renderTopBorder()) } if t.showHeader { builder.WriteString(t.renderRow(t.headers, true)) if t.showBorder { builder.WriteString(t.renderSeparator()) } } for _, row := range t.rows { builder.WriteString(t.renderRow(row, false)) } if t.showBorder { builder.WriteString(t.renderBottomBorder()) } return builder.String() } // PrintTable prints a table to stdout func PrintTable(headers []string, rows [][]string, borderColor string) { table := NewTable(headers) for _, row := range rows { table.AddRow(row) } output := table.Render() fmt.Print(borderColor) fmt.Print(output) fmt.Println(colors.Reset) } // PrintColoredTable prints a table with colored headers func PrintColoredTable(headers []string, rows [][]string, headerColor string) { // Add colors to headers coloredHeaders := make([]string, len(headers)) for i, header := range headers { coloredHeaders[i] = headerColor + header + colors.Reset } table := NewTable(coloredHeaders) for _, row := range rows { table.AddRow(row) } table.showBorder = false fmt.Print(table.Render()) } // renderRow renders a single row func (t *Table) renderRow(row []string, isHeader bool) string { var builder strings.Builder builder.WriteString("│") for i, cell := range row { if i > 0 { builder.WriteString("│") } builder.WriteString(t.padCell(cell, t.columnWidths[i], isHeader)) } builder.WriteString("│\n") return builder.String() } // padCell pads a cell to the correct width func (t *Table) padCell(cell string, width int, isHeader bool) string { padChar := " " // Left padding builder := strings.Builder{} for i := 0; i < t.padding; i++ { builder.WriteString(padChar) } // Cell content builder.WriteString(cell) // Right padding - pad to exact column width needed := width - len(cell) for i := 0; i < needed+t.padding; i++ { builder.WriteString(padChar) } return builder.String() } // renderTopBorder renders the top border func (t *Table) renderTopBorder() string { return t.renderBorderLine("╔", "═", "╗", "╦") } // renderBottomBorder renders the bottom border func (t *Table) renderBottomBorder() string { return t.renderBorderLine("╚", "═", "╝", "╩") } // renderSeparator renders the separator between header and rows func (t *Table) renderSeparator() string { return t.renderBorderLine("╠", "─", "╣", "╪") } // renderBorderLine renders a border line func (t *Table) renderBorderLine(left, middle, right, junction string) string { builder := strings.Builder{} builder.WriteString(left) for i, width := range t.columnWidths { if i > 0 { builder.WriteString(junction) } for j := 0; j < width+2*t.padding; j++ { builder.WriteString(middle) } } builder.WriteString(right) builder.WriteString("\n") return builder.String() } // calculateColumnWidths calculates the width of each column func calculateColumnWidths(headers []string, rows [][]string) []int { widths := make([]int, len(headers)) // Start with header widths for i, header := range headers { widths[i] = len(header) } // Adjust based on row content for _, row := range rows { for i, cell := range row { if i < len(widths) && len(cell) > widths[i] { widths[i] = len(cell) } } } return widths } // PrintCompactTable prints a compact table without borders func PrintCompactTable(headers []string, rows [][]string) { table := NewTable(headers) for _, row := range rows { table.AddRow(row) } table.showBorder = false table.showHeader = false table.padding = 2 fmt.Print(table.Render()) }