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
7 changes: 3 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ linters:
disable-default-exclusions: true
exclude-functions:
- '(*bytes.Buffer).Write' # always returns nil error
- '(*github.com/valyala/bytebufferpool.ByteBuffer).Write' # always returns nil error
- '(*github.com/valyala/bytebufferpool.ByteBuffer).WriteByte' # always returns nil error
- '(*github.com/valyala/bytebufferpool.ByteBuffer).WriteString' # always returns nil error
- '(*strings.Builder).WriteByte' # always returns nil error
- '(*strings.Builder).WriteString' # always returns nil error

errchkjson:
report-no-exported: true
Expand Down Expand Up @@ -200,7 +199,7 @@ linters:
- name: unchecked-type-assertion
disabled: true # TODO: Do not disable
- name: unhandled-error
arguments: ['bytes\\.Buffer\\.Write']
disabled: true
Comment thread
ReneWerner87 marked this conversation as resolved.
Comment thread
ReneWerner87 marked this conversation as resolved.
- name: enforce-switch-style
disabled: true
- name: var-naming
Expand Down
20 changes: 14 additions & 6 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"reflect"
"runtime"
"slices"
"strconv"
"strings"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -137,29 +136,38 @@ func ConvertToBytes(humanReadableString string) int {
return 0
}

var unitPrefixPos, lastNumberPos int
// loop backwards to find the last numeric character and the unit prefix
// Find the last digit position by scanning backwards
// Also identify the unit prefix position in the same pass
lastNumberPos := -1
unitPrefixPos := 0
for i := strLen - 1; i >= 0; i-- {
c := humanReadableString[i]
if c >= '0' && c <= '9' {
lastNumberPos = i
break
}
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
// Track the first letter position (unit prefix) from the end
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
unitPrefixPos = i
}
}

// No digits found
if lastNumberPos < 0 {
return 0
}

numPart := humanReadableString[:lastNumberPos+1]
var size float64

