Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/profile-table/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ func main() {
numRenders := 100000
if len(os.Args) > 1 {
var err error
numRenders, err = strconv.Atoi(os.Args[2])
numRenders, err = strconv.Atoi(os.Args[1])
if err != nil {
fmt.Printf("Invalid Argument: '%s'\n", os.Args[2])
fmt.Printf("Invalid Argument: '%s'\n", os.Args[1])
os.Exit(1)
}
}
Expand Down
2 changes: 1 addition & 1 deletion table/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ func (t *Table) renderLineMergeOutputs(out *strings.Builder, outLine *strings.Bu
}

func (t *Table) renderMarginLeft(out *strings.Builder, hint renderHint) {
out.WriteString(t.style.Format.Direction.Modifier())
out.WriteString(t.directionModifier)
if t.style.Options.DrawBorder {
border := t.getBorderLeft(hint)
colors := t.getBorderColors(hint)
Expand Down
38 changes: 18 additions & 20 deletions table/render_bidi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,25 @@ func TestTable_Render_BiDiText(t *testing.T) {
+---+------------+------+--------+-----------+`)

table.Style().Format.Direction = text.LeftToRight
compareOutput(t, table.Render(), `
‪+---+------------+------+--------+-----------+
‪| | ‪תאריך | ‪סכום | ‪מחלקה | ‪תגים |
‪+---+------------+------+--------+-----------+
‪| 1 | ‪2020-01-01 | ‪5 | ‪מחלקה1 | ‪[תג1 תג2] |
‪| 2 | ‪2021-02-01 | ‪5 | ‪מחלקה1 | ‪[תג1] |
‪| 3 | ‪2022-03-01 | ‪5 | ‪מחלקה2 | ‪[תג1] |
‪+---+------------+------+--------+-----------+
‪| | ‪סהכ | ‪30 | | |
‪+---+------------+------+--------+-----------+`)
compareOutput(t, table.Render(), "\u202A+---+------------+------+--------+-----------+\n"+
"\u202A| | \u202Aתאריך | \u202Aסכום | \u202Aמחלקה | \u202Aתגים |\n"+
"\u202A+---+------------+------+--------+-----------+\n"+
"\u202A| 1 | \u202A2020-01-01 | \u202A5 | \u202Aמחלקה1 | \u202A[תג1 תג2] |\n"+
"\u202A| 2 | \u202A2021-02-01 | \u202A5 | \u202Aמחלקה1 | \u202A[תג1] |\n"+
"\u202A| 3 | \u202A2022-03-01 | \u202A5 | \u202Aמחלקה2 | \u202A[תג1] |\n"+
"\u202A+---+------------+------+--------+-----------+\n"+
"\u202A| | \u202Aסהכ | \u202A30 | | |\n"+
"\u202A+---+------------+------+--------+-----------+")

table.Style().Format.Direction = text.RightToLeft
compareOutput(t, table.Render(), `
‫+---+------------+------+--------+-----------+
‫| | ‫תאריך | ‫סכום | ‫מחלקה | ‫תגים |
‫+---+------------+------+--------+-----------+
‫| 1 | ‫2020-01-01 | ‫5 | ‫מחלקה1 | ‫[תג1 תג2] |
‫| 2 | ‫2021-02-01 | ‫5 | ‫מחלקה1 | ‫[תג1] |
‫| 3 | ‫2022-03-01 | ‫5 | ‫מחלקה2 | ‫[תג1] |
‫+---+------------+------+--------+-----------+
‫| | ‫סהכ | ‫30 | | |
‫+---+------------+------+--------+-----------+`)
compareOutput(t, table.Render(), "\u202B+---+------------+------+--------+-----------+\n"+
"\u202B| | \u202Bתאריך | \u202Bסכום | \u202Bמחלקה | \u202Bתגים |\n"+
"\u202B+---+------------+------+--------+-----------+\n"+
"\u202B| 1 | \u202B2020-01-01 | \u202B5 | \u202Bמחלקה1 | \u202B[תג1 תג2] |\n"+
"\u202B| 2 | \u202B2021-02-01 | \u202B5 | \u202Bמחלקה1 | \u202B[תג1] |\n"+
"\u202B| 3 | \u202B2022-03-01 | \u202B5 | \u202Bמחלקה2 | \u202B[תג1] |\n"+
"\u202B+---+------------+------+--------+-----------+\n"+
"\u202B| | \u202Bסהכ | \u202B30 | | |\n"+
"\u202B+---+------------+------+--------+-----------+")
// sonar: ignore to here
}
11 changes: 9 additions & 2 deletions table/render_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,15 @@ func (t *Table) analyzeAndStringifyColumn(colIdx int, col interface{}, hint rend
} else if colStrVal, ok := col.(string); ok {
colStr = colStrVal
} else {
colStr = fmt.Sprint(col)
colStr = convertValueToString(col)
}
colStr = strings.ReplaceAll(colStr, "\t", " ")
colStr = text.ProcessCRLF(colStr)
return fmt.Sprintf("%s%s", t.style.Format.Direction.Modifier(), colStr)
// Avoid fmt.Sprintf when direction modifier is empty (most common case)
if t.directionModifier == "" {
return colStr
}
return t.directionModifier + colStr
}

func (t *Table) extractMaxColumnLengths(rows []rowStr, hint renderHint) {
Expand Down Expand Up @@ -156,6 +160,9 @@ func (t *Table) initForRender() {
// reset rendering state
t.reset()

// cache the direction modifier to avoid repeated calls
t.directionModifier = t.style.Format.Direction.Modifier()

// initialize the column configs and normalize them
t.initForRenderColumnConfigs()

Expand Down
2 changes: 1 addition & 1 deletion table/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (t *Table) getSortedRowIndices() []int {
sortedIndices[idx] = idx
}

if t.sortBy != nil && len(t.sortBy) > 0 {
if len(t.sortBy) > 0 {
sort.Sort(rowsSorter{
rows: t.rows,
sortBy: t.parseSortBy(t.sortBy),
Expand Down
8 changes: 5 additions & 3 deletions table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type Table struct {
// columnConfigMap stores the custom-configuration by column
// number and is generated before rendering
columnConfigMap map[int]ColumnConfig
// directionModifier caches the direction modifier string to avoid repeated calls
directionModifier string
// firstRowOfPage tells if the renderer is on the first row of a page?
firstRowOfPage bool
// htmlCSSClass stores the HTML CSS Class to use on the <table> node
Expand Down Expand Up @@ -305,12 +307,12 @@ func (t *Table) SetRowPainter(painter interface{}) {
t.rowPainterWithAttributes = nil

// if called as SetRowPainter(RowPainter(func...))
switch painter.(type) {
switch p := painter.(type) {
case RowPainter:
t.rowPainter = painter.(RowPainter)
t.rowPainter = p
return
case RowPainterWithAttributes:
t.rowPainterWithAttributes = painter.(RowPainterWithAttributes)
t.rowPainterWithAttributes = p
return
}

Expand Down
44 changes: 44 additions & 0 deletions table/util.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package table

import (
"fmt"
"reflect"
"sort"
"strconv"
)

// AutoIndexColumnID returns a unique Column ID/Name for the given Column Number.
Expand All @@ -26,6 +28,48 @@ func widthEnforcerNone(col string, _ int) string {
return col
}

// convertValueToString converts a value to string using fast type assertions
// for common numeric types before falling back to fmt.Sprint.
//
//gocyclo:ignore
func convertValueToString(v interface{}) string {
switch val := v.(type) {
case int:
return strconv.FormatInt(int64(val), 10)
case int8:
return strconv.FormatInt(int64(val), 10)
case int16:
return strconv.FormatInt(int64(val), 10)
case int32:
return strconv.FormatInt(int64(val), 10)
case int64:
return strconv.FormatInt(val, 10)
case uint:
return strconv.FormatUint(uint64(val), 10)
case uint8:
return strconv.FormatUint(uint64(val), 10)
case uint16:
return strconv.FormatUint(uint64(val), 10)
case uint32:
return strconv.FormatUint(uint64(val), 10)
case uint64:
return strconv.FormatUint(val, 10)
case float32:
return strconv.FormatFloat(float64(val), 'g', -1, 32)
case float64:
return strconv.FormatFloat(val, 'g', -1, 64)
case bool:
if val {
return "true"
}
return "false"
case string:
return val
default:
return fmt.Sprint(v)
}
}

// isNumber returns true if the argument is a numeric type; false otherwise.
func isNumber(x interface{}) bool {
if x == nil {
Expand Down
96 changes: 96 additions & 0 deletions table/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,102 @@ func TestIsNumber(t *testing.T) {
assert.False(t, isNumber(nil))
}

func Test_convertValueToString(t *testing.T) {
t.Run("int types", func(t *testing.T) {
assert.Equal(t, "0", convertValueToString(int(0)))
assert.Equal(t, "42", convertValueToString(int(42)))
assert.Equal(t, "-42", convertValueToString(int(-42)))
assert.Equal(t, "-128", convertValueToString(int8(-128)))
assert.Equal(t, "127", convertValueToString(int8(127)))
assert.Equal(t, "-32768", convertValueToString(int16(-32768)))
assert.Equal(t, "32767", convertValueToString(int16(32767)))
assert.Equal(t, "-2147483648", convertValueToString(int32(-2147483648)))
assert.Equal(t, "2147483647", convertValueToString(int32(2147483647)))
assert.Equal(t, "-9223372036854775808", convertValueToString(int64(-9223372036854775808)))
assert.Equal(t, "9223372036854775807", convertValueToString(int64(9223372036854775807)))
})

t.Run("uint types", func(t *testing.T) {
assert.Equal(t, "0", convertValueToString(uint(0)))
assert.Equal(t, "42", convertValueToString(uint(42)))
assert.Equal(t, "255", convertValueToString(uint8(255)))
assert.Equal(t, "0", convertValueToString(uint8(0)))
assert.Equal(t, "65535", convertValueToString(uint16(65535)))
assert.Equal(t, "4294967295", convertValueToString(uint32(4294967295)))
assert.Equal(t, "18446744073709551615", convertValueToString(uint64(18446744073709551615)))
})

t.Run("float types", func(t *testing.T) {
assert.Equal(t, "0", convertValueToString(float32(0)))
assert.Equal(t, "3.14", convertValueToString(float32(3.14)))
assert.Equal(t, "-3.14", convertValueToString(float32(-3.14)))
assert.Equal(t, "0", convertValueToString(float64(0)))
assert.Equal(t, "3.141592653589793", convertValueToString(float64(3.141592653589793)))
assert.Equal(t, "-3.141592653589793", convertValueToString(float64(-3.141592653589793)))
// Test scientific notation
assert.Contains(t, convertValueToString(float64(1e10)), "1e+10")
assert.Contains(t, convertValueToString(float32(1e10)), "1e+10")
})

t.Run("bool", func(t *testing.T) {
assert.Equal(t, "true", convertValueToString(bool(true)))
assert.Equal(t, "false", convertValueToString(bool(false)))
})

t.Run("default case - string", func(t *testing.T) {
assert.Equal(t, "hello", convertValueToString("hello"))
assert.Equal(t, "world", convertValueToString("world"))
assert.Equal(t, "", convertValueToString(""))
})

t.Run("default case - nil", func(t *testing.T) {
assert.Equal(t, "<nil>", convertValueToString(nil))
})

t.Run("default case - slice", func(t *testing.T) {
assert.Equal(t, "[1 2 3]", convertValueToString([]int{1, 2, 3}))
assert.Equal(t, "[]", convertValueToString([]string{}))
})

t.Run("default case - map", func(t *testing.T) {
assert.Equal(t, "map[a:1]", convertValueToString(map[string]int{"a": 1}))
assert.Equal(t, "map[]", convertValueToString(map[string]int{}))
})

t.Run("default case - struct", func(t *testing.T) {
type testStruct struct {
Field1 string
Field2 int
}
result := convertValueToString(testStruct{Field1: "test", Field2: 42})
assert.Contains(t, result, "test")
assert.Contains(t, result, "42")
})

t.Run("default case - pointer", func(t *testing.T) {
x := 42
result := convertValueToString(&x)
// fmt.Sprint of a pointer shows the address, which includes "0x"
assert.Contains(t, result, "0x")
assert.NotEmpty(t, result)
})

t.Run("edge cases", func(t *testing.T) {
// Zero values
assert.Equal(t, "0", convertValueToString(int(0)))
assert.Equal(t, "0", convertValueToString(uint(0)))
assert.Equal(t, "0", convertValueToString(float32(0)))
assert.Equal(t, "0", convertValueToString(float64(0)))
assert.Equal(t, "false", convertValueToString(bool(false)))

// Max values
assert.Equal(t, "127", convertValueToString(int8(127)))
assert.Equal(t, "255", convertValueToString(uint8(255)))
assert.Equal(t, "32767", convertValueToString(int16(32767)))
assert.Equal(t, "65535", convertValueToString(uint16(65535)))
})
}

func Test_objAsSlice(t *testing.T) {
a, b, c := 1, 2, 3
assert.Equal(t, "[1 2 3]", fmt.Sprint(objAsSlice([]int{a, b, c})))
Expand Down