-
Notifications
You must be signed in to change notification settings - Fork 1
/
list.go
187 lines (166 loc) · 4.22 KB
/
list.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package kv
import (
"bytes"
"context"
"fmt"
"github.com/jjeffery/kv/internal/logfmt"
"github.com/jjeffery/kv/internal/parse"
"github.com/jjeffery/kv/internal/pool"
)
// List is a slice of alternating keys and values.
type List []interface{}
// Parse parses the input and reports the message text,
// and the list of key/value pairs.
//
// The text slice, if non-nil, points to the same backing
// array as input.
func Parse(input []byte) (text []byte, list List) {
m := parse.Bytes(input)
text = m.Text
if len(m.List) > 0 {
list = make(List, len(m.List))
for i, v := range m.List {
list[i] = string(v)
}
}
m.Release()
return text, list
}
// With returns a list populated with keyvals as the key/value pairs.
func With(keyvals ...interface{}) List {
keyvals = flattenFix(keyvals)
return List(keyvals)
}
// From returns a new context with key/value pairs copied both from
// the list and the context.
func (l List) From(ctx context.Context) Context {
ctx = newContext(ctx, l)
return &contextT{ctx: ctx}
}
// Keyvals returns the list cast as []interface{}.
func (l List) Keyvals() []interface{} {
return []interface{}(l)
}
// MarshalText implements the TextMarshaler interface.
func (l List) MarshalText() (text []byte, err error) {
var buf bytes.Buffer
l.writeToBuffer(&buf)
return buf.Bytes(), nil
}
// NewError returns an error with the given message and a list of
// key/value pairs copied from the list.
func (l List) NewError(text string) Error {
e := newError(nil, nil, text)
e.list = l
return e
}
// String returns a string representation of the key/value pairs in
// logfmt format: "key1=value1 key2=value2 ...".
func (l List) String() string {
buf := pool.AllocBuffer()
defer pool.ReleaseBuffer(buf)
l.writeToBuffer(buf)
return buf.String()
}
// UnmarshalText implements the TextUnmarshaler interface.
func (l *List) UnmarshalText(text []byte) error {
m := parse.Bytes(text)
defer m.Release()
capacity := len(m.List)
if len(m.Text) == 0 {
capacity += 2
}
list := make(List, 0, capacity)
if len(m.Text) > 0 {
list = append(list, "msg", string(m.Text))
}
for _, v := range m.List {
list = append(list, string(v))
}
*l = list
return nil
}
// With returns a new list with keyvals appended. The original
// list (l) is not modified.
func (l List) With(keyvals ...interface{}) List {
keyvals = flattenFix(keyvals)
list := l.clone(len(l) + len(keyvals))
list = append(list, keyvals...)
return list
}
// Wrap wraps the error with the key/value pairs copied from the list,
// and the optional text.
func (l List) Wrap(err error, text ...string) Error {
e := newError(nil, err, text...)
e.list = l
return causer(e)
}
// Log is used to log a message. By default the message is logged
// using the standard logger in the Go "log" package.
func (l List) Log(args ...interface{}) {
logHelper(2, l, args...)
}
func (l List) clone(capacity int) List {
length := len(l)
if capacity < length {
capacity = length
}
list := make(List, length, capacity)
copy(list, l)
return list
}
func (l List) writeToBuffer(buf logfmt.Writer) {
fl := flattenFix(l)
for i := 0; i < len(fl); i += 2 {
if i > 0 {
buf.WriteRune(' ')
}
k := fl[i]
v := fl[i+1]
logfmt.WriteKeyValue(buf, k, v)
}
}
func dedup(lists ...List) List {
var (
totalLen int
)
for _, list := range lists {
totalLen += len(list)
}
if totalLen == 0 {
return nil
}
result := make(List, 0, totalLen)
m := make(map[string]map[string]struct{})
buf := pool.AllocBuffer()
defer pool.ReleaseBuffer(buf)
valueString := func(val interface{}) string {
buf.Reset()
logfmt.WriteValue(buf, val)
return buf.String()
}
for _, list := range lists {
contents := flattenFix(list)
for i := 0; i < len(contents); i += 2 {
key, ok := contents[i].(string)
if !ok {
// shouldn't happen, unless a different type is
// returned for missing keys, which might happen
// if the flatten/fix function is modified in future
key = fmt.Sprint(key)
}
val := contents[i+1]
valstr := valueString(val)
valstrs, found := m[key]
if !found {
valstrs = make(map[string]struct{})
m[key] = valstrs
}
if _, ok := valstrs[valstr]; !ok {
result = append(result, key, val)
valstrs[valstr] = struct{}{}
}
}
}
return result
}