Compare commits

..

5 Commits

Author SHA1 Message Date
4ab8fc16c2 Add godoc badge 2024-08-31 22:56:24 -04:00
5cc89d5b42 Add some tests 2024-08-31 22:54:54 -04:00
dbffe4b7c4 Fix escape sequence parsing 2024-08-31 22:54:38 -04:00
a854dd0618 Add String methods for structures 2024-08-31 22:37:05 -04:00
bb9e317fa7 Fix parsing bug where separator comparisons were flipped 2024-08-31 21:46:10 -04:00
3 changed files with 129 additions and 6 deletions

View File

@@ -1,4 +1,7 @@
# ASV
[![Go Reference](https://pkg.go.dev/badge/git.tebibyte.media/sashakoshka/go-asv.svg)](https://pkg.go.dev/git.tebibyte.media/sashakoshka/go-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.

64
asv.go
View File

@@ -4,6 +4,7 @@ package asv
import "io"
import "bufio"
import "strings"
import "strconv"
// Collection is composed of Files.
type Collection []File
@@ -16,6 +17,50 @@ type Record []Unit
// Unit is an element of a record.
type Unit string
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()
}
const (
Escape rune = '\x1B'
FileSeparator rune = '\x1C'
@@ -61,7 +106,7 @@ func (this *Decoder) ReadFile () (file File, next rune, err error) {
group, next, err := this.ReadGroup()
file = append(file, group)
if err != nil { return file, 0, err }
if next >= FileSeparator {
if next <= FileSeparator {
return file, next, nil
}
}
@@ -76,7 +121,7 @@ func (this *Decoder) ReadGroup () (group Group, next rune, err error) {
record, next, err := this.ReadRecord()
group = append(group, record)
if err != nil { return group, 0, err }
if next >= GroupSeparator {
if next <= GroupSeparator {
return group, next, nil
}
}
@@ -91,7 +136,7 @@ func (this *Decoder) ReadRecord () (record Record, next rune, err error) {
unit, next, err := this.ReadUnit()
record = append(record, unit)
if err != nil { return record, 0, err }
if next >= RecordSeparator {
if next <= RecordSeparator {
return record, next, nil
}
}
@@ -111,12 +156,17 @@ func (this *Decoder) ReadUnit () (unit Unit, next rune, err error) {
char, _, err := this.reader.ReadRune()
if err != nil { return Unit(str.String()), 0, err }
if esc {
switch {
case esc:
esc = false
} else if char >= FileSeparator && char <= UnitSeparator {
return Unit(str.String()), char, nil
}
str.WriteRune(char)
case char == Escape:
esc = true
case IsSeparator(char):
return Unit(str.String()), char, nil
default:
str.WriteRune(char)
}
}
}

70
asv_test.go Normal file
View File

@@ -0,0 +1,70 @@
package asv
import "io"
import "strings"
import "testing"
import "reflect"
func TestDecodeCollection (test *testing.T) {
decoder := NewDecoder(strings.NewReader(
"record\x1fone\x1erecord\x1ftwo\x1drecord one in group two\x1crecord one in file two" +
"\x1flots of \x1b\x1b\x1b\x1f\x1b\x1d\x1b\x1e\x1b\x1cescape sequences"))
got, err := decoder.ReadCollection()
testErr(test, err)
testEqual(test, "structures", got,
Collection {
File {
Group {
Record {
"record",
"one",
},
Record {
"record",
"two",
},
},
Group {
Record {
"record one in group two",
},
},
},
File {
Group {
Record {
"record one in file two",
"lots of \x1b\x1f\x1d\x1e\x1cescape sequences",
},
},
},
})
}
func TestDecodeRecord (test *testing.T) {
decoder := NewDecoder(strings.NewReader(
"record\x1fone"))
got, next, err := decoder.ReadRecord()
testEqual(test, "errors", err, io.EOF)
testEqual(test, "next", next, rune(0))
testEqual(test, "structures", got,
Record {
"record",
"one",
})
}
func testErr (test *testing.T, err error) {
if err != nil {
test.Fatalf("unexpected error: %v", err)
}
}
func testEqual (test *testing.T, desc string, got, correct any) {
if !reflect.DeepEqual(got, correct) {
test.Error(desc, "not equal")
test.Errorf("GOT:\n%v", got)
test.Errorf("CORRECT:\n%v", correct)
test.FailNow()
}
}