]> Cypherpunks.ru repositories - gorecfile.git/blob - r.go
Raised copyright years
[gorecfile.git] / r.go
1 /*
2 recfile -- GNU recutils'es recfiles parser on pure Go
3 Copyright (C) 2020-2022 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         "regexp"
25         "strings"
26 )
27
28 var KeyValRe = regexp.MustCompile(`([a-zA-Z%][a-zA-Z0-9_]*):\s*(.*)$`)
29
30 type Reader struct {
31         scanner *bufio.Scanner
32 }
33
34 // Create Reader for iterating through the records. It uses
35 // bufio.Scanner, so can read more than currently parsed records take.
36 func NewReader(r io.Reader) *Reader {
37         return &Reader{bufio.NewScanner(r)}
38 }
39
40 // Get next record. Each record is just a collection of fields. io.EOF
41 // is returned if there is nothing to read more.
42 func (r *Reader) Next() ([]Field, error) {
43         fields := make([]Field, 0, 1)
44         var text string
45         var name string
46         var line string
47         lines := make([]string, 0)
48         continuation := false
49         var err error
50         for {
51                 if !r.scanner.Scan() {
52                         if err := r.scanner.Err(); err != nil {
53                                 return fields, err
54                         }
55                         err = io.EOF
56                         break
57                 }
58                 text = r.scanner.Text()
59
60                 if continuation {
61                         if text[len(text)-1] == '\\' {
62                                 lines = append(lines, text[:len(text)-1])
63                         } else {
64                                 lines = append(lines, text)
65                                 fields = append(fields, Field{name, strings.Join(lines, "")})
66                                 lines = make([]string, 0)
67                                 continuation = false
68                         }
69                         continue
70                 }
71
72                 if len(text) > 0 && text[0] == '#' {
73                         continue
74                 }
75
76                 if len(text) > 0 && text[0] == '+' {
77                         lines = append(lines, "\n")
78                         if len(text) > 1 {
79                                 if text[1] == ' ' {
80                                         lines = append(lines, text[2:])
81                                 } else {
82                                         lines = append(lines, text[1:])
83                                 }
84                         }
85                         continue
86                 }
87
88                 if len(lines) > 0 {
89                         fields = append(fields, Field{name, strings.Join(lines, "")})
90                         lines = make([]string, 0)
91                 }
92
93                 if text == "" {
94                         break
95                 }
96
97                 matches := KeyValRe.FindStringSubmatch(text)
98                 if len(matches) == 0 {
99                         return fields, errors.New("invalid field format")
100                 }
101                 name = matches[1]
102                 line = matches[2]
103
104                 if len(line) > 0 && line[len(line)-1] == '\\' {
105                         continuation = true
106                         lines = append(lines, line[:len(line)-1])
107                 } else {
108                         lines = append(lines, line)
109                 }
110         }
111         if continuation {
112                 return fields, errors.New("left continuation")
113         }
114         if len(lines) > 0 {
115                 fields = append(fields, Field{name, strings.Join(lines, "")})
116         }
117         if len(fields) == 0 {
118                 if err == nil {
119                         return r.Next()
120                 }
121                 return fields, err
122         }
123         return fields, nil
124 }
125
126 // Same as Next(), but with unique keys and last value.
127 func (r *Reader) NextMap() (map[string]string, error) {
128         fields, err := r.Next()
129         if err != nil {
130                 return nil, err
131         }
132         m := make(map[string]string, len(fields))
133         for _, f := range fields {
134                 m[f.Name] = f.Value
135         }
136         return m, nil
137 }
138
139 // Same as Next(), but with unique keys and slices of values.
140 func (r *Reader) NextMapWithSlice() (map[string][]string, error) {
141         fields, err := r.Next()
142         if err != nil {
143                 return nil, err
144         }
145         m := make(map[string][]string)
146         for _, f := range fields {
147                 m[f.Name] = append(m[f.Name], f.Value)
148         }
149         return m, nil
150 }