go-asv/asv.go

275 lines
7.6 KiB
Go
Raw Permalink Normal View History

2024-08-31 00:31:29 -06:00
// Package asv provides encoding and decoding for ASCII separated values.
package asv
import "io"
import "bufio"
import "strings"
2024-08-31 20:37:05 -06:00
import "strconv"
2024-08-31 00:31:29 -06:00
// Collection is composed of Files.
type Collection []File
// File is an element of a Collection, and is composed of Groups.
type File []Group
// Groups is an element of a File, and is composed of Records.
type Group []Record
// Record is an element of a Group, and is composed of Units.
type Record []Unit
// Unit is an element of a record.
type Unit string
2024-08-31 20:37:05 -06:00
func (collection Collection) String () string {
builder := strings.Builder { }
builder.WriteString("Collection { ")
for index, file := range collection {
if index > 0 { builder.WriteString(", ") }
builder.WriteString(file.String())
}
builder.WriteString(" }")
return builder.String()
}
func (file File) String () string {
builder := strings.Builder { }
builder.WriteString("File { ")
for index, group := range file {
if index > 0 { builder.WriteString(", ") }
builder.WriteString(group.String())
}
builder.WriteString(" }")
return builder.String()
}
func (group Group) String () string {
builder := strings.Builder { }
builder.WriteString("Group { ")
for index, record := range group {
if index > 0 { builder.WriteString(", ") }
builder.WriteString(record.String())
}
builder.WriteString(" }")
return builder.String()
}
func (record Record) String () string {
builder := strings.Builder { }
builder.WriteString("Record { ")
for index, unit := range record {
if index > 0 { builder.WriteString(", ") }
builder.WriteString(strconv.Quote(string(unit)))
}
builder.WriteString(" }")
return builder.String()
}
2024-08-31 00:31:29 -06:00
const (
Escape rune = '\x1B'
FileSeparator rune = '\x1C'
GroupSeparator rune = '\x1D'
RecordSeparator rune = '\x1E'
UnitSeparator rune = '\x1F'
)
// IsSeparator returns true if the given rune is a separator.
func IsSeparator (r rune) bool {
return r >= FileSeparator && r <= UnitSeparator
}
// Decoder decodes ASV values.
type Decoder struct {
reader *bufio.Reader
}
// NewDecoder creates a new Decoder that reads from the specified io.Reader.
func NewDecoder (reader io.Reader) *Decoder {
return &Decoder {
reader: bufio.NewReader(reader),
}
}
2024-08-31 00:39:10 -06:00
// ReadCollection reads files until EOF and returns them in a collection. This
// function will return nil instead of io.EOF.
func (this *Decoder) ReadCollection () (collection Collection, err error) {
for {
file, _, err := this.ReadFile()
collection = append(collection, file)
if err == io.EOF { return collection, nil}
if err != nil { return collection, err }
}
}
// ReadFile reads groups until a separator higher than or equal to FileSeparator
// is encountered, or an EOF is encountered. The function will then return the
// resulting File, as well as the separator that caused it to terminate. On EOF,
// it will return 0 for next and io.EOF for err.
func (this *Decoder) ReadFile () (file File, next rune, err error) {
for {
group, next, err := this.ReadGroup()
file = append(file, group)
if err != nil { return file, 0, err }
if next <= FileSeparator {
2024-08-31 00:39:10 -06:00
return file, next, nil
}
}
}
2024-08-31 00:31:29 -06:00
// ReadGroup reads records until a separator higher than or equal to
// GroupSeparator is encountered, or an EOF is encountered. The function will
// then return the resulting Group, as well as the separator that caused it to
// terminate. On EOF, it will return 0 for next and io.EOF for err.
func (this *Decoder) ReadGroup () (group Group, next rune, err error) {
for {
record, next, err := this.ReadRecord()
group = append(group, record)
if err != nil { return group, 0, err }
if next <= GroupSeparator {
2024-08-31 00:31:29 -06:00
return group, next, nil
}
}
}
// ReadRecord reads units until a separator higher than or equal to
// RecordSeparator is encountered, or an EOF is encountered. The function will
// then return the resulting Record, as well as the separator that caused it to
// terminate. On EOF, it will return 0 for next and io.EOF for err.
func (this *Decoder) ReadRecord () (record Record, next rune, err error) {
for {
unit, next, err := this.ReadUnit()
record = append(record, unit)
if err != nil { return record, 0, err }
if next <= RecordSeparator {
2024-08-31 00:31:29 -06:00
return record, next, nil
}
}
}
// ReadUnit reads characters until a separator higher than or equal to
// UnitSeparator is encountered, or an EOF is encountered. The function will
// then return the resulting Unit, as well as the separator that caused it to
// terminate. On EOF, it will return 0 for next and io.EOF for err.
//
// If Escape is encountered before any character, that character will be
// included in the unit's string literally, minus the Escape.
func (this *Decoder) ReadUnit () (unit Unit, next rune, err error) {
esc := false
str := strings.Builder { }
for {
char, _, err := this.reader.ReadRune()
if err != nil { return Unit(str.String()), 0, err }
2024-08-31 20:54:38 -06:00
switch {
case esc:
2024-08-31 00:31:29 -06:00
esc = false
2024-08-31 20:54:38 -06:00
str.WriteRune(char)
case char == Escape:
esc = true
case IsSeparator(char):
2024-08-31 00:31:29 -06:00
return Unit(str.String()), char, nil
2024-08-31 20:54:38 -06:00
default:
str.WriteRune(char)
2024-08-31 00:31:29 -06:00
}
}
}
// Encoder encodes ASV values. It keeps track of the last entity that was
// written, and inserts separators where necessary. After all data has been
// written, the client should call the Flush method to guarantee all data has
// been forwarded to the underlying io.Writer.
type Encoder struct {
writer *bufio.Writer
lastEntity rune
}
// WriteCollection writes a collection to the underlying io.Writer.
func (this *Encoder) WriteCollection (collection Collection) (n int, err error) {
for _, file := range collection {
nn, err := this.WriteFile(file)
n += nn
if err != nil { return n, err }
}
return n, nil
}
// WriteFile writes a file to the underlying io.Writer.
func (this *Encoder) WriteFile (file File) (n int, err error) {
nn, err := this.begin(FileSeparator)
n += nn
if err != nil { return n, err }
defer this.end(FileSeparator)
for _, group := range file {
nn, err := this.WriteGroup(group)
n += nn
if err != nil { return n, err }
}
return n, nil
}
// WriteGroup writes a group to the underlying io.Writer.
func (this *Encoder) WriteGroup (group Group) (n int, err error) {
nn, err := this.begin(GroupSeparator)
n += nn
if err != nil { return n, err }
defer this.end(GroupSeparator)
for _, record := range group {
nn, err := this.WriteRecord(record)
n += nn
if err != nil { return n, err }
}
return n, nil
}
// WriteRecord writes a record to the underlying io.Writer.
func (this *Encoder) WriteRecord (record Record) (n int, err error) {
nn, err := this.begin(RecordSeparator)
n += nn
if err != nil { return n, err }
defer this.end(RecordSeparator)
for _, unit := range record {
nn, err := this.WriteUnit(unit)
n += nn
if err != nil { return n, err }
}
return n, nil
}
// WriteUnit writes a unit to the underlying io.Writer. All separators, as well
// as Escape, are prepended with an Escape.
func (this *Encoder) WriteUnit (unit Unit) (n int, err error) {
nn, err := this.begin(UnitSeparator)
n += nn
if err != nil { return n, err }
defer this.end(UnitSeparator)
for _, char := range unit {
if IsSeparator(char) || char == Escape {
nn, _ := this.writer.WriteRune(Escape)
n += nn
}
nn, err := this.writer.WriteRune(char)
n += nn
if err != nil { return n, err }
}
return n, nil
}
func (this *Encoder) begin (separator rune) (n int, err error) {
if this.lastEntity == UnitSeparator {
nn, err := this.writer.WriteRune(separator)
n += nn
if err != nil { return n, err }
}
return n, nil
}
func (this *Encoder) end (separator rune) {
this.lastEntity = separator
}
// Flush writes any buffered data to the underlying io.Writer.
func (this *Encoder) Flush () error {
return this.writer.Flush()
}