From: Sergey Matveev Date: Thu, 15 Oct 2020 15:41:33 +0000 (+0300) Subject: Initial commit X-Git-Tag: v0.1.0^0 X-Git-Url: http://www.git.cypherpunks.ru/?p=gorecfile.git;a=commitdiff_plain;h=d2a8c845d659ec5b5dbda5e9c5207e7f09269c50 Initial commit --- d2a8c845d659ec5b5dbda5e9c5207e7f09269c50 diff --git a/README b/README new file mode 100644 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 index 0000000..13844c6 --- /dev/null +++ b/cmd/gorecsel/main.go @@ -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 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 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 + +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 . +*/ + +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 +}