diff --git a/cmd/profile-table/profile.go b/cmd/profile-table/profile.go
index da8c3493..74c68d77 100644
--- a/cmd/profile-table/profile.go
+++ b/cmd/profile-table/profile.go
@@ -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)
}
}
diff --git a/table/render.go b/table/render.go
index 4ef68f99..80d84dac 100644
--- a/table/render.go
+++ b/table/render.go
@@ -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)
diff --git a/table/render_bidi_test.go b/table/render_bidi_test.go
index 51d78250..b988af67 100644
--- a/table/render_bidi_test.go
+++ b/table/render_bidi_test.go
@@ -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
}
diff --git a/table/render_init.go b/table/render_init.go
index 333c3988..f4c58170 100644
--- a/table/render_init.go
+++ b/table/render_init.go
@@ -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) {
@@ -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()
diff --git a/table/sort.go b/table/sort.go
index 7a47765a..7356f51a 100644
--- a/table/sort.go
+++ b/table/sort.go
@@ -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),
diff --git a/table/table.go b/table/table.go
index 191dd771..c4127d44 100644
--- a/table/table.go
+++ b/table/table.go
@@ -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
node
@@ -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
}
diff --git a/table/util.go b/table/util.go
index 4636e881..b4179a20 100644
--- a/table/util.go
+++ b/table/util.go
@@ -1,8 +1,10 @@
package table
import (
+ "fmt"
"reflect"
"sort"
+ "strconv"
)
// AutoIndexColumnID returns a unique Column ID/Name for the given Column Number.
@@ -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 {
diff --git a/table/util_test.go b/table/util_test.go
index d176bca2..2bce7f44 100644
--- a/table/util_test.go
+++ b/table/util_test.go
@@ -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, "", 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})))