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 }