// Package asv provides encoding and decoding for ASCII separated values. package asv import "io" import "bufio" import "strings" import "strconv" // 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 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' 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), } } // 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 { return file, next, nil } } } // 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 } switch { case esc: esc = false str.WriteRune(char) case char == Escape: esc = true case IsSeparator(char): return Unit(str.String()), char, nil default: 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() }