mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-09 07:02:29 +08:00
feat: move new functions from v2 version
This commit is contained in:
@@ -4,14 +4,176 @@
|
||||
// Package formatter implements some functions to format string, struct.
|
||||
package formatter
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
// Comma add comma to number by every 3 numbers from right. ahead by symbol char
|
||||
func Comma(v interface{}, symbol string) string {
|
||||
s := numString(v)
|
||||
dotIndex := strings.Index(s, ".")
|
||||
if dotIndex != -1 {
|
||||
return symbol + commaString(s[:dotIndex]) + s[dotIndex:]
|
||||
"github.com/duke-git/lancet/convertor"
|
||||
"github.com/duke-git/lancet/strutil"
|
||||
"github.com/duke-git/lancet/validator"
|
||||
)
|
||||
|
||||
// Comma add comma to a number value by every 3 numbers from right. ahead by symbol char.
|
||||
// if value is invalid number string eg "aa", return empty string
|
||||
// Comma("12345", "$") => "$12,345", Comma(12345, "$") => "$12,345"
|
||||
func Comma(value interface{}, symbol string) string {
|
||||
if validator.IsInt(value) {
|
||||
v, err := convertor.ToInt(value)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return symbol + commaInt(v)
|
||||
}
|
||||
return symbol + commaString(s)
|
||||
|
||||
if validator.IsFloat(value) {
|
||||
v, err := convertor.ToFloat(value)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return symbol + commaFloat(v)
|
||||
}
|
||||
|
||||
if strutil.IsString(value) {
|
||||
v := fmt.Sprintf("%v", value)
|
||||
if validator.IsNumberStr(v) {
|
||||
return symbol + commaStr(v)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Pretty data to JSON string.
|
||||
func Pretty(v interface{}) (string, error) {
|
||||
out, err := json.MarshalIndent(v, "", " ")
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
// PrettyToWriter pretty encode data to writer.
|
||||
func PrettyToWriter(v interface{}, out io.Writer) error {
|
||||
enc := json.NewEncoder(out)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// http://en.wikipedia.org/wiki/Binary_prefix
|
||||
const (
|
||||
// Decimal
|
||||
UnitB = 1
|
||||
UnitKB = 1000
|
||||
UnitMB = 1000 * UnitKB
|
||||
UnitGB = 1000 * UnitMB
|
||||
UnitTB = 1000 * UnitGB
|
||||
UnitPB = 1000 * UnitTB
|
||||
UnitEB = 1000 * UnitPB
|
||||
|
||||
// Binary
|
||||
UnitBiB = 1
|
||||
UnitKiB = 1024
|
||||
UnitMiB = 1024 * UnitKiB
|
||||
UnitGiB = 1024 * UnitMiB
|
||||
UnitTiB = 1024 * UnitGiB
|
||||
UnitPiB = 1024 * UnitTiB
|
||||
UnitEiB = 1024 * UnitPiB
|
||||
)
|
||||
|
||||
// type byteUnitMap map[byte]int64
|
||||
|
||||
var (
|
||||
decimalByteMap = map[string]uint64{
|
||||
"b": UnitB,
|
||||
"kb": UnitKB,
|
||||
"mb": UnitMB,
|
||||
"gb": UnitGB,
|
||||
"tb": UnitTB,
|
||||
"pb": UnitPB,
|
||||
"eb": UnitEB,
|
||||
|
||||
// Without suffix
|
||||
"": UnitB,
|
||||
"k": UnitKB,
|
||||
"m": UnitMB,
|
||||
"g": UnitGB,
|
||||
"t": UnitTB,
|
||||
"p": UnitPB,
|
||||
"e": UnitEB,
|
||||
}
|
||||
|
||||
binaryByteMap = map[string]uint64{
|
||||
"bi": UnitBiB,
|
||||
"kib": UnitKiB,
|
||||
"mib": UnitMiB,
|
||||
"gib": UnitGiB,
|
||||
"tib": UnitTiB,
|
||||
"pib": UnitPiB,
|
||||
"eib": UnitEiB,
|
||||
|
||||
// Without suffix
|
||||
"": UnitBiB,
|
||||
"ki": UnitKiB,
|
||||
"mi": UnitMiB,
|
||||
"gi": UnitGiB,
|
||||
"ti": UnitTiB,
|
||||
"pi": UnitPiB,
|
||||
"ei": UnitEiB,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
decimalByteUnits = []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||
binaryByteUnits = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
|
||||
)
|
||||
|
||||
// DecimalBytes returns a human readable byte size under decimal standard (base 1000)
|
||||
// The precision parameter specifies the number of digits after the decimal point, which defaults to 4.
|
||||
// Play: https://go.dev/play/p/FPXs1suwRcs
|
||||
func DecimalBytes(size float64, precision ...int) string {
|
||||
p := 5
|
||||
if len(precision) > 0 {
|
||||
p = precision[0] + 1
|
||||
}
|
||||
size, unit := calculateByteSize(size, 1000.0, decimalByteUnits)
|
||||
|
||||
return fmt.Sprintf("%.*g%s", p, size, unit)
|
||||
}
|
||||
|
||||
// BinaryBytes returns a human-readable byte size under binary standard (base 1024)
|
||||
// The precision parameter specifies the number of digits after the decimal point, which defaults to 4.
|
||||
func BinaryBytes(size float64, precision ...int) string {
|
||||
p := 5
|
||||
if len(precision) > 0 {
|
||||
p = precision[0] + 1
|
||||
}
|
||||
size, unit := calculateByteSize(size, 1024.0, binaryByteUnits)
|
||||
|
||||
return fmt.Sprintf("%.*g%s", p, size, unit)
|
||||
}
|
||||
|
||||
func calculateByteSize(size float64, base float64, byteUnits []string) (float64, string) {
|
||||
i := 0
|
||||
unitsLimit := len(byteUnits) - 1
|
||||
for size >= base && i < unitsLimit {
|
||||
size = size / base
|
||||
i++
|
||||
}
|
||||
return size, byteUnits[i]
|
||||
}
|
||||
|
||||
// ParseDecimalBytes return the human readable bytes size string into the amount it represents(base 1000).
|
||||
// ParseDecimalBytes("42 MB") -> 42000000, nil
|
||||
func ParseDecimalBytes(size string) (uint64, error) {
|
||||
return parseBytes(size, "decimal")
|
||||
}
|
||||
|
||||
// ParseBinaryBytes return the human readable bytes size string into the amount it represents(base 1024).
|
||||
// ParseBinaryBytes("42 mib") -> 44040192, nil
|
||||
func ParseBinaryBytes(size string) (uint64, error) {
|
||||
return parseBytes(size, "binary")
|
||||
}
|
||||
|
||||
@@ -1,40 +1,134 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func commaString(s string) string {
|
||||
// see https://github.com/dustin/go-humanize/blob/master/comma.go
|
||||
func commaInt(v int64) string {
|
||||
sign := ""
|
||||
|
||||
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
||||
if v == math.MinInt64 {
|
||||
return "-9,223,372,036,854,775,808"
|
||||
}
|
||||
|
||||
if v < 0 {
|
||||
sign = "-"
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
parts := []string{"", "", "", "", "", "", ""}
|
||||
j := len(parts) - 1
|
||||
|
||||
for v > 999 {
|
||||
parts[j] = strconv.FormatInt(v%1000, 10)
|
||||
switch len(parts[j]) {
|
||||
case 2:
|
||||
parts[j] = "0" + parts[j]
|
||||
case 1:
|
||||
parts[j] = "00" + parts[j]
|
||||
}
|
||||
v = v / 1000
|
||||
j--
|
||||
}
|
||||
parts[j] = strconv.Itoa(int(v))
|
||||
return sign + strings.Join(parts[j:], ",")
|
||||
}
|
||||
|
||||
func commaFloat(v float64) string {
|
||||
buf := &bytes.Buffer{}
|
||||
if v < 0 {
|
||||
buf.Write([]byte{'-'})
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
comma := []byte{','}
|
||||
|
||||
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
|
||||
pos := 0
|
||||
if len(parts[0])%3 != 0 {
|
||||
pos += len(parts[0]) % 3
|
||||
buf.WriteString(parts[0][:pos])
|
||||
buf.Write(comma)
|
||||
}
|
||||
for ; pos < len(parts[0]); pos += 3 {
|
||||
buf.WriteString(parts[0][pos : pos+3])
|
||||
buf.Write(comma)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
if len(parts) > 1 {
|
||||
buf.Write([]byte{'.'})
|
||||
buf.WriteString(parts[1])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func commaStr(s string) string {
|
||||
dotIndex := strings.Index(s, ".")
|
||||
if dotIndex != -1 {
|
||||
return commaStrRecursive(s[:dotIndex]) + s[dotIndex:]
|
||||
}
|
||||
|
||||
return commaStrRecursive(s)
|
||||
}
|
||||
|
||||
func commaStrRecursive(s string) string {
|
||||
if len(s) <= 3 {
|
||||
return s
|
||||
}
|
||||
return commaString(s[:len(s)-3]) + "," + commaString(s[len(s)-3:])
|
||||
return commaStrRecursive(s[:len(s)-3]) + "," + commaStrRecursive(s[len(s)-3:])
|
||||
}
|
||||
|
||||
func numString(value interface{}) string {
|
||||
switch reflect.TypeOf(value).Kind() {
|
||||
case reflect.Int, reflect.Int64, reflect.Float32, reflect.Float64:
|
||||
return fmt.Sprintf("%v", value)
|
||||
case reflect.String:
|
||||
{
|
||||
sv := fmt.Sprintf("%v", value)
|
||||
if strings.Contains(sv, ".") {
|
||||
_, err := strconv.ParseFloat(sv, 64)
|
||||
if err == nil {
|
||||
return sv
|
||||
}
|
||||
} else {
|
||||
_, err := strconv.ParseInt(sv, 10, 64)
|
||||
if err == nil {
|
||||
return sv
|
||||
}
|
||||
}
|
||||
// see https://github.com/dustin/go-humanize/blob/master/bytes.go
|
||||
func parseBytes(s string, kind string) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
default:
|
||||
return ""
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
return ""
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
|
||||
if kind == "decimal" {
|
||||
if m, ok := decimalByteMap[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
} else {
|
||||
if m, ok := binaryByteMap[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/internal"
|
||||
@@ -12,12 +13,148 @@ func TestComma(t *testing.T) {
|
||||
assert.Equal("", Comma("", ""))
|
||||
assert.Equal("", Comma("aa", ""))
|
||||
assert.Equal("", Comma("aa.a", ""))
|
||||
assert.Equal("", Comma([]int{1}, ""))
|
||||
assert.Equal("123", Comma("123", ""))
|
||||
assert.Equal("12,345", Comma("12345", ""))
|
||||
assert.Equal("12,345.6789", Comma("12345.6789", ""))
|
||||
assert.Equal("123,456,789,000", Comma("123456789000", ""))
|
||||
assert.Equal("12,345,678.9", Comma("12345678.9", ""))
|
||||
|
||||
assert.Equal("12,345", Comma(12345, ""))
|
||||
assert.Equal("$12,345", Comma(12345, "$"))
|
||||
assert.Equal("¥12,345", Comma(12345, "¥"))
|
||||
assert.Equal("12,345.6789", Comma(12345.6789, ""))
|
||||
assert.Equal("12,345.6789", Comma(+12345.6789, ""))
|
||||
assert.Equal("12,345,678.9", Comma(12345678.9, ""))
|
||||
assert.Equal("123,456,789,000", Comma(123456789000, ""))
|
||||
}
|
||||
|
||||
func TestPretty(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestPretty")
|
||||
|
||||
cases := []interface{}{
|
||||
"",
|
||||
"abc",
|
||||
123,
|
||||
[]string{"a", "b", "c"},
|
||||
map[string]int{"a": 1},
|
||||
struct {
|
||||
Abc int `json:"abc"`
|
||||
}{Abc: 123},
|
||||
}
|
||||
|
||||
expects := []string{
|
||||
"\"\"",
|
||||
`"abc"`,
|
||||
"123",
|
||||
"[\n \"a\",\n \"b\",\n \"c\"\n]",
|
||||
"{\n \"a\": 1\n}",
|
||||
"{\n \"abc\": 123\n}",
|
||||
}
|
||||
|
||||
for i, v := range cases {
|
||||
result, err := Pretty(v)
|
||||
|
||||
assert.IsNil(err)
|
||||
|
||||
t.Log("result -> ", result)
|
||||
assert.Equal(expects[i], result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrettyToWriter(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestPrettyToWriter")
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Aage uint `json:"age"`
|
||||
}
|
||||
user := User{Name: "King", Aage: 10000}
|
||||
|
||||
expects := "{\n \"name\": \"King\",\n \"age\": 10000\n}\n"
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := PrettyToWriter(user, buf)
|
||||
|
||||
assert.IsNil(err)
|
||||
assert.Equal(expects, buf.String())
|
||||
}
|
||||
|
||||
func TestDecimalBytes(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestDecimalBytes")
|
||||
|
||||
assert.Equal("1KB", DecimalBytes(1000))
|
||||
assert.Equal("1.024KB", DecimalBytes(1024))
|
||||
assert.Equal("1.2346MB", DecimalBytes(1234567))
|
||||
assert.Equal("1.235MB", DecimalBytes(1234567, 3))
|
||||
assert.Equal("1.123GB", DecimalBytes(float64(1.123*UnitGB)))
|
||||
assert.Equal("2.123TB", DecimalBytes(float64(2.123*UnitTB)))
|
||||
assert.Equal("3.123PB", DecimalBytes(float64(3.123*UnitPB)))
|
||||
assert.Equal("4.123EB", DecimalBytes(float64(4.123*UnitEB)))
|
||||
assert.Equal("1EB", DecimalBytes(float64(1000*UnitPB)))
|
||||
}
|
||||
|
||||
func TestBinaryBytes(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestBinaryBytes")
|
||||
|
||||
assert.Equal("1KiB", BinaryBytes(1024))
|
||||
assert.Equal("1MiB", BinaryBytes(1024*1024))
|
||||
assert.Equal("1.1774MiB", BinaryBytes(1234567))
|
||||
assert.Equal("1.18MiB", BinaryBytes(1234567, 2))
|
||||
}
|
||||
|
||||
func TestParseDecimalBytes(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestParseDecimalBytes")
|
||||
|
||||
cases := map[string]uint64{
|
||||
"12": uint64(12),
|
||||
"12 k": uint64(12000),
|
||||
"12 kb": uint64(12000),
|
||||
"12kb": uint64(12000),
|
||||
"12k": uint64(12000),
|
||||
"12K": uint64(12000),
|
||||
"12KB": uint64(12000),
|
||||
"12 K": uint64(12000),
|
||||
"12 KB": uint64(12000),
|
||||
"12 Kb": uint64(12000),
|
||||
"12 kB": uint64(12000),
|
||||
"12.2 KB": uint64(12200),
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
result, err := ParseDecimalBytes(k)
|
||||
assert.Equal(v, result)
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
_, err := ParseDecimalBytes("12 AB")
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
func TestParseBinaryBytes(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestParseBinaryBytes")
|
||||
|
||||
cases := map[string]uint64{
|
||||
"12": uint64(12),
|
||||
"12 ki": uint64(12288),
|
||||
"12 kib": uint64(12288),
|
||||
"12kib": uint64(12288),
|
||||
"12ki": uint64(12288),
|
||||
"12KI": uint64(12288),
|
||||
"12KIB": uint64(12288),
|
||||
"12KiB": uint64(12288),
|
||||
"12 Ki": uint64(12288),
|
||||
"12 KiB": uint64(12288),
|
||||
"12 Kib": uint64(12288),
|
||||
"12 kiB": uint64(12288),
|
||||
"12.2 KiB": uint64(12492),
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
result, err := ParseBinaryBytes(k)
|
||||
assert.Equal(v, result)
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
_, err := ParseDecimalBytes("12 AB")
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user