]> Cypherpunks.ru repositories - gorecfile.git/blob - r.go
Workaround for backslash in multilines
[gorecfile.git] / r.go
1 /*
2 recfile -- GNU recutils'es recfiles parser on pure Go
3 Copyright (C) 2020-2023 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 package recfile
19
20 import (
21         "bufio"
22         "errors"
23         "io"
24         "strings"
25 )
26
27 type Reader struct {
28         scanner *bufio.Scanner
29 }
30
31 // Create Reader for iterating through the records. It uses
32 // bufio.Scanner, so can read more than currently parsed records take.
33 func NewReader(r io.Reader) *Reader {
34         return &Reader{bufio.NewScanner(r)}
35 }
36
37 func getKeyValue(text string) (string, string) {
38         cols := strings.SplitN(text, ":", 2)
39         if len(cols) != 2 {
40                 return "", ""
41         }
42         k := cols[0]
43         if len(k) == 0 {
44                 return "", ""
45         }
46         if !((k[0] == '%') ||
47                 ('a' <= k[0] && k[0] <= 'z') ||
48                 ('A' <= k[0] && k[0] <= 'Z')) {
49                 return "", ""
50         }
51         for _, c := range k[1:] {
52                 if !((c == '_') ||
53                         ('a' <= c && c <= 'z') ||
54                         ('A' <= c && c <= 'Z') ||
55                         ('0' <= c && c <= '9')) {
56                         return "", ""
57                 }
58         }
59         return k, strings.TrimPrefix(cols[1], " ")
60 }
61
62 // Get next record. Each record is just a collection of fields. io.EOF
63 // is returned if there is nothing to read more.
64 func (r *Reader) Next() ([]Field, error) {
65         fields := make([]Field, 0, 1)
66         var text string
67         var name string
68         var line string
69         lines := make([]string, 0)
70         continuation := false
71         var err error
72         for {
73                 if !r.scanner.Scan() {
74                         if err := r.scanner.Err(); err != nil {
75                                 return fields, err
76                         }
77                         err = io.EOF
78                         break
79                 }
80                 text = r.scanner.Text()
81
82                 if continuation {
83                         if len(text) == 0 {
84                                 continuation = false
85                         } else if text[len(text)-1] == '\\' {
86                                 lines = append(lines, text[:len(text)-1])
87                         } else {
88                                 lines = append(lines, text)
89                                 fields = append(fields, Field{name, strings.Join(lines, "")})
90                                 lines = make([]string, 0)
91                                 continuation = false
92                         }
93                         continue
94                 }
95
96                 if len(text) > 0 && text[0] == '#' {
97                         continue
98                 }
99
100                 if len(text) > 0 && text[0] == '+' {
101                         lines = append(lines, "\n")
102                         if len(text) > 1 {
103                                 if text[1] == ' ' {
104                                         lines = append(lines, text[2:])
105                                 } else {
106                                         lines = append(lines, text[1:])
107                                 }
108                         }
109                         continue
110                 }
111
112                 if len(lines) > 0 {
113                         fields = append(fields, Field{name, strings.Join(lines, "")})
114                         lines = make([]string, 0)
115                 }
116
117                 if text == "" {
118                         break
119                 }
120
121                 name, line = getKeyValue(text)
122                 if name == "" {
123                         return fields, errors.New("invalid field format")
124                 }
125
126                 if len(line) > 0 && line[len(line)-1] == '\\' {
127                         continuation = true
128                         lines = append(lines, line[:len(line)-1])
129                 } else {
130                         lines = append(lines, line)
131                 }
132         }
133         if continuation {
134                 return fields, errors.New("left continuation")
135         }
136         if len(lines) > 0 {
137                 fields = append(fields, Field{name, strings.Join(lines, "")})
138         }
139         if len(fields) == 0 {
140                 if err == nil {
141                         return r.Next()
142                 }
143                 return fields, err
144         }
145         return fields, nil
146 }
147
148 // Same as Next(), but with unique keys and last value.
149 func (r *Reader) NextMap() (map[string]string, error) {
150         fields, err := r.Next()
151         if err != nil {
152                 return nil, err
153         }
154         m := make(map[string]string, len(fields))
155         for _, f := range fields {
156                 m[f.Name] = f.Value
157         }
158         return m, nil
159 }
160
161 // Same as Next(), but with unique keys and slices of values.
162 func (r *Reader) NextMapWithSlice() (map[string][]string, error) {
163         fields, err := r.Next()
164         if err != nil {
165                 return nil, err
166         }
167         m := make(map[string][]string)
168         for _, f := range fields {
169                 m[f.Name] = append(m[f.Name], f.Value)
170         }
171         return m, nil
172 }