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
148 changes: 68 additions & 80 deletions text/transformer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ import (

// Transformer related constants
const (
unixTimeMinMilliseconds = int64(10000000000)
unixTimeMinMicroseconds = unixTimeMinMilliseconds * 1000
unixTimeMinNanoSeconds = unixTimeMinMicroseconds * 1000
// Pre-computed time conversion constants to avoid repeated calculations
nanosPerSecond = int64(time.Second)
microsPerSecond = nanosPerSecond / 1000
millisPerSecond = nanosPerSecond / 1000000

// Thresholds for detecting unix timestamp units (10 seconds worth in each unit)
unixTimeMinMilliseconds = 10 * nanosPerSecond
unixTimeMinMicroseconds = 10 * nanosPerSecond * 1000
unixTimeMinNanoSeconds = 10 * nanosPerSecond * 1000000
)

// Transformer related variables
Expand All @@ -40,103 +46,84 @@ type Transformer func(val interface{}) string
// - transforms the number as directed by 'format' (ex.: %.2f)
// - colors negative values Red
// - colors positive values Green
//
//gocyclo:ignore
func NewNumberTransformer(format string) Transformer {
return func(val interface{}) string {
if valStr := transformInt(format, val); valStr != "" {
return valStr
}
if valStr := transformUint(format, val); valStr != "" {
return valStr
}
if valStr := transformFloat(format, val); valStr != "" {
return valStr
}
return fmt.Sprint(val)
}
}
// Pre-compute negative format string to avoid repeated allocations
negFormat := "-" + format

func transformInt(format string, val interface{}) string {
transform := func(val int64) string {
transformInt64 := func(val int64) string {
if val < 0 {
return colorsNumberNegative.Sprintf("-"+format, -val)
return colorsNumberNegative.Sprintf(negFormat, -val)
}
if val > 0 {
return colorsNumberPositive.Sprintf(format, val)
}
return colorsNumberZero.Sprintf(format, val)
}

if number, ok := val.(int); ok {
return transform(int64(number))
}
if number, ok := val.(int8); ok {
return transform(int64(number))
}
if number, ok := val.(int16); ok {
return transform(int64(number))
}
if number, ok := val.(int32); ok {
return transform(int64(number))
}
if number, ok := val.(int64); ok {
return transform(number)
}
return ""
}

func transformUint(format string, val interface{}) string {
transform := func(val uint64) string {
transformUint64 := func(val uint64) string {
if val > 0 {
return colorsNumberPositive.Sprintf(format, val)
}
return colorsNumberZero.Sprintf(format, val)
}

if number, ok := val.(uint); ok {
return transform(uint64(number))
}
if number, ok := val.(uint8); ok {
return transform(uint64(number))
}
if number, ok := val.(uint16); ok {
return transform(uint64(number))
}
if number, ok := val.(uint32); ok {
return transform(uint64(number))
}
if number, ok := val.(uint64); ok {
return transform(number)
}
return ""
}

func transformFloat(format string, val interface{}) string {
transform := func(val float64) string {
transformFloat64 := func(val float64) string {
if val < 0 {
return colorsNumberNegative.Sprintf("-"+format, -val)
return colorsNumberNegative.Sprintf(negFormat, -val)
}
if val > 0 {
return colorsNumberPositive.Sprintf(format, val)
}
return colorsNumberZero.Sprintf(format, val)
}

if number, ok := val.(float32); ok {
return transform(float64(number))
}
if number, ok := val.(float64); ok {
return transform(number)
// Use type switch for O(1) type checking instead of sequential type assertions
return func(val interface{}) string {
switch v := val.(type) {
case int:
return transformInt64(int64(v))
case int8:
return transformInt64(int64(v))
case int16:
return transformInt64(int64(v))
case int32:
return transformInt64(int64(v))
case int64:
return transformInt64(v)
case uint:
return transformUint64(uint64(v))
case uint8:
return transformUint64(uint64(v))
case uint16:
return transformUint64(uint64(v))
case uint32:
return transformUint64(uint64(v))
case uint64:
return transformUint64(v)
case float32:
return transformFloat64(float64(v))
case float64:
return transformFloat64(v)
default:
return fmt.Sprint(val)
}
}
return ""
}

// NewJSONTransformer returns a Transformer that can format a JSON string or an
// object into pretty-indented JSON-strings.
func NewJSONTransformer(prefix string, indent string) Transformer {
return func(val interface{}) string {
if valStr, ok := val.(string); ok {
valStr = strings.TrimSpace(valStr)
// Validate JSON before attempting to indent to avoid unnecessary processing
if !json.Valid([]byte(valStr)) {
return fmt.Sprintf("%#v", valStr)
}
var b bytes.Buffer
if err := json.Indent(&b, []byte(strings.TrimSpace(valStr)), prefix, indent); err == nil {
if err := json.Indent(&b, []byte(valStr), prefix, indent); err == nil {
return b.String()
}
} else if b, err := json.MarshalIndent(val, prefix, indent); err == nil {
Expand All @@ -154,17 +141,17 @@ func NewJSONTransformer(prefix string, indent string) Transformer {
// location (use time.Local to get localized timestamps).
func NewTimeTransformer(layout string, location *time.Location) Transformer {
return func(val interface{}) string {
rsp := fmt.Sprint(val)
// Check for time.Time first to avoid unnecessary fmt.Sprint conversion
if valTime, ok := val.(time.Time); ok {
rsp = formatTime(valTime, layout, location)
} else {
// cycle through some supported layouts to see if the string form
// of the object matches any of these layouts
for _, possibleTimeLayout := range possibleTimeLayouts {
if valTime, err := time.Parse(possibleTimeLayout, rsp); err == nil {
rsp = formatTime(valTime, layout, location)
break
}
return formatTime(valTime, layout, location)
}
// Only convert to string if not already time.Time
rsp := fmt.Sprint(val)
// Cycle through some supported layouts to see if the string form
// of the object matches any of these layouts
for _, possibleTimeLayout := range possibleTimeLayouts {
if valTime, err := time.Parse(possibleTimeLayout, rsp); err == nil {
return formatTime(valTime, layout, location)
}
}
return rsp
Expand Down Expand Up @@ -217,12 +204,13 @@ func formatTime(t time.Time, layout string, location *time.Location) string {
}

func formatTimeUnix(unixTime int64, timeTransformer Transformer) string {
// Use pre-computed constants instead of repeated time.Second.Nanoseconds() calls
if unixTime >= unixTimeMinNanoSeconds {
unixTime = unixTime / time.Second.Nanoseconds()
unixTime = unixTime / nanosPerSecond
} else if unixTime >= unixTimeMinMicroseconds {
unixTime = unixTime / (time.Second.Nanoseconds() / 1000)
unixTime = unixTime / microsPerSecond
} else if unixTime >= unixTimeMinMilliseconds {
unixTime = unixTime / (time.Second.Nanoseconds() / 1000000)
unixTime = unixTime / millisPerSecond
}
return timeTransformer(time.Unix(unixTime, 0))
}
19 changes: 19 additions & 0 deletions text/transformer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ func TestNewJSONTransformer(t *testing.T) {
]
}`
assert.Equal(t, expectedOutput, transformer(input))

// Test error case: object that cannot be marshaled (channel) - triggers error path in json.MarshalIndent
ch := make(chan int)
result := transformer(ch)
assert.Contains(t, result, "chan int")

// Test error case: function that cannot be marshaled - triggers error path in json.MarshalIndent
fn := func() {}
result = transformer(fn)
assert.Contains(t, result, "func()")
}

func TestNewTimeTransformer(t *testing.T) {
Expand Down Expand Up @@ -180,6 +190,15 @@ func TestNewTimeTransformer(t *testing.T) {
for _, possibleTimeLayout := range possibleTimeLayouts {
assert.Equal(t, expected, transformer(inTime.Format(possibleTimeLayout)), possibleTimeLayout)
}

// Test case where string doesn't match any time layout (returns original string)
transformer = NewTimeTransformer(time.RFC3339, nil)
nonTimeString := "not a time string"
assert.Equal(t, nonTimeString, transformer(nonTimeString))

// Test with nil location
transformer = NewTimeTransformer(time.RFC3339, nil)
assert.Equal(t, "2010-11-12T13:14:15-07:00", transformer(inTime))
}

func TestNewUnixTimeTransformer(t *testing.T) {
Expand Down