1package rediscmd
2
3import (
4	"encoding/hex"
5	"fmt"
6	"strconv"
7	"strings"
8	"time"
9
10	"github.com/go-redis/redis/v8"
11)
12
13func CmdString(cmd redis.Cmder) string {
14	b := make([]byte, 0, 32)
15	b = AppendCmd(b, cmd)
16	return String(b)
17}
18
19func CmdsString(cmds []redis.Cmder) (string, string) {
20	const numCmdLimit = 100
21	const numNameLimit = 10
22
23	seen := make(map[string]struct{}, numNameLimit)
24	unqNames := make([]string, 0, numNameLimit)
25
26	b := make([]byte, 0, 32*len(cmds))
27
28	for i, cmd := range cmds {
29		if i > numCmdLimit {
30			break
31		}
32
33		if i > 0 {
34			b = append(b, '\n')
35		}
36		b = AppendCmd(b, cmd)
37
38		if len(unqNames) >= numNameLimit {
39			continue
40		}
41
42		name := cmd.FullName()
43		if _, ok := seen[name]; !ok {
44			seen[name] = struct{}{}
45			unqNames = append(unqNames, name)
46		}
47	}
48
49	summary := strings.Join(unqNames, " ")
50	return summary, String(b)
51}
52
53func AppendCmd(b []byte, cmd redis.Cmder) []byte {
54	const numArgLimit = 32
55
56	for i, arg := range cmd.Args() {
57		if i > numArgLimit {
58			break
59		}
60		if i > 0 {
61			b = append(b, ' ')
62		}
63		b = appendArg(b, arg)
64	}
65
66	if err := cmd.Err(); err != nil {
67		b = append(b, ": "...)
68		b = append(b, err.Error()...)
69	}
70
71	return b
72}
73
74func appendArg(b []byte, v interface{}) []byte {
75	const argLenLimit = 64
76
77	switch v := v.(type) {
78	case nil:
79		return append(b, "<nil>"...)
80	case string:
81		if len(v) > argLenLimit {
82			v = v[:argLenLimit]
83		}
84		return appendUTF8String(b, Bytes(v))
85	case []byte:
86		if len(v) > argLenLimit {
87			v = v[:argLenLimit]
88		}
89		return appendUTF8String(b, v)
90	case int:
91		return strconv.AppendInt(b, int64(v), 10)
92	case int8:
93		return strconv.AppendInt(b, int64(v), 10)
94	case int16:
95		return strconv.AppendInt(b, int64(v), 10)
96	case int32:
97		return strconv.AppendInt(b, int64(v), 10)
98	case int64:
99		return strconv.AppendInt(b, v, 10)
100	case uint:
101		return strconv.AppendUint(b, uint64(v), 10)
102	case uint8:
103		return strconv.AppendUint(b, uint64(v), 10)
104	case uint16:
105		return strconv.AppendUint(b, uint64(v), 10)
106	case uint32:
107		return strconv.AppendUint(b, uint64(v), 10)
108	case uint64:
109		return strconv.AppendUint(b, v, 10)
110	case float32:
111		return strconv.AppendFloat(b, float64(v), 'f', -1, 64)
112	case float64:
113		return strconv.AppendFloat(b, v, 'f', -1, 64)
114	case bool:
115		if v {
116			return append(b, "true"...)
117		}
118		return append(b, "false"...)
119	case time.Time:
120		return v.AppendFormat(b, time.RFC3339Nano)
121	default:
122		return append(b, fmt.Sprint(v)...)
123	}
124}
125
126func appendUTF8String(dst []byte, src []byte) []byte {
127	if isSimple(src) {
128		dst = append(dst, src...)
129		return dst
130	}
131
132	s := len(dst)
133	dst = append(dst, make([]byte, hex.EncodedLen(len(src)))...)
134	hex.Encode(dst[s:], src)
135	return dst
136}
137
138func isSimple(b []byte) bool {
139	for _, c := range b {
140		if !isSimpleByte(c) {
141			return false
142		}
143	}
144	return true
145}
146
147func isSimpleByte(c byte) bool {
148	return c >= 0x21 && c <= 0x7e
149}
150