if strings.IndexByte(numPart, '.') >= 0 {
var err error
size, err = strconv.ParseFloat(numPart, 64)
size, err = ParseFloat64(numPart)
if err != nil {
return 0
}
} else {
i64, err := strconv.ParseUint(numPart, 10, 64)
i64, err := ParseUint(numPart)
if err != nil {
return 0
}
Expand Down
109 changes: 89 additions & 20 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ func ByteSize(bytes uint64) string {
}

buf := make([]byte, 0, 16)

if div == 1 {
buf = strconv.AppendUint(buf, bytes, 10)
buf = AppendUint(buf, bytes)
buf = append(buf, unit...)
return UnsafeString(buf)
}
Expand All @@ -100,10 +101,10 @@ func ByteSize(bytes uint64) string {
integer := scaled / 10
fractional := scaled % 10

buf = strconv.AppendUint(buf, integer, 10)
buf = AppendUint(buf, integer)
if fractional > 0 {
buf = append(buf, '.')
buf = strconv.AppendUint(buf, fractional, 10)
buf = AppendUint(buf, fractional)
}
buf = append(buf, unit...)
return UnsafeString(buf)
Expand All @@ -113,25 +114,25 @@ func ByteSize(bytes uint64) string {
func ToString(arg any, timeFormat ...string) string {
switch v := arg.(type) {
case int:
return strconv.Itoa(v)
return FormatInt(int64(v))
case int8:
return strconv.FormatInt(int64(v), 10)
return FormatInt8(v)
case int16:
return strconv.FormatInt(int64(v), 10)
return FormatInt16(v)
case int32:
return strconv.FormatInt(int64(v), 10)
return FormatInt32(v)
case int64:
return strconv.FormatInt(v, 10)
return FormatInt(v)
case uint:
return strconv.Itoa(int(v))
return FormatUint(uint64(v))
case uint8:
return strconv.FormatInt(int64(v), 10)
return FormatUint8(v)
case uint16:
return strconv.FormatInt(int64(v), 10)
return FormatUint16(v)
case uint32:
return strconv.FormatInt(int64(v), 10)
return FormatUint32(v)
case uint64:
return strconv.FormatInt(int64(v), 10)
return FormatUint(v)
case string:
return v
case []byte:
Expand All @@ -151,23 +152,91 @@ func ToString(arg any, timeFormat ...string) string {
return ToString(v.Interface(), timeFormat...)
case fmt.Stringer:
return v.String()
// Handle common pointer types directly to avoid reflection
case *string:
if v != nil {
return *v
}
return ""
case *int:
if v != nil {
return FormatInt(int64(*v))
}
return "0"
case *int64:
if v != nil {
return FormatInt(*v)
}
return "0"
case *uint64:
if v != nil {
return FormatUint(*v)
}
return "0"
case *float64:
if v != nil {
return strconv.FormatFloat(*v, 'f', -1, 64)
}
return "0"
case *bool:
if v != nil {
return strconv.FormatBool(*v)
}
return "false"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Handle common slice types directly to avoid reflection
case []string:
if len(v) == 0 {
return "[]"
}
var buf strings.Builder
buf.Grow(len(v) * 8) // Pre-allocate approximate size
buf.WriteByte('[')
for i, s := range v {
if i > 0 {
buf.WriteByte(' ')
}
buf.WriteString(s)
}
buf.WriteByte(']')
return buf.String()
case []int:
if len(v) == 0 {
return "[]"
}
var buf strings.Builder
buf.Grow(len(v) * 4) // Pre-allocate approximate size
buf.WriteByte('[')
for i, n := range v {
if i > 0 {
buf.WriteByte(' ')
}
buf.WriteString(FormatInt(int64(n)))
}
buf.WriteByte(']')
return buf.String()
default:
// Check if the type is a pointer by using reflection
rv := reflect.ValueOf(arg)
if rv.Kind() == reflect.Ptr && !rv.IsNil() {
kind := rv.Kind()
if kind == reflect.Ptr && !rv.IsNil() {
// Dereference the pointer and recursively call ToString
return ToString(rv.Elem().Interface(), timeFormat...)
} else if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
} else if kind == reflect.Slice || kind == reflect.Array {
// handle slices
n := rv.Len()
if n == 0 {
return "[]"
}
var buf strings.Builder
buf.WriteString("[") //nolint:errcheck // no need to check error
for i := 0; i < rv.Len(); i++ {
buf.Grow(n * 8) // Pre-allocate approximate size
buf.WriteByte('[')
for i := range n {
if i > 0 {
buf.WriteString(" ") //nolint:errcheck // no need to check error
buf.WriteByte(' ')
}
buf.WriteString(ToString(rv.Index(i).Interface())) //nolint:errcheck // no need to check error
buf.WriteString(ToString(rv.Index(i).Interface()))
}
buf.WriteString("]") //nolint:errcheck // no need to check error
buf.WriteByte(']')
return buf.String()
}

Expand Down
26 changes: 26 additions & 0 deletions convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,32 @@ func Test_ToString(t *testing.T) {
t.Parallel()
require.Equal(t, "<nil>", ToString(nil))
})

// Test nil pointer handling - nil pointers return type-specific defaults
t.Run("nil pointer to string", func(t *testing.T) {
t.Parallel()
require.Empty(t, ToString((*string)(nil)))
})
t.Run("nil pointer to int", func(t *testing.T) {
t.Parallel()
require.Equal(t, "0", ToString((*int)(nil)))
})
t.Run("nil pointer to int64", func(t *testing.T) {
t.Parallel()
require.Equal(t, "0", ToString((*int64)(nil)))
})
t.Run("nil pointer to uint64", func(t *testing.T) {
t.Parallel()
require.Equal(t, "0", ToString((*uint64)(nil)))
})
t.Run("nil pointer to float64", func(t *testing.T) {
t.Parallel()
require.Equal(t, "0", ToString((*float64)(nil)))
})
t.Run("nil pointer to bool", func(t *testing.T) {
t.Parallel()
require.Equal(t, "false", ToString((*bool)(nil)))
})
}

func TestCopyBytes(t *testing.T) {
Expand Down
Loading
Loading