1 // recfile -- GNU recutils'es recfiles parser on pure Go
2 // Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
26 scanner *bufio.Scanner
29 // Create Reader for iterating through the records. It uses
30 // bufio.Scanner, so can read more than currently parsed records take.
31 func NewReader(r io.Reader) *Reader {
32 return &Reader{bufio.NewScanner(r)}
35 func getKeyValue(text string) (string, string) {
36 cols := strings.SplitN(text, ":", 2)
45 ('a' <= k[0] && k[0] <= 'z') ||
46 ('A' <= k[0] && k[0] <= 'Z')) {
49 for _, c := range k[1:] {
51 ('a' <= c && c <= 'z') ||
52 ('A' <= c && c <= 'Z') ||
53 ('0' <= c && c <= '9')) {
57 return k, strings.TrimPrefix(cols[1], " ")
60 // Get next record. Each record is just a collection of fields. io.EOF
61 // is returned if there is nothing to read more.
62 func (r *Reader) Next() ([]Field, error) {
63 fields := make([]Field, 0, 1)
67 lines := make([]string, 0)
71 if !r.scanner.Scan() {
72 if err := r.scanner.Err(); err != nil {
78 text = r.scanner.Text()
83 } else if text[len(text)-1] == '\\' {
84 lines = append(lines, text[:len(text)-1])
86 lines = append(lines, text)
87 fields = append(fields, Field{name, strings.Join(lines, "")})
88 lines = make([]string, 0)
94 if len(text) > 0 && text[0] == '#' {
98 if len(text) > 0 && text[0] == '+' {
99 lines = append(lines, "\n")
102 lines = append(lines, text[2:])
104 lines = append(lines, text[1:])
111 fields = append(fields, Field{name, strings.Join(lines, "")})
112 lines = make([]string, 0)
119 name, line = getKeyValue(text)
121 return fields, errors.New("invalid field format")
124 if len(line) > 0 && line[len(line)-1] == '\\' {
126 lines = append(lines, line[:len(line)-1])
128 lines = append(lines, line)
132 return fields, errors.New("left continuation")
135 fields = append(fields, Field{name, strings.Join(lines, "")})
137 if len(fields) == 0 {
146 // Same as Next(), but with unique keys and last value.
147 func (r *Reader) NextMap() (map[string]string, error) {
148 fields, err := r.Next()
152 m := make(map[string]string, len(fields))
153 for _, f := range fields {
159 // Same as Next(), but with unique keys and slices of values.
160 func (r *Reader) NextMapWithSlice() (map[string][]string, error) {
161 fields, err := r.Next()
165 m := make(map[string][]string)
166 for _, f := range fields {
167 m[f.Name] = append(m[f.Name], f.Value)