kos/entry/cli/serialize.go

251 lines
5.7 KiB
Go

package cli
import (
"bytes"
"encoding/json"
"fmt"
"git.nspix.com/golang/kos/util/pool"
"github.com/mattn/go-runewidth"
"reflect"
"strconv"
"strings"
"time"
)
func isNormalKind(kind reflect.Kind) bool {
normalKinds := []reflect.Kind{
reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int,
reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint,
reflect.Float32, reflect.Float64,
reflect.String,
}
for _, k := range normalKinds {
if k == kind {
return true
}
}
return false
}
func serializeMap(val map[any]any) ([]byte, error) {
var (
canFormat bool
width int
maxWidth int
)
canFormat = true
for k, v := range val {
if !isNormalKind(reflect.Indirect(reflect.ValueOf(k)).Kind()) || !isNormalKind(reflect.Indirect(reflect.ValueOf(v)).Kind()) {
canFormat = false
break
}
}
if !canFormat {
return json.MarshalIndent(val, "", "\t")
}
ms := make(map[string]string)
for k, v := range val {
sk := fmt.Sprint(k)
ms[sk] = fmt.Sprint(v)
width = runewidth.StringWidth(sk)
if width > maxWidth {
maxWidth = width
}
}
buffer := pool.GetBuffer()
defer pool.PutBuffer(buffer)
for k, v := range ms {
buffer.WriteString(fmt.Sprintf("%-"+strconv.Itoa(maxWidth+4)+"s %s\n", k, v))
}
return buffer.Bytes(), nil
}
func printBorder(w *bytes.Buffer, ws []int) {
for _, l := range ws {
w.WriteString("+")
w.WriteString(strings.Repeat("-", l+2))
}
w.WriteString("+\n")
}
func toString(v any) string {
switch t := v.(type) {
case float32, float64:
return fmt.Sprintf("%.2f", t)
case time.Time:
return t.Format("2006-01-02 15:04:05")
default:
return fmt.Sprint(v)
}
}
func printArray(vals [][]any) (buf []byte) {
var (
cell string
str string
widths []int
maxLength int
width int
rows [][]string
)
rows = make([][]string, 0, len(vals))
for _, value := range vals {
if len(value) > maxLength {
maxLength = len(value)
}
}
widths = make([]int, maxLength)
for _, vs := range vals {
rl := len(vs)
row := make([]string, rl)
for i, val := range vs {
str = toString(val)
if rl > 1 {
width = runewidth.StringWidth(str)
if width > widths[i] {
widths[i] = width
}
}
row[i] = str
}
rows = append(rows, row)
}
buffer := pool.GetBuffer()
defer pool.PutBuffer(buffer)
printBorder(buffer, widths)
for index, row := range rows {
size := len(row)
for i, w := range widths {
cell = ""
buffer.WriteString("|")
if size > i {
cell = row[i]
}
buffer.WriteString(" ")
buffer.WriteString(cell)
cl := runewidth.StringWidth(cell)
if w > cl {
buffer.WriteString(strings.Repeat(" ", w-cl))
}
buffer.WriteString(" ")
}
buffer.WriteString("|\n")
if index == 0 {
printBorder(buffer, widths)
}
}
printBorder(buffer, widths)
return buffer.Bytes()
}
func serializeArray(val []any) (buf []byte, err error) {
var (
ok bool
vs [][]any
normalFormat bool
isArrayElement bool
isStructElement bool
columnName string
)
normalFormat = true
for _, row := range val {
kind := reflect.Indirect(reflect.ValueOf(row)).Kind()
if !isNormalKind(kind) {
normalFormat = false
}
if kind == reflect.Array || kind == reflect.Slice {
isArrayElement = true
}
if kind == reflect.Struct {
isStructElement = true
}
}
if normalFormat {
goto __END
}
if isArrayElement {
vs = make([][]any, 0, len(val))
for _, v := range val {
rv := reflect.Indirect(reflect.ValueOf(v))
if rv.Kind() == reflect.Array || rv.Kind() == reflect.Slice {
row := make([]any, 0, rv.Len())
for i := 0; i < rv.Len(); i++ {
if isNormalKind(rv.Index(i).Kind()) || rv.Index(i).Interface() == nil {
row = append(row, rv.Index(i).Interface())
} else {
goto __END
}
}
vs = append(vs, row)
} else {
goto __END
}
}
}
if isStructElement {
vs = make([][]any, 0, len(val))
for i, v := range val {
rv := reflect.Indirect(reflect.ValueOf(v))
if rv.Kind() == reflect.Struct {
if i == 0 {
row := make([]any, 0, rv.Type().NumField())
for j := 0; j < rv.Type().NumField(); j++ {
st := rv.Type().Field(j).Tag
if columnName, ok = st.Lookup("name"); !ok {
columnName = strings.ToUpper(rv.Type().Field(j).Name)
}
row = append(row, columnName)
}
vs = append(vs, row)
}
row := make([]any, 0, rv.Type().NumField())
for j := 0; j < rv.Type().NumField(); j++ {
row = append(row, rv.Field(j).Interface())
}
vs = append(vs, row)
} else {
goto __END
}
}
}
buf = printArray(vs)
return
__END:
return json.MarshalIndent(val, "", "\t")
}
func serialize(val any) (buf []byte, err error) {
var (
refVal reflect.Value
)
refVal = reflect.Indirect(reflect.ValueOf(val))
switch refVal.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
buf = []byte(strconv.FormatInt(refVal.Int(), 10))
case reflect.Float32, reflect.Float64:
buf = []byte(strconv.FormatFloat(refVal.Float(), 'f', -1, 64))
case reflect.String:
buf = []byte(refVal.String())
case reflect.Slice, reflect.Array:
if refVal.Type().Elem().Kind() == reflect.Uint8 {
buf = refVal.Bytes()
} else {
as := make([]any, 0, refVal.Len())
for i := 0; i < refVal.Len(); i++ {
as = append(as, refVal.Index(i).Interface())
}
buf, err = serializeArray(as)
}
case reflect.Map:
ms := make(map[any]any)
keys := refVal.MapKeys()
for _, key := range keys {
ms[key.Interface()] = refVal.MapIndex(key).Interface()
}
buf, err = serializeMap(ms)
default:
buf, err = json.MarshalIndent(refVal.Interface(), "", "\t")
}
return
}