diff --git a/text/transformer.go b/text/transformer.go index 193a721..ef12227 100644 --- a/text/transformer.go +++ b/text/transformer.go @@ -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 @@ -40,25 +46,15 @@ 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) @@ -66,54 +62,16 @@ func transformInt(format string, val interface{}) string { 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) @@ -121,13 +79,37 @@ func transformFloat(format string, val interface{}) string { 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 @@ -135,8 +117,13 @@ func transformFloat(format string, val interface{}) string { 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 { @@ -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 @@ -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)) } diff --git a/text/transformer_test.go b/text/transformer_test.go index b42aea4..4571354 100644 --- a/text/transformer_test.go +++ b/text/transformer_test.go @@ -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) { @@ -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) {