Initial commit
This commit is contained in:
commit
9e79073e0f
41
README.md
Normal file
41
README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# ASV
|
||||
This module allows encoding and decoding data using ASV (ASCII-separated
|
||||
values). Data is organized into a hierarchy of files, groups, records, and
|
||||
units, separated by ASCII FS, GS, RS, and US respectively.
|
||||
|
||||
An example stream of data...
|
||||
```
|
||||
record[US]one[RS]record[US]two[GS]record one in group two[FS]record one in file two
|
||||
```
|
||||
|
||||
... Is parsed into:
|
||||
```
|
||||
Collection {
|
||||
File {
|
||||
Group {
|
||||
Record {
|
||||
"record",
|
||||
"one",
|
||||
},
|
||||
Record {
|
||||
"record",
|
||||
"two",
|
||||
},
|
||||
},
|
||||
Group {
|
||||
Record {
|
||||
"record one in group two",
|
||||
},
|
||||
},
|
||||
},
|
||||
File {
|
||||
Group {
|
||||
Record {
|
||||
"record one in file two",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Although it uses ASCII control characters, this module operates on UTF-8 text.
|
198
asv.go
Normal file
198
asv.go
Normal file
@ -0,0 +1,198 @@
|
||||
// Package asv provides encoding and decoding for ASCII separated values.
|
||||
package asv
|
||||
|
||||
import "io"
|
||||
import "bufio"
|
||||
import "strings"
|
||||
|
||||
// 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
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
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
|
||||
} else if char >= FileSeparator && char <= UnitSeparator {
|
||||
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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user