]> Cypherpunks.ru repositories - gorecfile.git/commitdiff
Initial commit v0.1.0
authorSergey Matveev <stargrave@stargrave.org>
Thu, 15 Oct 2020 15:41:33 +0000 (18:41 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Thu, 15 Oct 2020 15:45:28 +0000 (18:45 +0300)
README [new file with mode: 0644]
cmd/gorecsel/main.go [new file with mode: 0644]
go.mod [new file with mode: 0644]
r.go [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
index 0000000..8489c68
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+GNU recutils'es recfile parser on pure Go.
diff --git a/cmd/gorecsel/main.go b/cmd/gorecsel/main.go
new file mode 100644 (file)
index 0000000..13844c6
--- /dev/null
@@ -0,0 +1,31 @@
+package main
+
+import (
+       "fmt"
+       "io"
+       "os"
+
+       "go.cypherpunks.ru/recfile"
+)
+
+func main() {
+       r := recfile.NewReader(os.Stdin)
+       n := 0
+       for {
+               fields, err := r.Next()
+               if err != nil {
+                       if err == io.EOF {
+                               return
+                       }
+                       panic(err)
+               }
+               if n > 0 {
+                       fmt.Println("")
+               }
+               fmt.Println("Record:", n)
+               for _, field := range fields {
+                       fmt.Printf("%s: %s\n", field.Name, field.Value)
+               }
+               n++
+       }
+}
diff --git a/go.mod b/go.mod
new file mode 100644 (file)
index 0000000..a605297
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module go.cypherpunks.ru/recfile
+
+go 1.14
diff --git a/r.go b/r.go
new file mode 100644 (file)
index 0000000..3614171
--- /dev/null
+++ b/r.go
@@ -0,0 +1,125 @@
+/*
+recfile -- GNU recutils'es recfiles parser on pure Go
+Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package recfile
+
+import (
+       "bufio"
+       "errors"
+       "io"
+       "regexp"
+       "strings"
+)
+
+type Reader struct {
+       scanner *bufio.Scanner
+}
+
+func NewReader(r io.Reader) *Reader {
+       return &Reader{bufio.NewScanner(r)}
+}
+
+type Field struct {
+       Name  string
+       Value string
+}
+
+var KeyValRe = regexp.MustCompile(`([a-zA-Z%][a-zA-Z0-9_]*):\s*(.*)$`)
+
+func (r *Reader) Next() ([]Field, error) {
+       fields := make([]Field, 0, 1)
+       var text string
+       var name string
+       var line string
+       lines := make([]string, 0)
+       continuation := false
+       var err error
+       for {
+               if !r.scanner.Scan() {
+                       if err := r.scanner.Err(); err != nil {
+                               return fields, err
+                       }
+                       err = io.EOF
+                       break
+               }
+               text = r.scanner.Text()
+
+               if continuation {
+                       if text[len(text)-1] == '\\' {
+                               lines = append(lines, text[:len(text)-1])
+                       } else {
+                               lines = append(lines, text)
+                               fields = append(fields, Field{name, strings.Join(lines, "")})
+                               lines = make([]string, 0)
+                               continuation = false
+                       }
+                       continue
+               }
+
+               if len(text) > 0 && text[0] == '#' {
+                       continue
+               }
+
+               if len(text) > 0 && text[0] == '+' {
+                       lines = append(lines, "\n")
+                       if len(text) > 1 {
+                               if text[1] == ' ' {
+                                       lines = append(lines, text[2:])
+                               } else {
+                                       lines = append(lines, text[1:])
+                               }
+                       }
+                       continue
+               }
+
+               if len(lines) > 0 {
+                       fields = append(fields, Field{name, strings.Join(lines, "")})
+                       lines = make([]string, 0)
+               }
+
+               if text == "" {
+                       break
+               }
+
+               matches := KeyValRe.FindStringSubmatch(text)
+               if len(matches) == 0 {
+                       return fields, errors.New("invalid field format")
+               }
+               name = matches[1]
+               line = matches[2]
+
+               if len(line) > 0 && line[len(line)-1] == '\\' {
+                       continuation = true
+                       lines = append(lines, line[:len(line)-1])
+               } else {
+                       lines = append(lines, line)
+               }
+       }
+       if continuation {
+               return fields, errors.New("left continuation")
+       }
+       if len(lines) > 0 {
+               fields = append(fields, Field{name, strings.Join(lines, "")})
+       }
+       if len(fields) == 0 {
+               if err == nil {
+                       return r.Next()
+               }
+               return fields, err
+       }
+       return fields, nil
+}