]> Cypherpunks.ru repositories - gorecfile.git/blob - r.go
Unify copyright comment format
[gorecfile.git] / r.go
1 // recfile -- GNU recutils'es recfiles parser on pure Go
2 // Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
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.
7 //
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.
12 //
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/>.
15
16 package recfile
17
18 import (
19         "bufio"
20         "errors"
21         "io"
22         "strings"
23 )
24
25 type Reader struct {
26         scanner *bufio.Scanner
27 }
28
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)}
33 }
34
35 func getKeyValue(text string) (string, string) {
36         cols := strings.SplitN(text, ":", 2)
37         if len(cols) != 2 {
38                 return "", ""
39         }
40         k := cols[0]
41         if len(k) == 0 {
42                 return "", ""
43         }
44         if !((k[0] == '%') ||
45                 ('a' <= k[0] && k[0] <= 'z') ||
46                 ('A' <= k[0] && k[0] <= 'Z')) {
47                 return "", ""
48         }
49         for _, c := range k[1:] {
50                 if !((c == '_') ||
51                         ('a' <= c && c <= 'z') ||
52                         ('A' <= c && c <= 'Z') ||
53                         ('0' <= c && c <= '9')) {
54                         return "", ""
55                 }
56         }
57         return k, strings.TrimPrefix(cols[1], " ")
58 }
59
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)
64         var text string
65         var name string
66         var line string
67         lines := make([]string, 0)
68         continuation := false
69         var err error
70         for {
71                 if !r.scanner.Scan() {
72                         if err := r.scanner.Err(); err != nil {
73                                 return fields, err
74                         }
75                         err = io.EOF
76                         break
77                 }
78                 text = r.scanner.Text()
79
80                 if continuation {
81                         if len(text) == 0 {
82                                 continuation = false
83                         } else if text[len(text)-1] == '\\' {
84                                 lines = append(lines, text[:len(text)-1])
85                         } else {
86                                 lines = append(lines, text)
87                                 fields = append(fields, Field{name, strings.Join(lines, "")})
88                                 lines = make([]string, 0)
89                                 continuation = false
90                         }
91                         continue
92                 }
93
94                 if len(text) > 0 && text[0] == '#' {
95                         continue
96                 }
97
98                 if len(text) > 0 && text[0] == '+' {
99                         lines = append(lines, "\n")
100                         if len(text) > 1 {
101                                 if text[1] == ' ' {
102                                         lines = append(lines, text[2:])
103                                 } else {
104                                         lines = append(lines, text[1:])
105                                 }
106                         }
107                         continue
108                 }
109
110                 if len(lines) > 0 {
111                         fields = append(fields, Field{name, strings.Join(lines, "")})
112                         lines = make([]string, 0)
113                 }
114
115                 if text == "" {
116                         break
117                 }
118
119                 name, line = getKeyValue(text)
120                 if name == "" {
121                         return fields, errors.New("invalid field format")
122                 }
123
124                 if len(line) > 0 && line[len(line)-1] == '\\' {
125                         continuation = true
126                         lines = append(lines, line[:len(line)-1])
127                 } else {
128                         lines = append(lines, line)
129                 }
130         }
131         if continuation {
132                 return fields, errors.New("left continuation")
133         }
134         if len(lines) > 0 {
135                 fields = append(fields, Field{name, strings.Join(lines, "")})
136         }
137         if len(fields) == 0 {
138                 if err == nil {
139                         return r.Next()
140                 }
141                 return fields, err
142         }
143         return fields, nil
144 }
145
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()
149         if err != nil {
150                 return nil, err
151         }
152         m := make(map[string]string, len(fields))
153         for _, f := range fields {
154                 m[f.Name] = f.Value
155         }
156         return m, nil
157 }
158
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()
162         if err != nil {
163                 return nil, err
164         }
165         m := make(map[string][]string)
166         for _, f := range fields {
167                 m[f.Name] = append(m[f.Name], f.Value)
168         }
169         return m, nil
170 }