/
multipart.go
120 lines (102 loc) · 2.7 KB
/
multipart.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
package file
import (
"bufio"
"bytes"
"crypto/rand"
"encoding/base32"
"fmt"
"strings"
)
// Multipart is meant to contain the delimited sections of output and can
// be marshalled into a single delimited string safely and automatically
// simply by using it in a string context.
type Multipart struct {
Delimiter string
Map map[string]string
}
// MarshalText fulfills the encoding.TextMarshaler interface by
// delimiting each section of output with a unique delimiter line that
// contains a space and the key for each section. Order of sections is
// indeterminate officially (but consistent for testing, per Go). The
// special "break" delimiter is always the last line. The Delimiter is
// used if defined, otherwise one is automatically assigned.
func (o Multipart) MarshalText() ([]byte, error) {
var out string
if o.Delimiter == "" {
o.Delimiter = _base32()
}
if o.Delimiter == "" {
return nil, fmt.Errorf(`unable to get random data`)
}
for k, v := range o.Map {
out += o.Delimiter + " " + k + "\n" + v + "\n"
}
out += o.Delimiter + " break"
return []byte(out), nil
}
// UnmarshalText fulfills the encoding.TextUnmarshaler interface by
// using its internal Delimiter or sensing the delimiter as the first
// text field (up to the first space) if not set and using that
// delimiter to parse the remaining data into the key/value pairs ending
// when either the end of text is encountered or the special "break"
// delimiter is read.
func (o *Multipart) UnmarshalText(text []byte) error {
var cur string
s := bufio.NewScanner(bytes.NewReader(text))
o.Map = map[string]string{}
// detect delimiter as first line if Delimiter is unset
if o.Delimiter == "" {
if !s.Scan() {
return fmt.Errorf(`failed to scan first line`)
}
f := strings.Fields(s.Text())
if len(f) < 2 {
return fmt.Errorf(`first line is not delimiter`)
}
if f[1] == "break" {
return nil
}
o.Delimiter = f[0]
cur = f[1]
}
for s.Scan() {
line := s.Text()
// delimiter?
if strings.HasPrefix(line, o.Delimiter) {
f := strings.Fields(line)
if len(f) < 2 {
return fmt.Errorf(`delimiter missing key`)
}
if cur != "" {
o.Map[cur] = o.Map[cur][:len(o.Map[cur])-1]
}
if f[1] == `break` {
return nil
}
cur = f[1]
continue
}
if cur == "" {
continue
}
o.Map[cur] += line + "\n"
}
return nil
}
// String fulfills the fmt.Stringer interface by calling MarshalText.
func (o Multipart) String() string {
buf, err := o.MarshalText()
if err != nil {
return ""
}
return string(buf)
}
// from github.com/rwxrob/uniq
func _base32() string {
byt := make([]byte, 20)
_, err := rand.Read(byt)
if err != nil {
return ""
}
return base32.HexEncoding.EncodeToString(byt)
}