Initial QSfera import
This commit is contained in:
+79
@@ -0,0 +1,79 @@
|
||||
package tw
|
||||
|
||||
// CellFormatting holds formatting options for table cells.
|
||||
type CellFormatting struct {
|
||||
AutoWrap int // Wrapping behavior (e.g., WrapTruncate, WrapNormal)
|
||||
AutoFormat State // Enables automatic formatting (e.g., title case for headers)
|
||||
|
||||
// Deprecated: Kept for backward compatibility. Use CellConfig.CellMerging.Mode instead.
|
||||
// This will be removed in a future version.
|
||||
MergeMode int
|
||||
|
||||
// Deprecated: Kept for backward compatibility. Use CellConfig.Alignment instead.
|
||||
// This will be removed in a future version.
|
||||
Alignment Align
|
||||
}
|
||||
|
||||
// CellMerging holds the configuration for how cells should be merged.
|
||||
// This new struct replaces the deprecated MergeMode.
|
||||
type CellMerging struct {
|
||||
// Mode is a bitmask specifying the type of merge (e.g., MergeHorizontal, MergeVertical).
|
||||
Mode int
|
||||
|
||||
// ByColumnIndex specifies which column indices should be considered for merging.
|
||||
// If the mapper is nil or empty, merging applies to all columns (if Mode is set).
|
||||
// Otherwise, only columns with an index present as a key will be merged.
|
||||
ByColumnIndex Mapper[int, bool]
|
||||
|
||||
// ByRowIndex is reserved for future features to specify merging on specific rows.
|
||||
ByRowIndex Mapper[int, bool]
|
||||
}
|
||||
|
||||
// CellPadding defines padding settings for table cells.
|
||||
type CellPadding struct {
|
||||
Global Padding // Default padding applied to all cells
|
||||
PerColumn []Padding // Column-specific padding overrides
|
||||
}
|
||||
|
||||
// CellFilter defines filtering functions for cell content.
|
||||
type CellFilter struct {
|
||||
Global func([]string) []string // Processes the entire row
|
||||
PerColumn []func(string) string // Processes individual cells by column
|
||||
}
|
||||
|
||||
// CellCallbacks holds callback functions for cell processing.
|
||||
// Note: These are currently placeholders and not fully implemented.
|
||||
type CellCallbacks struct {
|
||||
Global func() // Global callback applied to all cells
|
||||
PerColumn []func() // Column-specific callbacks
|
||||
}
|
||||
|
||||
// CellAlignment defines alignment settings for table cells.
|
||||
type CellAlignment struct {
|
||||
Global Align // Default alignment applied to all cells
|
||||
PerColumn []Align // Column-specific alignment overrides
|
||||
}
|
||||
|
||||
// CellConfig combines formatting, padding, and callback settings for a table section.
|
||||
type CellConfig struct {
|
||||
Formatting CellFormatting // Cell formatting options
|
||||
Padding CellPadding // Padding configuration
|
||||
Callbacks CellCallbacks // Callback functions (unused)
|
||||
Filter CellFilter // Function to filter cell content (renamed from Filter Filter)
|
||||
Alignment CellAlignment // Alignment configuration for cells
|
||||
ColMaxWidths CellWidth // Per-column maximum width overrides
|
||||
Merging CellMerging // Merging holds all configuration related to cell merging.
|
||||
|
||||
// Deprecated: use Alignment.PerColumn instead. Will be removed in a future version.
|
||||
// will be removed soon
|
||||
ColumnAligns []Align // Per-column alignment overrides
|
||||
}
|
||||
|
||||
type CellWidth struct {
|
||||
Global int
|
||||
PerColumn Mapper[int, int]
|
||||
}
|
||||
|
||||
func (c CellWidth) Constrained() bool {
|
||||
return c.Global > 0 || c.PerColumn.Len() > 0
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package tw
|
||||
|
||||
// Deprecated: SymbolASCII is deprecated; use Glyphs with StyleASCII instead.
|
||||
// this will be removed soon
|
||||
type SymbolASCII struct{}
|
||||
|
||||
// SymbolASCII symbol methods
|
||||
func (s *SymbolASCII) Name() string { return StyleNameASCII.String() }
|
||||
func (s *SymbolASCII) Center() string { return "+" }
|
||||
func (s *SymbolASCII) Row() string { return "-" }
|
||||
func (s *SymbolASCII) Column() string { return "|" }
|
||||
func (s *SymbolASCII) TopLeft() string { return "+" }
|
||||
func (s *SymbolASCII) TopMid() string { return "+" }
|
||||
func (s *SymbolASCII) TopRight() string { return "+" }
|
||||
func (s *SymbolASCII) MidLeft() string { return "+" }
|
||||
func (s *SymbolASCII) MidRight() string { return "+" }
|
||||
func (s *SymbolASCII) BottomLeft() string { return "+" }
|
||||
func (s *SymbolASCII) BottomMid() string { return "+" }
|
||||
func (s *SymbolASCII) BottomRight() string { return "+" }
|
||||
func (s *SymbolASCII) HeaderLeft() string { return "+" }
|
||||
func (s *SymbolASCII) HeaderMid() string { return "+" }
|
||||
func (s *SymbolASCII) HeaderRight() string { return "+" }
|
||||
|
||||
// Deprecated: SymbolUnicode is deprecated; use Glyphs with appropriate styles (e.g., StyleLight, StyleHeavy) instead.
|
||||
// this will be removed soon
|
||||
type SymbolUnicode struct {
|
||||
row string
|
||||
column string
|
||||
center string
|
||||
corners [9]string // [topLeft, topMid, topRight, midLeft, center, midRight, bottomLeft, bottomMid, bottomRight]
|
||||
}
|
||||
|
||||
// SymbolUnicode symbol methods
|
||||
func (s *SymbolUnicode) Name() string { return "unicode" }
|
||||
func (s *SymbolUnicode) Center() string { return s.center }
|
||||
func (s *SymbolUnicode) Row() string { return s.row }
|
||||
func (s *SymbolUnicode) Column() string { return s.column }
|
||||
func (s *SymbolUnicode) TopLeft() string { return s.corners[0] }
|
||||
func (s *SymbolUnicode) TopMid() string { return s.corners[1] }
|
||||
func (s *SymbolUnicode) TopRight() string { return s.corners[2] }
|
||||
func (s *SymbolUnicode) MidLeft() string { return s.corners[3] }
|
||||
func (s *SymbolUnicode) MidRight() string { return s.corners[5] }
|
||||
func (s *SymbolUnicode) BottomLeft() string { return s.corners[6] }
|
||||
func (s *SymbolUnicode) BottomMid() string { return s.corners[7] }
|
||||
func (s *SymbolUnicode) BottomRight() string { return s.corners[8] }
|
||||
func (s *SymbolUnicode) HeaderLeft() string { return s.MidLeft() }
|
||||
func (s *SymbolUnicode) HeaderMid() string { return s.Center() }
|
||||
func (s *SymbolUnicode) HeaderRight() string { return s.MidRight() }
|
||||
|
||||
// Deprecated: SymbolMarkdown is deprecated; use Glyphs with StyleMarkdown instead.
|
||||
// this will be removed soon
|
||||
type SymbolMarkdown struct{}
|
||||
|
||||
// SymbolMarkdown symbol methods
|
||||
func (s *SymbolMarkdown) Name() string { return StyleNameMarkdown.String() }
|
||||
func (s *SymbolMarkdown) Center() string { return "|" }
|
||||
func (s *SymbolMarkdown) Row() string { return "-" }
|
||||
func (s *SymbolMarkdown) Column() string { return "|" }
|
||||
func (s *SymbolMarkdown) TopLeft() string { return "" }
|
||||
func (s *SymbolMarkdown) TopMid() string { return "" }
|
||||
func (s *SymbolMarkdown) TopRight() string { return "" }
|
||||
func (s *SymbolMarkdown) MidLeft() string { return "|" }
|
||||
func (s *SymbolMarkdown) MidRight() string { return "|" }
|
||||
func (s *SymbolMarkdown) BottomLeft() string { return "" }
|
||||
func (s *SymbolMarkdown) BottomMid() string { return "" }
|
||||
func (s *SymbolMarkdown) BottomRight() string { return "" }
|
||||
func (s *SymbolMarkdown) HeaderLeft() string { return "|" }
|
||||
func (s *SymbolMarkdown) HeaderMid() string { return "|" }
|
||||
func (s *SymbolMarkdown) HeaderRight() string { return "|" }
|
||||
|
||||
// Deprecated: SymbolNothing is deprecated; use Glyphs with StyleNone instead.
|
||||
// this will be removed soon
|
||||
type SymbolNothing struct{}
|
||||
|
||||
// SymbolNothing symbol methods
|
||||
func (s *SymbolNothing) Name() string { return StyleNameNothing.String() }
|
||||
func (s *SymbolNothing) Center() string { return "" }
|
||||
func (s *SymbolNothing) Row() string { return "" }
|
||||
func (s *SymbolNothing) Column() string { return "" }
|
||||
func (s *SymbolNothing) TopLeft() string { return "" }
|
||||
func (s *SymbolNothing) TopMid() string { return "" }
|
||||
func (s *SymbolNothing) TopRight() string { return "" }
|
||||
func (s *SymbolNothing) MidLeft() string { return "" }
|
||||
func (s *SymbolNothing) MidRight() string { return "" }
|
||||
func (s *SymbolNothing) BottomLeft() string { return "" }
|
||||
func (s *SymbolNothing) BottomMid() string { return "" }
|
||||
func (s *SymbolNothing) BottomRight() string { return "" }
|
||||
func (s *SymbolNothing) HeaderLeft() string { return "" }
|
||||
func (s *SymbolNothing) HeaderMid() string { return "" }
|
||||
func (s *SymbolNothing) HeaderRight() string { return "" }
|
||||
|
||||
// Deprecated: SymbolGraphical is deprecated; use Glyphs with StyleGraphical instead.
|
||||
// this will be removed soon
|
||||
type SymbolGraphical struct{}
|
||||
|
||||
// SymbolGraphical symbol methods
|
||||
func (s *SymbolGraphical) Name() string { return StyleNameGraphical.String() }
|
||||
func (s *SymbolGraphical) Center() string { return "🟧" } // Orange square (matches mid junctions)
|
||||
func (s *SymbolGraphical) Row() string { return "🟥" } // Red square (matches corners)
|
||||
func (s *SymbolGraphical) Column() string { return "🟦" } // Blue square (vertical line)
|
||||
func (s *SymbolGraphical) TopLeft() string { return "🟥" } // Top-left corner
|
||||
func (s *SymbolGraphical) TopMid() string { return "🔳" } // Top junction
|
||||
func (s *SymbolGraphical) TopRight() string { return "🟥" } // Top-right corner
|
||||
func (s *SymbolGraphical) MidLeft() string { return "🟧" } // Left junction
|
||||
func (s *SymbolGraphical) MidRight() string { return "🟧" } // Right junction
|
||||
func (s *SymbolGraphical) BottomLeft() string { return "🟥" } // Bottom-left corner
|
||||
func (s *SymbolGraphical) BottomMid() string { return "🔳" } // Bottom junction
|
||||
func (s *SymbolGraphical) BottomRight() string { return "🟥" } // Bottom-right corner
|
||||
func (s *SymbolGraphical) HeaderLeft() string { return "🟧" } // Header left (matches mid junctions)
|
||||
func (s *SymbolGraphical) HeaderMid() string { return "🟧" } // Header middle (matches mid junctions)
|
||||
func (s *SymbolGraphical) HeaderRight() string { return "🟧" } // Header right (matches mid junctions)
|
||||
|
||||
// Deprecated: SymbolMerger is deprecated; use Glyphs with StyleMerger instead.
|
||||
// this will be removed soon
|
||||
type SymbolMerger struct {
|
||||
row string
|
||||
column string
|
||||
center string
|
||||
corners [9]string // [TL, TM, TR, ML, CenterIdx(unused), MR, BL, BM, BR]
|
||||
}
|
||||
|
||||
// SymbolMerger symbol methods
|
||||
func (s *SymbolMerger) Name() string { return StyleNameMerger.String() }
|
||||
func (s *SymbolMerger) Center() string { return s.center } // Main crossing symbol
|
||||
func (s *SymbolMerger) Row() string { return s.row }
|
||||
func (s *SymbolMerger) Column() string { return s.column }
|
||||
func (s *SymbolMerger) TopLeft() string { return s.corners[0] }
|
||||
func (s *SymbolMerger) TopMid() string { return s.corners[1] } // LevelHeader junction
|
||||
func (s *SymbolMerger) TopRight() string { return s.corners[2] }
|
||||
func (s *SymbolMerger) MidLeft() string { return s.corners[3] } // Left junction
|
||||
func (s *SymbolMerger) MidRight() string { return s.corners[5] } // Right junction
|
||||
func (s *SymbolMerger) BottomLeft() string { return s.corners[6] }
|
||||
func (s *SymbolMerger) BottomMid() string { return s.corners[7] } // LevelFooter junction
|
||||
func (s *SymbolMerger) BottomRight() string { return s.corners[8] }
|
||||
func (s *SymbolMerger) HeaderLeft() string { return s.MidLeft() }
|
||||
func (s *SymbolMerger) HeaderMid() string { return s.Center() }
|
||||
func (s *SymbolMerger) HeaderRight() string { return s.MidRight() }
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
// Package tw provides utility functions for text formatting, width calculation, and string manipulation
|
||||
// specifically tailored for table rendering, including handling ANSI escape codes and Unicode text.
|
||||
package tw
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
// For mathematical operations like ceiling
|
||||
// For string-to-number conversions
|
||||
// For string manipulation utilities
|
||||
// For Unicode character classification
|
||||
// For UTF-8 rune handling
|
||||
)
|
||||
|
||||
// Title normalizes and uppercases a label string for use in headers.
|
||||
// It replaces underscores and certain dots with spaces and trims whitespace.
|
||||
func Title(name string) string {
|
||||
origLen := len(name)
|
||||
rs := []rune(name)
|
||||
for i, r := range rs {
|
||||
switch r {
|
||||
case '_':
|
||||
rs[i] = ' ' // Replace underscores with spaces
|
||||
case '.':
|
||||
// Replace dots with spaces unless they are between numeric or space characters
|
||||
if (i != 0 && !IsIsNumericOrSpace(rs[i-1])) || (i != len(rs)-1 && !IsIsNumericOrSpace(rs[i+1])) {
|
||||
rs[i] = ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
name = string(rs)
|
||||
name = strings.TrimSpace(name)
|
||||
// If the input was non-empty but trimmed to empty, return a single space
|
||||
if len(name) == 0 && origLen > 0 {
|
||||
name = " "
|
||||
}
|
||||
// Convert to uppercase for header formatting
|
||||
return strings.ToUpper(name)
|
||||
}
|
||||
|
||||
// PadCenter centers a string within a specified width using a padding character.
|
||||
// Extra padding is split between left and right, with slight preference to left if uneven.
|
||||
func PadCenter(s, pad string, width int) string {
|
||||
gap := width - twwidth.Width(s)
|
||||
if gap > 0 {
|
||||
// Calculate left and right padding; ceil ensures left gets extra if gap is odd
|
||||
gapLeft := int(math.Ceil(float64(gap) / 2))
|
||||
gapRight := gap - gapLeft
|
||||
return strings.Repeat(pad, gapLeft) + s + strings.Repeat(pad, gapRight)
|
||||
}
|
||||
// If no padding needed or string is too wide, return as is
|
||||
return s
|
||||
}
|
||||
|
||||
// PadRight left-aligns a string within a specified width, filling remaining space on the right with padding.
|
||||
func PadRight(s, pad string, width int) string {
|
||||
gap := width - twwidth.Width(s)
|
||||
if gap > 0 {
|
||||
// Append padding to the right
|
||||
return s + strings.Repeat(pad, gap)
|
||||
}
|
||||
// If no padding needed or string is too wide, return as is
|
||||
return s
|
||||
}
|
||||
|
||||
// PadLeft right-aligns a string within a specified width, filling remaining space on the left with padding.
|
||||
func PadLeft(s, pad string, width int) string {
|
||||
gap := width - twwidth.Width(s)
|
||||
if gap > 0 {
|
||||
// Prepend padding to the left
|
||||
return strings.Repeat(pad, gap) + s
|
||||
}
|
||||
// If no padding needed or string is too wide, return as is
|
||||
return s
|
||||
}
|
||||
|
||||
// Pad aligns a string within a specified width using a padding character.
|
||||
// It truncates if the string is wider than the target width.
|
||||
func Pad(s, padChar string, totalWidth int, alignment Align) string {
|
||||
sDisplayWidth := twwidth.Width(s)
|
||||
if sDisplayWidth > totalWidth {
|
||||
return twwidth.Truncate(s, totalWidth) // Only truncate if necessary
|
||||
}
|
||||
switch alignment {
|
||||
case AlignLeft:
|
||||
return PadRight(s, padChar, totalWidth)
|
||||
case AlignRight:
|
||||
return PadLeft(s, padChar, totalWidth)
|
||||
case AlignCenter:
|
||||
return PadCenter(s, padChar, totalWidth)
|
||||
default:
|
||||
return PadRight(s, padChar, totalWidth)
|
||||
}
|
||||
}
|
||||
|
||||
// IsIsNumericOrSpace checks if a rune is a digit or space character.
|
||||
// Used in formatting logic to determine safe character replacements.
|
||||
func IsIsNumericOrSpace(r rune) bool {
|
||||
return ('0' <= r && r <= '9') || r == ' '
|
||||
}
|
||||
|
||||
// IsNumeric checks if a string represents a valid integer or floating-point number.
|
||||
func IsNumeric(s string) bool {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
// Try parsing as integer first
|
||||
if _, err := strconv.Atoi(s); err == nil {
|
||||
return true
|
||||
}
|
||||
// Then try parsing as float
|
||||
_, err := strconv.ParseFloat(s, 64)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// SplitCamelCase splits a camelCase or PascalCase or snake_case string into separate words.
|
||||
// It detects transitions between uppercase, lowercase, digits, and other characters.
|
||||
func SplitCamelCase(src string) (entries []string) {
|
||||
// Validate UTF-8 input; return as single entry if invalid
|
||||
if !utf8.ValidString(src) {
|
||||
return []string{src}
|
||||
}
|
||||
entries = []string{}
|
||||
var runes [][]rune
|
||||
lastClass := 0
|
||||
class := 0
|
||||
// Classify each rune into categories: lowercase (1), uppercase (2), digit (3), other (4)
|
||||
for _, r := range src {
|
||||
switch {
|
||||
case unicode.IsLower(r):
|
||||
class = 1
|
||||
case unicode.IsUpper(r):
|
||||
class = 2
|
||||
case unicode.IsDigit(r):
|
||||
class = 3
|
||||
default:
|
||||
class = 4
|
||||
}
|
||||
// Group consecutive runes of the same class together
|
||||
if class == lastClass {
|
||||
runes[len(runes)-1] = append(runes[len(runes)-1], r)
|
||||
} else {
|
||||
runes = append(runes, []rune{r})
|
||||
}
|
||||
lastClass = class
|
||||
}
|
||||
// Adjust for cases where an uppercase letter is followed by lowercase (e.g., CamelCase)
|
||||
for i := 0; i < len(runes)-1; i++ {
|
||||
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
|
||||
// Move the last uppercase rune to the next group for proper word splitting
|
||||
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
|
||||
runes[i] = runes[i][:len(runes[i])-1]
|
||||
}
|
||||
}
|
||||
// Convert rune groups to strings, excluding empty, underscore or whitespace-only groups
|
||||
for _, s := range runes {
|
||||
str := string(s)
|
||||
if len(s) > 0 && strings.TrimSpace(str) != "" && str != "_" {
|
||||
entries = append(entries, str)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Or provides a ternary-like operation for strings, returning 'valid' if cond is true, else 'inValid'.
|
||||
func Or(cond bool, valid, inValid string) string {
|
||||
if cond {
|
||||
return valid
|
||||
}
|
||||
return inValid
|
||||
}
|
||||
|
||||
// Max returns the greater of two integers.
|
||||
func Max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Min returns the smaller of two integers.
|
||||
func Min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// BreakPoint finds the rune index where the display width of a string first exceeds the specified limit.
|
||||
// It returns the number of runes if the entire string fits, or 0 if nothing fits.
|
||||
func BreakPoint(s string, limit int) int {
|
||||
// If limit is 0 or negative, nothing can fit
|
||||
if limit <= 0 {
|
||||
return 0
|
||||
}
|
||||
// Empty string has a breakpoint of 0
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
currentWidth := 0
|
||||
runeCount := 0
|
||||
// Iterate over runes, accumulating display width
|
||||
for _, r := range s {
|
||||
runeWidth := twwidth.Width(string(r)) // Calculate width of individual rune
|
||||
if currentWidth+runeWidth > limit {
|
||||
// Adding this rune would exceed the limit; breakpoint is before this rune
|
||||
if currentWidth == 0 {
|
||||
// First rune is too wide; allow breaking after it if limit > 0
|
||||
if runeWidth > limit && limit > 0 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return runeCount
|
||||
}
|
||||
currentWidth += runeWidth
|
||||
runeCount++
|
||||
}
|
||||
|
||||
// Entire string fits within the limit
|
||||
return runeCount
|
||||
}
|
||||
|
||||
func MakeAlign(l int, align Align) Alignment {
|
||||
aa := make(Alignment, l)
|
||||
for i := 0; i < l; i++ {
|
||||
aa[i] = align
|
||||
}
|
||||
return aa
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
package tw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// KeyValuePair represents a single key-value pair from a Mapper.
|
||||
type KeyValuePair[K comparable, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
// Mapper is a generic map type with comparable keys and any value type.
|
||||
// It provides type-safe operations on maps with additional convenience methods.
|
||||
type Mapper[K comparable, V any] map[K]V
|
||||
|
||||
// NewMapper creates and returns a new initialized Mapper.
|
||||
func NewMapper[K comparable, V any]() Mapper[K, V] {
|
||||
return make(Mapper[K, V])
|
||||
}
|
||||
|
||||
// Get returns the value associated with the key.
|
||||
// If the key doesn't exist or the map is nil, it returns the zero value for the value type.
|
||||
func (m Mapper[K, V]) Get(key K) V {
|
||||
if m == nil {
|
||||
var zero V
|
||||
return zero
|
||||
}
|
||||
return m[key]
|
||||
}
|
||||
|
||||
// OK returns the value associated with the key and a boolean indicating whether the key exists.
|
||||
func (m Mapper[K, V]) OK(key K) (V, bool) {
|
||||
if m == nil {
|
||||
var zero V
|
||||
return zero, false
|
||||
}
|
||||
val, ok := m[key]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Set sets the value for the specified key.
|
||||
// Does nothing if the map is nil.
|
||||
func (m Mapper[K, V]) Set(key K, value V) Mapper[K, V] {
|
||||
if m != nil {
|
||||
m[key] = value
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Delete removes the specified key from the map.
|
||||
// Does nothing if the key doesn't exist or the map is nil.
|
||||
func (m Mapper[K, V]) Delete(key K) Mapper[K, V] {
|
||||
if m != nil {
|
||||
delete(m, key)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Has returns true if the key exists in the map, false otherwise.
|
||||
func (m Mapper[K, V]) Has(key K) bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
_, exists := m[key]
|
||||
return exists
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the map.
|
||||
// Returns 0 if the map is nil.
|
||||
func (m Mapper[K, V]) Len() int {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
return len(m)
|
||||
}
|
||||
|
||||
// Keys returns a slice containing all keys in the map.
|
||||
// Returns nil if the map is nil or empty.
|
||||
func (m Mapper[K, V]) Keys() []K {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
keys := make([]K, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (m Mapper[K, V]) Clear() {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Values returns a slice containing all values in the map.
|
||||
// Returns nil if the map is nil or empty.
|
||||
func (m Mapper[K, V]) Values() []V {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
values := make([]V, 0, len(m))
|
||||
for _, v := range m {
|
||||
values = append(values, v)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Each iterates over each key-value pair in the map and calls the provided function.
|
||||
// Does nothing if the map is nil.
|
||||
func (m Mapper[K, V]) Each(fn func(K, V)) {
|
||||
for k, v := range m {
|
||||
fn(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter returns a new Mapper containing only the key-value pairs that satisfy the predicate.
|
||||
func (m Mapper[K, V]) Filter(fn func(K, V) bool) Mapper[K, V] {
|
||||
result := NewMapper[K, V]()
|
||||
for k, v := range m {
|
||||
if fn(k, v) {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MapValues returns a new Mapper with the same keys but values transformed by the provided function.
|
||||
func (m Mapper[K, V]) MapValues(fn func(V) V) Mapper[K, V] {
|
||||
result := NewMapper[K, V]()
|
||||
for k, v := range m {
|
||||
result[k] = fn(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Clone returns a shallow copy of the Mapper.
|
||||
func (m Mapper[K, V]) Clone() Mapper[K, V] {
|
||||
result := NewMapper[K, V]()
|
||||
for k, v := range m {
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Slicer converts the Mapper to a Slicer of key-value pairs.
|
||||
func (m Mapper[K, V]) Slicer() Slicer[KeyValuePair[K, V]] {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
result := make(Slicer[KeyValuePair[K, V]], 0, len(m))
|
||||
for k, v := range m {
|
||||
result = append(result, KeyValuePair[K, V]{Key: k, Value: v})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (m Mapper[K, V]) SortedKeys() []K {
|
||||
keys := make([]K, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
a, b := any(keys[i]), any(keys[j])
|
||||
|
||||
switch va := a.(type) {
|
||||
case int:
|
||||
if vb, ok := b.(int); ok {
|
||||
return va < vb
|
||||
}
|
||||
case int32:
|
||||
if vb, ok := b.(int32); ok {
|
||||
return va < vb
|
||||
}
|
||||
case int64:
|
||||
if vb, ok := b.(int64); ok {
|
||||
return va < vb
|
||||
}
|
||||
case uint:
|
||||
if vb, ok := b.(uint); ok {
|
||||
return va < vb
|
||||
}
|
||||
case uint64:
|
||||
if vb, ok := b.(uint64); ok {
|
||||
return va < vb
|
||||
}
|
||||
case float32:
|
||||
if vb, ok := b.(float32); ok {
|
||||
return va < vb
|
||||
}
|
||||
case float64:
|
||||
if vb, ok := b.(float64); ok {
|
||||
return va < vb
|
||||
}
|
||||
case string:
|
||||
if vb, ok := b.(string); ok {
|
||||
return va < vb
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to string comparison
|
||||
return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b)
|
||||
})
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func NewBoolMapper[K comparable](keys ...K) Mapper[K, bool] {
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
mapper := NewMapper[K, bool]()
|
||||
for _, key := range keys {
|
||||
mapper.Set(key, true)
|
||||
}
|
||||
return mapper
|
||||
}
|
||||
|
||||
func NewIntMapper[K comparable](keys ...K) Mapper[K, int] {
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
mapper := NewMapper[K, int]()
|
||||
for _, key := range keys {
|
||||
mapper.Set(key, 0)
|
||||
}
|
||||
return mapper
|
||||
}
|
||||
|
||||
func NewIdentityMapper[K comparable](keys ...K) Mapper[K, K] {
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
mapper := NewMapper[K, K]()
|
||||
for _, key := range keys {
|
||||
mapper.Set(key, key)
|
||||
}
|
||||
return mapper
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package tw
|
||||
|
||||
// BorderNone defines a border configuration with all sides disabled.
|
||||
var (
|
||||
// PaddingNone represents explicitly empty padding (no spacing on any side)
|
||||
// Equivalent to Padding{Overwrite: true}
|
||||
PaddingNone = Padding{Left: Empty, Right: Empty, Top: Empty, Bottom: Empty, Overwrite: true}
|
||||
BorderNone = Border{Left: Off, Right: Off, Top: Off, Bottom: Off}
|
||||
LinesNone = Lines{ShowTop: Off, ShowBottom: Off, ShowHeaderLine: Off, ShowFooterLine: Off}
|
||||
SeparatorsNone = Separators{ShowHeader: Off, ShowFooter: Off, BetweenRows: Off, BetweenColumns: Off}
|
||||
)
|
||||
|
||||
// PaddingDefault represents standard single-space padding on left/right
|
||||
// Equivalent to Padding{Left: " ", Right: " ", Overwrite: true}
|
||||
var PaddingDefault = Padding{Left: " ", Right: " ", Overwrite: true}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package tw
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/olekukonko/ll"
|
||||
)
|
||||
|
||||
// Renderer defines the interface for rendering tables to an io.Writer.
|
||||
// Implementations must handle headers, rows, footers, and separator lines.
|
||||
type Renderer interface {
|
||||
Start(w io.Writer) error
|
||||
Header(headers [][]string, ctx Formatting) // Renders table header
|
||||
Row(row []string, ctx Formatting) // Renders a single row
|
||||
Footer(footers [][]string, ctx Formatting) // Renders table footer
|
||||
Line(ctx Formatting) // Renders separator line
|
||||
Config() Rendition // Returns renderer config
|
||||
Close() error // Gets Rendition form Blueprint
|
||||
Logger(logger *ll.Logger) // send logger to renderers
|
||||
}
|
||||
|
||||
// Rendition holds the configuration for the default renderer.
|
||||
type Rendition struct {
|
||||
Borders Border // Border visibility settings
|
||||
Symbols Symbols // Symbols used for table drawing
|
||||
Settings Settings // Rendering behavior settings
|
||||
Streaming bool
|
||||
}
|
||||
|
||||
// Renditioning has a method to update its rendition.
|
||||
// Let's define an optional interface for this.
|
||||
type Renditioning interface {
|
||||
Rendition(r Rendition)
|
||||
}
|
||||
|
||||
// Formatting encapsulates the complete formatting context for a table row.
|
||||
// It provides all necessary information to render a row correctly within the table structure.
|
||||
type Formatting struct {
|
||||
Row RowContext // Detailed configuration for the row and its cells
|
||||
Level Level // Hierarchical level (Header, Body, Footer) affecting line drawing
|
||||
HasFooter bool // Indicates if the table includes a footer section
|
||||
IsSubRow bool // Marks this as a continuation or padding line in multi-line rows
|
||||
NormalizedWidths Mapper[int, int]
|
||||
}
|
||||
|
||||
// CellContext defines the properties and formatting state of an individual table cell.
|
||||
type CellContext struct {
|
||||
Data string // Content to be displayed in the cell, provided by the caller
|
||||
Align Align // Text alignment within the cell (Left, Right, Center, Skip)
|
||||
Padding Padding // Padding characters surrounding the cell content
|
||||
Width int // Suggested width (often overridden by Row.Widths)
|
||||
Merge MergeState // Details about cell spanning across rows or columns
|
||||
}
|
||||
|
||||
// MergeState captures how a cell merges across different directions.
|
||||
type MergeState struct {
|
||||
Vertical MergeStateOption // Properties for vertical merging (across rows)
|
||||
Horizontal MergeStateOption // Properties for horizontal merging (across columns)
|
||||
Hierarchical MergeStateOption // Properties for nested/hierarchical merging
|
||||
}
|
||||
|
||||
// MergeStateOption represents common attributes for merging in a specific direction.
|
||||
type MergeStateOption struct {
|
||||
Present bool // True if this merge direction is active
|
||||
Span int // Number of cells this merge spans
|
||||
Start bool // True if this cell is the starting point of the merge
|
||||
End bool // True if this cell is the ending point of the merge
|
||||
}
|
||||
|
||||
// RowContext manages layout properties and relationships for a row and its columns.
|
||||
// It maintains state about the current row and its neighbors for proper rendering.
|
||||
type RowContext struct {
|
||||
Position Position // Section of the table (Header, Row, Footer)
|
||||
Location Location // Boundary position (First, Middle, End)
|
||||
Current map[int]CellContext // Cells in this row, indexed by column
|
||||
Previous map[int]CellContext // Cells from the row above; nil if none
|
||||
Next map[int]CellContext // Cells from the row below; nil if none
|
||||
Widths Mapper[int, int] // Computed widths for each column
|
||||
ColMaxWidths CellWidth // Maximum allowed width per column
|
||||
}
|
||||
|
||||
func (r RowContext) GetCell(col int) CellContext {
|
||||
return r.Current[col]
|
||||
}
|
||||
|
||||
// Separators controls the visibility of separators in the table.
|
||||
type Separators struct {
|
||||
ShowHeader State // Controls header separator visibility
|
||||
ShowFooter State // Controls footer separator visibility
|
||||
BetweenRows State // Determines if lines appear between rows
|
||||
BetweenColumns State // Determines if separators appear between columns
|
||||
}
|
||||
|
||||
// Lines manages the visibility of table boundary lines.
|
||||
type Lines struct {
|
||||
ShowTop State // Top border visibility
|
||||
ShowBottom State // Bottom border visibility
|
||||
ShowHeaderLine State // Header separator line visibility
|
||||
ShowFooterLine State // Footer separator line visibility
|
||||
}
|
||||
|
||||
// Settings holds configuration preferences for rendering behavior.
|
||||
type Settings struct {
|
||||
Separators Separators // Separator visibility settings
|
||||
Lines Lines // Line visibility settings
|
||||
CompactMode State // Reserved for future compact rendering (unused)
|
||||
// Cushion State
|
||||
}
|
||||
|
||||
// Border defines the visibility states of table borders.
|
||||
type Border struct {
|
||||
Left State // Left border visibility
|
||||
Right State // Right border visibility
|
||||
Top State // Top border visibility
|
||||
Bottom State // Bottom border visibility
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
type StreamConfig struct {
|
||||
Enable bool
|
||||
|
||||
// StrictColumns, if true, causes Append() to return an error
|
||||
// in streaming mode if the number of cells in an appended row
|
||||
// does not match the established number of columns for the stream.
|
||||
// If false (default), rows with mismatched column counts will be
|
||||
// padded or truncated with a warning log.
|
||||
StrictColumns bool
|
||||
|
||||
// Deprecated: Use top-level Config.Widths for streaming width control.
|
||||
// This field will be removed in a future version. It will be respected if
|
||||
// Config.Widths is not set and this field is.
|
||||
Widths CellWidth
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package tw
|
||||
|
||||
import "slices"
|
||||
|
||||
// Slicer is a generic slice type that provides additional methods for slice manipulation.
|
||||
type Slicer[T any] []T
|
||||
|
||||
// NewSlicer creates and returns a new initialized Slicer.
|
||||
func NewSlicer[T any]() Slicer[T] {
|
||||
return make(Slicer[T], 0)
|
||||
}
|
||||
|
||||
// Get returns the element at the specified index.
|
||||
// Returns the zero value if the index is out of bounds or the slice is nil.
|
||||
func (s Slicer[T]) Get(index int) T {
|
||||
if s == nil || index < 0 || index >= len(s) {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
return s[index]
|
||||
}
|
||||
|
||||
// GetOK returns the element at the specified index and a boolean indicating whether the index was valid.
|
||||
func (s Slicer[T]) GetOK(index int) (T, bool) {
|
||||
if s == nil || index < 0 || index >= len(s) {
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
return s[index], true
|
||||
}
|
||||
|
||||
// Append appends elements to the slice and returns the new slice.
|
||||
func (s Slicer[T]) Append(elements ...T) Slicer[T] {
|
||||
return append(s, elements...)
|
||||
}
|
||||
|
||||
// Prepend adds elements to the beginning of the slice and returns the new slice.
|
||||
func (s Slicer[T]) Prepend(elements ...T) Slicer[T] {
|
||||
return append(elements, s...)
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the slice.
|
||||
// Returns 0 if the slice is nil.
|
||||
func (s Slicer[T]) Len() int {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the slice is nil or has zero elements.
|
||||
func (s Slicer[T]) IsEmpty() bool {
|
||||
return s.Len() == 0
|
||||
}
|
||||
|
||||
// Has returns true if the index exists in the slice.
|
||||
func (s Slicer[T]) Has(index int) bool {
|
||||
return index >= 0 && index < s.Len()
|
||||
}
|
||||
|
||||
// First returns the first element of the slice, or the zero value if empty.
|
||||
func (s Slicer[T]) First() T {
|
||||
return s.Get(0)
|
||||
}
|
||||
|
||||
// Last returns the last element of the slice, or the zero value if empty.
|
||||
func (s Slicer[T]) Last() T {
|
||||
return s.Get(s.Len() - 1)
|
||||
}
|
||||
|
||||
// Each iterates over each element in the slice and calls the provided function.
|
||||
// Does nothing if the slice is nil.
|
||||
func (s Slicer[T]) Each(fn func(T)) {
|
||||
for _, v := range s {
|
||||
fn(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter returns a new Slicer containing only elements that satisfy the predicate.
|
||||
func (s Slicer[T]) Filter(fn func(T) bool) Slicer[T] {
|
||||
result := NewSlicer[T]()
|
||||
for _, v := range s {
|
||||
if fn(v) {
|
||||
result = result.Append(v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Map returns a new Slicer with each element transformed by the provided function.
|
||||
func (s Slicer[T]) Map(fn func(T) T) Slicer[T] {
|
||||
result := NewSlicer[T]()
|
||||
for _, v := range s {
|
||||
result = result.Append(fn(v))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Contains returns true if the slice contains an element that satisfies the predicate.
|
||||
func (s Slicer[T]) Contains(fn func(T) bool) bool {
|
||||
return slices.ContainsFunc(s, fn)
|
||||
}
|
||||
|
||||
// Find returns the first element that satisfies the predicate, along with a boolean indicating if it was found.
|
||||
func (s Slicer[T]) Find(fn func(T) bool) (T, bool) {
|
||||
for _, v := range s {
|
||||
if fn(v) {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
|
||||
// Clone returns a shallow copy of the Slicer.
|
||||
func (s Slicer[T]) Clone() Slicer[T] {
|
||||
result := NewSlicer[T]()
|
||||
if s != nil {
|
||||
result = append(result, s...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// SlicerToMapper converts a Slicer of KeyValuePair to a Mapper.
|
||||
func SlicerToMapper[K comparable, V any](s Slicer[KeyValuePair[K, V]]) Mapper[K, V] {
|
||||
result := make(Mapper[K, V])
|
||||
if s == nil {
|
||||
return result
|
||||
}
|
||||
for _, pair := range s {
|
||||
result[pair.Key] = pair.Value
|
||||
}
|
||||
return result
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package tw
|
||||
|
||||
// State represents an on/off state
|
||||
type State int
|
||||
|
||||
// Public: Methods for State type
|
||||
|
||||
// Enabled checks if the state is on
|
||||
func (o State) Enabled() bool { return o == Success }
|
||||
|
||||
// Default checks if the state is unknown
|
||||
func (o State) Default() bool { return o == Unknown }
|
||||
|
||||
// Disabled checks if the state is off
|
||||
func (o State) Disabled() bool { return o == Fail }
|
||||
|
||||
// Toggle switches the state between on and off
|
||||
func (o State) Toggle() State {
|
||||
if o == Fail {
|
||||
return Success
|
||||
}
|
||||
return Fail
|
||||
}
|
||||
|
||||
// Cond executes a condition if the state is enabled
|
||||
func (o State) Cond(c func() bool) bool {
|
||||
if o.Enabled() {
|
||||
return c()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Or returns this state if enabled, else the provided state
|
||||
func (o State) Or(c State) State {
|
||||
if o.Enabled() {
|
||||
return o
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// String returns the string representation of the state
|
||||
func (o State) String() string {
|
||||
if o.Enabled() {
|
||||
return "on"
|
||||
}
|
||||
|
||||
if o.Disabled() {
|
||||
return "off"
|
||||
}
|
||||
return "undefined"
|
||||
}
|
||||
+1012
File diff suppressed because it is too large
Load Diff
+109
@@ -0,0 +1,109 @@
|
||||
package tw
|
||||
|
||||
// Operation Status Constants
|
||||
// Used to indicate the success or failure of operations
|
||||
const (
|
||||
Pending = 0 // Operation failed
|
||||
Fail = -1 // Operation failed
|
||||
Success = 1 // Operation succeeded
|
||||
|
||||
MinimumColumnWidth = 8
|
||||
|
||||
DefaultCacheStringCapacity = 10 * 1024 // 10 KB
|
||||
)
|
||||
|
||||
const (
|
||||
Empty = ""
|
||||
Skip = ""
|
||||
Space = " "
|
||||
NewLine = "\n"
|
||||
Column = ":"
|
||||
Dash = "-"
|
||||
)
|
||||
|
||||
// Feature State Constants
|
||||
// Represents enabled/disabled states for features
|
||||
const (
|
||||
Unknown State = Pending // Feature is enabled
|
||||
On State = Success // Feature is enabled
|
||||
Off State = Fail // Feature is disabled
|
||||
)
|
||||
|
||||
// Table Alignment Constants
|
||||
// Defines text alignment options for table content
|
||||
const (
|
||||
AlignNone Align = "none" // Center-aligned text
|
||||
AlignCenter Align = "center" // Center-aligned text
|
||||
AlignRight Align = "right" // Right-aligned text
|
||||
AlignLeft Align = "left" // Left-aligned text
|
||||
AlignDefault = AlignLeft // Left-aligned text
|
||||
)
|
||||
|
||||
const (
|
||||
Header Position = "header" // Table header section
|
||||
Row Position = "row" // Table row section
|
||||
Footer Position = "footer" // Table footer section
|
||||
)
|
||||
|
||||
const (
|
||||
LevelHeader Level = iota // Topmost line position
|
||||
LevelBody // LevelBody line position
|
||||
LevelFooter // LevelFooter line position
|
||||
)
|
||||
|
||||
const (
|
||||
LocationFirst Location = "first" // Topmost line position
|
||||
LocationMiddle Location = "middle" // LevelBody line position
|
||||
LocationEnd Location = "end" // LevelFooter line position
|
||||
)
|
||||
|
||||
const (
|
||||
SectionHeader = "header"
|
||||
SectionRow = "row"
|
||||
SectionFooter = "footer"
|
||||
)
|
||||
|
||||
// Text Wrapping Constants
|
||||
// Defines text wrapping behavior in table cells
|
||||
const (
|
||||
WrapNone = iota // No wrapping
|
||||
WrapNormal // Standard word wrapping
|
||||
WrapTruncate // Truncate text with ellipsis
|
||||
WrapBreak // Break words to fit
|
||||
)
|
||||
|
||||
// Cell Merge Constants
|
||||
// Specifies cell merging behavior in tables
|
||||
|
||||
const (
|
||||
MergeNone = iota // No merging
|
||||
MergeVertical // Merge cells vertically
|
||||
MergeHorizontal // Merge cells horizontally
|
||||
MergeBoth // Merge both vertically and horizontally
|
||||
MergeHierarchical // Hierarchical merging
|
||||
)
|
||||
|
||||
// Special Character Constants
|
||||
// Defines special characters used in formatting
|
||||
const (
|
||||
CharEllipsis = "…" // Ellipsis character for truncation
|
||||
CharBreak = "↩" // Break character for wrapping
|
||||
)
|
||||
|
||||
type Spot int
|
||||
|
||||
const (
|
||||
SpotNone Spot = iota
|
||||
SpotTopLeft
|
||||
SpotTopCenter
|
||||
SpotTopRight
|
||||
SpotBottomLeft
|
||||
SpotBottomCenter // Default for legacy SetCaption
|
||||
SpotBottomRight
|
||||
SpotLeftTop
|
||||
SpotLeftCenter
|
||||
SpotLeftBottom
|
||||
SpotRightTop
|
||||
SpotRightCenter
|
||||
SpotRIghtBottom
|
||||
)
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
// Package tw defines types and constants for table formatting and configuration,
|
||||
// including validation logic for various table properties.
|
||||
package tw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/errors"
|
||||
) // Custom error handling library
|
||||
|
||||
// Position defines where formatting applies in the table (e.g., header, footer, or rows).
|
||||
type Position string
|
||||
|
||||
// Validate checks if the Position is one of the allowed values: Header, Footer, or Row.
|
||||
func (pos Position) Validate() error {
|
||||
switch pos {
|
||||
case Header, Footer, Row:
|
||||
return nil // Valid position
|
||||
}
|
||||
// Return an error for any unrecognized position
|
||||
return errors.New("invalid position")
|
||||
}
|
||||
|
||||
// Filter defines a function type for processing cell content.
|
||||
// It takes a slice of strings (representing cell data) and returns a processed slice.
|
||||
type Filter func([]string) []string
|
||||
|
||||
// Formatter defines an interface for types that can format themselves into a string.
|
||||
// Used for custom formatting of table cell content.
|
||||
type Formatter interface {
|
||||
Format() string // Returns the formatted string representation
|
||||
}
|
||||
|
||||
// Counter defines an interface that combines io.Writer with a method to retrieve a total.
|
||||
// This is used by the WithCounter option to allow for counting lines, bytes, etc.
|
||||
type Counter interface {
|
||||
io.Writer // It must be a writer to be used in io.MultiWriter.
|
||||
Total() int
|
||||
}
|
||||
|
||||
// Align specifies the text alignment within a table cell.
|
||||
type Align string
|
||||
|
||||
// Validate checks if the Align is one of the allowed values: None, Center, Left, or Right.
|
||||
func (a Align) Validate() error {
|
||||
switch a {
|
||||
case AlignNone, AlignCenter, AlignLeft, AlignRight:
|
||||
return nil // Valid alignment
|
||||
}
|
||||
// Return an error for any unrecognized alignment
|
||||
return errors.New("invalid align")
|
||||
}
|
||||
|
||||
type Alignment []Align
|
||||
|
||||
func (a Alignment) String() string {
|
||||
var str strings.Builder
|
||||
for i, a := range a {
|
||||
if i > 0 {
|
||||
str.WriteString("; ")
|
||||
}
|
||||
str.WriteString(strconv.Itoa(i))
|
||||
str.WriteString("=")
|
||||
str.WriteString(string(a))
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
func (a Alignment) Add(aligns ...Align) Alignment {
|
||||
aa := make(Alignment, len(aligns))
|
||||
copy(aa, aligns)
|
||||
return aa
|
||||
}
|
||||
|
||||
func (a Alignment) Set(col int, align Align) Alignment {
|
||||
if col >= 0 && col < len(a) {
|
||||
a[col] = align
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Copy creates a new independent copy of the Alignment
|
||||
func (a Alignment) Copy() Alignment {
|
||||
aa := make(Alignment, len(a))
|
||||
copy(aa, a)
|
||||
return aa
|
||||
}
|
||||
|
||||
// Level indicates the vertical position of a line in the table (e.g., header, body, or footer).
|
||||
type Level int
|
||||
|
||||
// Validate checks if the Level is one of the allowed values: Header, Body, or Footer.
|
||||
func (l Level) Validate() error {
|
||||
switch l {
|
||||
case LevelHeader, LevelBody, LevelFooter:
|
||||
return nil // Valid level
|
||||
}
|
||||
// Return an error for any unrecognized level
|
||||
return errors.New("invalid level")
|
||||
}
|
||||
|
||||
// Location specifies the horizontal position of a cell or column within a table row.
|
||||
type Location string
|
||||
|
||||
// Validate checks if the Location is one of the allowed values: First, Middle, or End.
|
||||
func (l Location) Validate() error {
|
||||
switch l {
|
||||
case LocationFirst, LocationMiddle, LocationEnd:
|
||||
return nil // Valid location
|
||||
}
|
||||
// Return an error for any unrecognized location
|
||||
return errors.New("invalid location")
|
||||
}
|
||||
|
||||
type Caption struct {
|
||||
Text string
|
||||
Spot Spot
|
||||
Align Align
|
||||
Width int
|
||||
}
|
||||
|
||||
func (c Caption) WithText(text string) Caption {
|
||||
c.Text = text
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Caption) WithSpot(spot Spot) Caption {
|
||||
c.Spot = spot
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Caption) WithAlign(align Align) Caption {
|
||||
c.Align = align
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Caption) WithWidth(width int) Caption {
|
||||
c.Width = width
|
||||
return c
|
||||
}
|
||||
|
||||
type Control struct {
|
||||
Hide State
|
||||
}
|
||||
|
||||
// Compact configures compact width optimization for merged cells.
|
||||
type Compact struct {
|
||||
Merge State // Merge enables compact width calculation during cell merging, optimizing space allocation.
|
||||
}
|
||||
|
||||
// Struct holds settings for struct-based operations like AutoHeader.
|
||||
type Struct struct {
|
||||
// AutoHeader automatically extracts and sets headers from struct fields when Bulk is called with a slice of structs.
|
||||
// Uses JSON tags if present, falls back to field names (title-cased). Skips unexported or json:"-" fields.
|
||||
// Enabled by default for convenience.
|
||||
AutoHeader State
|
||||
|
||||
// Tags is a priority-ordered list of struct tag keys to check for header names.
|
||||
// The first tag found on a field will be used. Defaults to ["json", "db"].
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// Behavior defines settings that control table rendering behaviors, such as column visibility and content formatting.
|
||||
type Behavior struct {
|
||||
AutoHide State // AutoHide determines whether empty columns are hidden. Ignored in streaming mode.
|
||||
TrimSpace State // TrimSpace determines trimming of leading and trailing spaces from cell content.
|
||||
TrimLine State // TrimLine determines whether empty visual lines within a cell are collapsed.
|
||||
TrimTab State // TrimTab determines trimming of leading and trailing tabs from cell content.
|
||||
|
||||
Header Control // Header specifies control settings for the table header.
|
||||
Footer Control // Footer specifies control settings for the table footer.
|
||||
|
||||
// Compact enables optimized width calculation for merged cells, such as in horizontal merges,
|
||||
// by systematically determining the most efficient width instead of scaling by the number of columns.
|
||||
Compact Compact
|
||||
|
||||
// Structs contains settings for how struct data is processed.
|
||||
Structs Struct
|
||||
}
|
||||
|
||||
// Padding defines the spacing characters around cell content in all four directions.
|
||||
// A zero-value Padding struct will use the table's default padding unless Overwrite is true.
|
||||
type Padding struct {
|
||||
Left string
|
||||
Right string
|
||||
Top string
|
||||
Bottom string
|
||||
|
||||
// Overwrite forces tablewriter to use this padding configuration exactly as specified,
|
||||
// even when empty. When false (default), empty Padding fields will inherit defaults.
|
||||
//
|
||||
// For explicit no-padding, use the PaddingNone constant instead of setting Overwrite.
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
// Common padding configurations for convenience
|
||||
|
||||
// Equals reports whether two Padding configurations are identical in all fields.
|
||||
// This includes comparing the Overwrite flag as part of the equality check.
|
||||
func (p Padding) Equals(padding Padding) bool {
|
||||
return p.Left == padding.Left &&
|
||||
p.Right == padding.Right &&
|
||||
p.Top == padding.Top &&
|
||||
p.Bottom == padding.Bottom &&
|
||||
p.Overwrite == padding.Overwrite
|
||||
}
|
||||
|
||||
// Empty reports whether all padding strings are empty (all fields == "").
|
||||
// Note that an Empty padding may still take effect if Overwrite is true.
|
||||
func (p Padding) Empty() bool {
|
||||
return p.Left == "" && p.Right == "" && p.Top == "" && p.Bottom == ""
|
||||
}
|
||||
|
||||
// Paddable reports whether this Padding configuration should override existing padding.
|
||||
// Returns true if either:
|
||||
// - Any padding string is non-empty (!p.Empty())
|
||||
// - Overwrite flag is true (even with all strings empty)
|
||||
//
|
||||
// This is used internally during configuration merging to determine whether to
|
||||
// apply the padding settings.
|
||||
func (p Padding) Paddable() bool {
|
||||
return !p.Empty() || p.Overwrite
|
||||
}
|
||||
|
||||
// LineCounter is the default implementation of the Counter interface.
|
||||
// It counts the number of newline characters written to it.
|
||||
type LineCounter struct {
|
||||
count int
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface, counting newlines in the input.
|
||||
// It uses a pointer receiver to modify the internal count.
|
||||
func (lc *LineCounter) Write(p []byte) (n int, err error) {
|
||||
lc.count += bytes.Count(p, []byte{'\n'})
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Total implements the Counter interface, returning the final count.
|
||||
func (lc *LineCounter) Total() int {
|
||||
return lc.count
|
||||
}
|
||||
Reference in New Issue
Block a user