Skip to content

Commit c1f3185

Browse files
committed
Added helper functions for getting and setting fields with int, int64, time.Time and ID values.
1 parent 64d1acb commit c1f3185

File tree

2 files changed

+874
-6
lines changed

2 files changed

+874
-6
lines changed

warcfields.go

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ package gowarc
1919
import (
2020
"fmt"
2121
"io"
22+
"net/http"
2223
"sort"
24+
"strconv"
2325
"strings"
26+
"time"
2427
)
2528

2629
type nameValue struct {
@@ -32,21 +35,64 @@ func (n *nameValue) String() string {
3235
return n.Name + ": " + n.Value
3336
}
3437

38+
// WarcFields represents the key value pairs in a WARC-record header.
39+
//
40+
// It is also used for representing the record block of records with content-type "application/warc-fields".
41+
//
42+
// All key-manipulating functions take case-insensitive keys and modify them to their canonical form.
3543
type WarcFields []*nameValue
3644

37-
// Get gets the first value associated with the given key. It is case insensitive.
38-
// If the key doesn't exist or there are no values associated with the key, Get returns "".
45+
func (wf *WarcFields) CanonicalHeaderKey(s string) string {
46+
47+
return http.CanonicalHeaderKey(s)
48+
}
49+
50+
// Get gets the first value associated with the given key. It is case-insensitive.
51+
// If the key doesn't exist or there are no values associated with the key, Get returns the empty string.
3952
// To access multiple values of a key, use GetAll.
40-
func (wf *WarcFields) Get(name string) string {
53+
func (wf *WarcFields) Get(key string) string {
54+
key, _ = normalizeName(key)
4155
for _, nv := range *wf {
42-
if nv.Name == name {
56+
if nv.Name == key {
4357
return nv.Value
4458
}
4559
}
4660
return ""
4761
}
4862

63+
// GetInt is like Get, but converts the field value to int.
64+
func (wf *WarcFields) GetInt(key string) (int, error) {
65+
if !wf.Has(key) {
66+
return 0, fmt.Errorf("missing field %s", key)
67+
}
68+
return strconv.Atoi(wf.Get(key))
69+
}
70+
71+
// GetInt64 is like Get, but converts the field value to int64.
72+
func (wf *WarcFields) GetInt64(name string) (int64, error) {
73+
if !wf.Has(name) {
74+
return 0, fmt.Errorf("missing field %s", name)
75+
}
76+
return strconv.ParseInt(wf.Get(name), 10, 64)
77+
}
78+
79+
// GetTime is like Get, but converts the field value to time.Time.
80+
// The field is expected to be in RFC 3339 format.
81+
func (wf *WarcFields) GetTime(name string) (time.Time, error) {
82+
if !wf.Has(name) {
83+
return time.Time{}, fmt.Errorf("missing field %s", name)
84+
}
85+
return time.Parse(time.RFC3339, wf.Get(name))
86+
}
87+
88+
// GetId is like Get, but removes the surrounding '<' and '>' from the field value.
89+
func (wf *WarcFields) GetId(name string) string {
90+
return strings.Trim(wf.Get(name), "<>")
91+
}
92+
93+
// GetAll returns all values associated with the given key. It is case-insensitive.
4994
func (wf *WarcFields) GetAll(name string) []string {
95+
name, _ = normalizeName(name)
5096
var result []string
5197
for _, nv := range *wf {
5298
if nv.Name == name {
@@ -56,7 +102,10 @@ func (wf *WarcFields) GetAll(name string) []string {
56102
return result
57103
}
58104

105+
// Has returns true if field exists.
106+
// This can be used to separate a missing field from a field for which value is the empty string.
59107
func (wf *WarcFields) Has(name string) bool {
108+
name, _ = normalizeName(name)
60109
for _, nv := range *wf {
61110
if nv.Name == name {
62111
return true
@@ -65,11 +114,51 @@ func (wf *WarcFields) Has(name string) bool {
65114
return false
66115
}
67116

117+
// Add adds the key, value pair to the header.
118+
// It appends to any existing values associated with key. The key is case-insensitive.
68119
func (wf *WarcFields) Add(name string, value string) {
120+
name, _ = normalizeName(name)
69121
*wf = append(*wf, &nameValue{Name: name, Value: value})
70122
}
71123

124+
// AddInt adds the key, value pair to the header.
125+
// It appends to any existing values associated with key. The key is case-insensitive.
126+
func (wf *WarcFields) AddInt(name string, value int) {
127+
wf.Add(name, strconv.Itoa(value))
128+
}
129+
130+
// AddInt64 adds the key, value pair to the header.
131+
// It appends to any existing values associated with key. The key is case-insensitive.
132+
func (wf *WarcFields) AddInt64(name string, value int64) {
133+
wf.Add(name, strconv.FormatInt(value, 10))
134+
}
135+
136+
// AddTime adds the key, value pair to the header.
137+
// It appends to any existing values associated with key. The key is case-insensitive.
138+
//
139+
// The value is converted to RFC 3339 format.
140+
func (wf *WarcFields) AddTime(name string, value time.Time) {
141+
wf.Add(name, value.UTC().Format(time.RFC3339))
142+
}
143+
144+
// AddId adds the key, value pair to the header.
145+
// It appends to any existing values associated with key. The key is case-insensitive.
146+
//
147+
// The value is surrounded with '<' and '>' if not already present.
148+
func (wf *WarcFields) AddId(name, value string) {
149+
if len(value) == 0 {
150+
return
151+
}
152+
if value[0] != '<' && value[len(value)-1] != '>' {
153+
value = "<" + value + ">"
154+
}
155+
wf.Add(name, value)
156+
}
157+
158+
// Set sets the header entries associated with key to the single element value.
159+
// It replaces any existing values associated with key. The key is case-insensitive
72160
func (wf *WarcFields) Set(name string, value string) {
161+
name, _ = normalizeName(name)
73162
isSet := false
74163
for idx, nv := range *wf {
75164
if nv.Name == name {
@@ -86,22 +175,62 @@ func (wf *WarcFields) Set(name string, value string) {
86175
}
87176
}
88177

89-
func (wf *WarcFields) Delete(name string) {
178+
// SetInt sets the header entries associated with key to the single element value.
179+
// It replaces any existing values associated with key. The key is case-insensitive
180+
func (wf *WarcFields) SetInt(name string, value int) {
181+
wf.Set(name, strconv.Itoa(value))
182+
}
183+
184+
// SetInt64 sets the header entries associated with key to the single element value.
185+
// It replaces any existing values associated with key. The key is case-insensitive
186+
func (wf *WarcFields) SetInt64(name string, value int64) {
187+
wf.Set(name, strconv.FormatInt(value, 10))
188+
}
189+
190+
// SetTime sets the header entries associated with key to the single element value.
191+
// It replaces any existing values associated with key. The key is case-insensitive
192+
//
193+
// The value is converted to RFC 3339 format.
194+
func (wf *WarcFields) SetTime(name string, value time.Time) {
195+
wf.Set(name, value.UTC().Format(time.RFC3339))
196+
}
197+
198+
// SetId sets the header entries associated with key to the single element value.
199+
// It replaces any existing values associated with key. The key is case-insensitive
200+
//
201+
// The value is surrounded with '<' and '>' if not already present.
202+
func (wf *WarcFields) SetId(name, value string) {
203+
if len(value) == 0 {
204+
return
205+
}
206+
if value[0] != '<' && value[len(value)-1] != '>' {
207+
value = "<" + value + ">"
208+
}
209+
wf.Set(name, value)
210+
}
211+
212+
// Delete deletes the values associated with key. The key is case-insensitive.
213+
func (wf *WarcFields) Delete(key string) {
214+
key, _ = normalizeName(key)
90215
var result []*nameValue
91216
for _, nv := range *wf {
92-
if nv.Name != name {
217+
if nv.Name != key {
93218
result = append(result, nv)
94219
}
95220
}
96221
*wf = result
97222
}
98223

224+
// Sort sorts the fields in lexicographical order.
225+
//
226+
// Only field names are sorted. Order of values for a repeated field is kept as is.
99227
func (wf *WarcFields) Sort() {
100228
sort.SliceStable(*wf, func(i, j int) bool {
101229
return (*wf)[i].Name < (*wf)[j].Name
102230
})
103231
}
104232

233+
// Write implements the io.Writer interface.
105234
func (wf *WarcFields) Write(w io.Writer) (bytesWritten int64, err error) {
106235
var n int
107236
for _, field := range *wf {
@@ -122,6 +251,7 @@ func (wf *WarcFields) String() string {
122251
return sb.String()
123252
}
124253

254+
// clone creates a new deep copy.
125255
func (wf WarcFields) clone() *WarcFields {
126256
r := WarcFields{}
127257
for _, p := range wf {

0 commit comments

Comments
 (0)