Compare commits
5 Commits
5821510143
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ab8fc16c2 | |||
| 5cc89d5b42 | |||
| dbffe4b7c4 | |||
| a854dd0618 | |||
| bb9e317fa7 |
@@ -1,4 +1,7 @@
|
|||||||
# ASV
|
# ASV
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/git.tebibyte.media/sashakoshka/go-asv)
|
||||||
|
|
||||||
This module allows encoding and decoding data using ASV (ASCII-separated
|
This module allows encoding and decoding data using ASV (ASCII-separated
|
||||||
values). Data is organized into a hierarchy of files, groups, records, and
|
values). Data is organized into a hierarchy of files, groups, records, and
|
||||||
units, separated by ASCII FS, GS, RS, and US respectively.
|
units, separated by ASCII FS, GS, RS, and US respectively.
|
||||||
|
|||||||
62
asv.go
62
asv.go
@@ -4,6 +4,7 @@ package asv
|
|||||||
import "io"
|
import "io"
|
||||||
import "bufio"
|
import "bufio"
|
||||||
import "strings"
|
import "strings"
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
// Collection is composed of Files.
|
// Collection is composed of Files.
|
||||||
type Collection []File
|
type Collection []File
|
||||||
@@ -16,6 +17,50 @@ type Record []Unit
|
|||||||
// Unit is an element of a record.
|
// Unit is an element of a record.
|
||||||
type Unit string
|
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 (
|
const (
|
||||||
Escape rune = '\x1B'
|
Escape rune = '\x1B'
|
||||||
FileSeparator rune = '\x1C'
|
FileSeparator rune = '\x1C'
|
||||||
@@ -61,7 +106,7 @@ func (this *Decoder) ReadFile () (file File, next rune, err error) {
|
|||||||
group, next, err := this.ReadGroup()
|
group, next, err := this.ReadGroup()
|
||||||
file = append(file, group)
|
file = append(file, group)
|
||||||
if err != nil { return file, 0, err }
|
if err != nil { return file, 0, err }
|
||||||
if next >= FileSeparator {
|
if next <= FileSeparator {
|
||||||
return file, next, nil
|
return file, next, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +121,7 @@ func (this *Decoder) ReadGroup () (group Group, next rune, err error) {
|
|||||||
record, next, err := this.ReadRecord()
|
record, next, err := this.ReadRecord()
|
||||||
group = append(group, record)
|
group = append(group, record)
|
||||||
if err != nil { return group, 0, err }
|
if err != nil { return group, 0, err }
|
||||||
if next >= GroupSeparator {
|
if next <= GroupSeparator {
|
||||||
return group, next, nil
|
return group, next, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +136,7 @@ func (this *Decoder) ReadRecord () (record Record, next rune, err error) {
|
|||||||
unit, next, err := this.ReadUnit()
|
unit, next, err := this.ReadUnit()
|
||||||
record = append(record, unit)
|
record = append(record, unit)
|
||||||
if err != nil { return record, 0, err }
|
if err != nil { return record, 0, err }
|
||||||
if next >= RecordSeparator {
|
if next <= RecordSeparator {
|
||||||
return record, next, nil
|
return record, next, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,12 +156,17 @@ func (this *Decoder) ReadUnit () (unit Unit, next rune, err error) {
|
|||||||
char, _, err := this.reader.ReadRune()
|
char, _, err := this.reader.ReadRune()
|
||||||
if err != nil { return Unit(str.String()), 0, err }
|
if err != nil { return Unit(str.String()), 0, err }
|
||||||
|
|
||||||
if esc {
|
switch {
|
||||||
|
case esc:
|
||||||
esc = false
|
esc = false
|
||||||
} else if char >= FileSeparator && char <= UnitSeparator {
|
str.WriteRune(char)
|
||||||
|
case char == Escape:
|
||||||
|
esc = true
|
||||||
|
case IsSeparator(char):
|
||||||
return Unit(str.String()), char, nil
|
return Unit(str.String()), char, nil
|
||||||
|
default:
|
||||||
|
str.WriteRune(char)
|
||||||
}
|
}
|
||||||
str.WriteRune(char)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
70
asv_test.go
Normal file
70
asv_test.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user