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