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 }
|
2024-08-31 19:46:10 -06:00
|
|
|
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 }
|
2024-08-31 19:46:10 -06:00
|
|
|
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 }
|
2024-08-31 19:46:10 -06:00
|
|
|
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 }
|
|
|
|
|
|
|
|
if esc {
|
|
|
|
esc = false
|
2024-08-31 19:46:10 -06:00
|
|
|
} else if IsSeparator(char) {
|
2024-08-31 00:31:29 -06:00
|
|
|
return Unit(str.String()), char, nil
|
|
|
|
}
|
|
|
|
str.WriteRune(char)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
}
|