]> Cypherpunks.ru repositories - gorecfile.git/blob - r.go
Slightly more documentation
[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 type Field struct {
42         Name  string
43         Value string
44 }
45
46 // Get next record. Each record is just a collection of fields. io.EOF
47 // is returned if there is nothing to read more.
48 func (r *Reader) Next() ([]Field, error) {
49         fields := make([]Field, 0, 1)
50         var text string
51         var name string
52         var line string
53         lines := make([]string, 0)
54         continuation := false
55         var err error
56         for {
57                 if !r.scanner.Scan() {
58                         if err := r.scanner.Err(); err != nil {
59                                 return fields, err
60                         }
61                         err = io.EOF
62                         break
63                 }
64                 text = r.scanner.Text()
65
66                 if continuation {
67                         if text[len(text)-1] == '\\' {
68                                 lines = append(lines, text[:len(text)-1])
69                         } else {
70                                 lines = append(lines, text)
71                                 fields = append(fields, Field{name, strings.Join(lines, "")})
72                                 lines = make([]string, 0)
73                                 continuation = false
74                         }
75                         continue
76                 }
77
78                 if len(text) > 0 && text[0] == '#' {
79                         continue
80                 }
81
82                 if len(text) > 0 && text[0] == '+' {
83                         lines = append(lines, "\n")
84                         if len(text) > 1 {
85                                 if text[1] == ' ' {
86                                         lines = append(lines, text[2:])
87                                 } else {
88                                         lines = append(lines, text[1:])
89                                 }
90                         }
91                         continue
92                 }
93
94                 if len(lines) > 0 {
95                         fields = append(fields, Field{name, strings.Join(lines, "")})
96                         lines = make([]string, 0)
97                 }
98
99                 if text == "" {
100                         break
101                 }
102
103                 matches := KeyValRe.FindStringSubmatch(text)
104                 if len(matches) == 0 {
105                         return fields, errors.New("invalid field format")
106                 }
107                 name = matches[1]
108                 line = matches[2]
109
110                 if len(line) > 0 && line[len(line)-1] == '\\' {
111                         continuation = true
112                         lines = append(lines, line[:len(line)-1])
113                 } else {
114                         lines = append(lines, line)
115                 }
116         }
117         if continuation {
118                 return fields, errors.New("left continuation")
119         }
120         if len(lines) > 0 {
121                 fields = append(fields, Field{name, strings.Join(lines, "")})
122         }
123         if len(fields) == 0 {
124                 if err == nil {
125                         return r.Next()
126                 }
127                 return fields, err
128         }
129         return fields, nil
130 }