7 Commits

Author SHA1 Message Date
amery ae5c095792 Unmarshal: WIP
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-01 13:05:18 +00:00
amery adcbd9d7a3 Decoder: WIP
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-01 13:05:18 +00:00
amery 110b110feb basic: WIP
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-01 13:05:18 +00:00
amery b9771fdc3b build-sys: use local asciigoat.org/core [DO-NOT-MERGE]
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-01 13:05:02 +00:00
amery c92e0df47b chore: update asciigoat.org/core
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-01 13:04:05 +00:00
amery cf100578c0 Merge pull request 'README: add initial description of the package' (#3)
Reviewed-on: #3
2023-09-01 15:02:34 +02:00
amery 2eacc65215 README: add initial description of the package
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-01 12:57:55 +00:00
4 changed files with 200 additions and 30 deletions
+10 -1
View File
@@ -31,11 +31,20 @@ emiting tokens and errors via callbacks.
[`basic.Decode()`][godoc-basic-parser] provies a one-shot decoder
that returns a structured document for you to post-process.
To allow for correct handling of repetition of section and field names downstream, it uses arrays instead of maps, and makes almost no judgment
To allow for correct handling of repetition of section and field names downstream,
it uses arrays instead of maps, and makes almost no judgment
about what section or field names are acceptable.
## Other Implementations
Other implementations exist, and they are mature and feature-rich, but they
are highly opinionated about what's a valid file. Built around maps they don't
allow repeating names and constraint what characters can be used.
These are great when you can adapt, or already agree, to their conditions but
that's not always the case when you are parsing configuration files from
other applications and that's what [asciigoat.org/ini][godoc] attempts to solve.
* [gcfg](https://pkg.go.dev/gopkg.in/gcfg.v1)
* [unknwon's go-ini](https://github.com/go-ini/ini)
* [wlevene's GoINI](https://github.com/wlevene/ini)
+5 -29
View File
@@ -4,44 +4,20 @@ import (
"bytes"
"io"
"io/fs"
"log"
"strings"
"asciigoat.org/core/lexer"
"asciigoat.org/ini/parser"
)
type token struct {
pos lexer.Position
typ parser.TokenType
value string
}
type decoder struct {
p *parser.Parser
out *Document
queue []token
curSection *Section
curField *Field
queue []*token
current *Section
}
func (dec *decoder) OnToken(pos lexer.Position, typ parser.TokenType, value string) error {
tok := token{pos, typ, value}
log.Printf("%s[%v]: %s %s: %q", "decoder", len(dec.queue), pos, typ, value)
dec.queue = append(dec.queue, tok)
return nil
}
func (*decoder) OnError(pos lexer.Position, hint string, err error) error {
log.Printf("%s: %s %s: %s: %v", "decoder", pos, "error", hint, err)
return err
}
// Decode ...
// Decode attempts to decode an INI-style from an [io.Reader] array into a [Document]
func Decode(r io.Reader) (*Document, error) {
var out Document
@@ -62,12 +38,12 @@ func Decode(r io.Reader) (*Document, error) {
return &out, err
}
// DecodeBytes ...
// DecodeBytes attempts to decode an INI-style bytes array into a [Document]
func DecodeBytes(b []byte) (*Document, error) {
return Decode(bytes.NewReader(b))
}
// DecodeString ...
// DecodeString attempts to decode an INI-style string into a [Document]
func DecodeString(s string) (*Document, error) {
return Decode(strings.NewReader(s))
}
+33
View File
@@ -0,0 +1,33 @@
package basic
import (
"errors"
"asciigoat.org/core/lexer"
)
var (
errInvalidToken = errors.New("invalid token")
)
func newErrInvalidToken(t *token) *lexer.Error {
err := &lexer.Error{
Line: t.pos.Line,
Column: t.pos.Column,
Content: t.value,
Err: errInvalidToken,
}
return err
}
func (dec *decoder) OnError(pos lexer.Position, content string, err error) error {
err = &lexer.Error{
Line: pos.Line,
Column: pos.Column,
Content: content,
Err: err,
}
dec.executeFinal()
return err
}
+152
View File
@@ -0,0 +1,152 @@
package basic
import (
"fmt"
"asciigoat.org/core/lexer"
"asciigoat.org/ini/parser"
)
type token struct {
pos lexer.Position
typ parser.TokenType
value string
}
func (t token) String() string {
return fmt.Sprintf("%s %s: %q", t.pos, t.typ, t.value)
}
func (dec *decoder) executeFinal() {
if len(dec.queue) > 0 {
// we have unfinished businesses
switch dec.queue[0].typ {
case parser.TokenSectionStart:
dec.execute(parser.TokenSectionEnd)
case parser.TokenFieldKey:
dec.execute(parser.TokenFieldValue)
}
}
}
func (dec *decoder) execute(typ parser.TokenType) {
switch typ {
case parser.TokenSectionEnd:
name1, ok1 := dec.getValue(1, parser.TokenSectionName)
if ok1 {
name2, ok2 := dec.getValue(2, parser.TokenSectionSubname)
dec.addSection(name1, name2, ok2)
}
dec.reset()
case parser.TokenFieldValue:
key, _ := dec.getValue(0, parser.TokenFieldKey)
value, _ := dec.getValue(1, parser.TokenFieldValue)
dec.addField(key, value)
dec.reset()
}
}
func (dec *decoder) addSection(name, key string, hadKey bool) {
// index for dec.current
n := len(dec.out.Sections)
// new section
dec.out.Sections = append(dec.out.Sections, Section{
Name: name,
Key: key,
HadKey: hadKey,
})
// pointer to the latest section
dec.current = &dec.out.Sections[n]
}
func (dec *decoder) addField(key, value string) {
field := Field{
Key: key,
Value: value,
}
if p := dec.current; p != nil {
// in section
p.Fields = append(p.Fields, field)
} else {
// global
dec.out.Global = append(dec.out.Global, field)
}
}
func (dec *decoder) getValue(idx int, typ parser.TokenType) (string, bool) {
switch {
case idx < 0 || idx >= len(dec.queue):
// out of range
return "", false
case dec.queue[idx].typ != typ:
// wrong type
return "", false
default:
return dec.queue[idx].value, true
}
}
func (dec *decoder) reset() {
dec.queue = dec.queue[:0]
}
func (dec *decoder) depth(depth int) bool {
return len(dec.queue) == depth
}
func (dec *decoder) depthAfter(depth int, typ parser.TokenType) bool {
_, ok := dec.getValue(depth-1, typ)
if ok {
return len(dec.queue) == depth
}
return false
}
func (dec *decoder) typeOK(typ parser.TokenType) bool {
switch typ {
case parser.TokenSectionStart, parser.TokenFieldKey:
// first token only
return dec.depth(0)
case parser.TokenSectionName:
// right after TokenSectionStart
return dec.depthAfter(1, parser.TokenSectionStart)
case parser.TokenSectionSubname:
// right after TokenSectionName
return dec.depthAfter(2, parser.TokenSectionName)
case parser.TokenSectionEnd:
// only on a section with name
_, ok := dec.getValue(1, parser.TokenSectionName)
return ok
case parser.TokenFieldValue:
// right after a TokenFieldKey
return dec.depthAfter(1, parser.TokenFieldKey)
default:
// never
return false
}
}
func (dec *decoder) OnToken(pos lexer.Position, typ parser.TokenType, value string) error {
t := &token{pos, typ, value}
switch {
case typ == parser.TokenComment:
// ignore comments
return nil
case dec.typeOK(typ):
// acceptable token
dec.queue = append(dec.queue, t)
dec.execute(typ)
return nil
default:
// unacceptable
return newErrInvalidToken(t)
}
}