/
headerfile.go
135 lines (108 loc) · 2.83 KB
/
headerfile.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
// Package headerfile allows you to read a file which contains a
// small header of "key:value" lines.
//
// These kinds of files are very common when processing simple blogs,
// and similar files.
//
package headerfile
import (
"fmt"
"io/ioutil"
"regexp"
"strings"
)
// HeaderFile is the structure that encapsulates our object.
type HeaderFile struct {
// File is the filename the user wished us to read.
File string
// Headers is a map of the key/values we found in the header,
// if any were present.
headers map[string]string
// Body is the body of the text, after the headers were read.
body string
// Parsed records whether we've parsed the file already
parsed bool
}
// New creates a new object.
func New(filename string) *HeaderFile {
return &HeaderFile{File: filename,
body: "",
parsed: false,
headers: make(map[string]string)}
}
// parse parses the input file we were constructed with.
//
// This is called the first time a header/body is requested
// from the package. We don't re-read the file on-demand.
func (h *HeaderFile) parse() error {
// Read the file
bytes, err := ioutil.ReadFile(h.File)
if err != nil {
return err
}
// Compile our regular expression
headerRegex := regexp.MustCompile("^([^:=]+)[:=](.*)$")
// We're in the header by default
header := true
// Split by newlines - because we want to process the header
// specially, splitting it into fields.
lines := strings.Split(string(bytes), "\n")
// Process each line
for _, line := range lines {
// If we're in the header ..
if header {
// Empty line? Then header-time is over now.
if len(line) < 1 {
header = false
continue
}
// Find the key + value which we expect to see
// in the header.
header := headerRegex.FindStringSubmatch(line)
// If we did then we're good.
if len(header) == 3 {
// Save the key + value
key := header[1]
val := header[2]
// Normalize keys & values.
key = strings.ToLower(key)
key = strings.TrimSpace(key)
val = strings.TrimSpace(val)
h.headers[key] = val
} else {
return fmt.Errorf("malformed header '%s' in %s", line, h.File)
}
} else {
h.body += line + "\n"
}
}
return nil
}
// Headers returns the headers associated with the file.
//
// The map will have all the header-names downcased, and the values
// will be stripped of any leading/trailing whitespace.
func (h *HeaderFile) Headers() (map[string]string, error) {
if !h.parsed {
err := h.parse()
if err != nil {
return nil, err
}
h.parsed = true
}
return h.headers, nil
}
// Body returns the body of the file, which is the region after
// the header.
//
// The body will contain an extra trailing newline.
func (h *HeaderFile) Body() (string, error) {
if !h.parsed {
err := h.parse()
if err != nil {
return "", err
}
h.parsed = true
}
return h.body, nil
}