Skip to content

Commit

Permalink
control comment encoding per collection type
Browse files Browse the repository at this point in the history
- tapestry wants comment encoding for maps but not for sequences: so expose control over them separately.
- unify the iterator for both to simplify the code slightly ( there was already an under the hood adapter for sequences to return dash; now its part of the iterator definition.
- fix an issue with comment encoding via the "GetValue" interface
- fix a mispel in an error message
  • Loading branch information
ionous committed Jan 16, 2024
1 parent 5858f17 commit dcef29b
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 172 deletions.
2 changes: 1 addition & 1 deletion collect/orderedmap/omIterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "github.com/ionous/tell/encode"

// implements the encoder mapping
// modification of the map during iteration may yield surprising results.
func (o OrderedMap) TellMapping() encode.MappingIter {
func (o OrderedMap) TellMapping() encode.Iterator {
return &mapIter{o: &o}
}

Expand Down
2 changes: 1 addition & 1 deletion decode/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (p *pendingMap) setKey(key string) (err error) {
if len(p.key) > 0 {
err = fmt.Errorf("unused key %s", p.key)
} else if len(key) == 0 {
err = errors.New("cant add indexed elements to map ping")
err = errors.New("cant add indexed elements to mapping")
} else {
p.key = key
}
Expand Down
48 changes: 22 additions & 26 deletions encode/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,19 @@ func MakeCommentEncoder(w io.Writer) Encoder {
var m MapTransform
var n SequenceTransform
return Encoder{
Tabs: TabWriter{Writer: w},
Mapper: m.Mapper(),
Sequencer: n.Sequencer(),
Commenter: CommentBlock,
Tabs: TabWriter{Writer: w},
Mapper: m.Mapper(),
Sequencer: n.Sequencer(),
MapComments: CommentBlock,
SequenceComments: CommentBlock,
}
}

type Encoder struct {
Tabs TabWriter
Mapper MappingFactory
Sequencer SequenceFactory
Commenter CommentFactory
Tabs TabWriter
Mapper, Sequencer Collection
MapComments Commenting
SequenceComments Commenting
}

func (enc *Encoder) Encode(v any) (err error) {
Expand Down Expand Up @@ -124,7 +125,7 @@ func (enc *Encoder) WriteValue(v r.Value, wasMaps bool) (err error) {

case r.Array, r.Slice:
// tbd: look at tag for "want array"?
if it, e := enc.Sequencer(v); e != nil {
if it, e := enc.Sequencer.StartCollection(v); e != nil {
err = e
} else if it == nil {
tab.WriteRune(runes.ArrayOpen)
Expand All @@ -134,7 +135,7 @@ func (enc *Encoder) WriteValue(v r.Value, wasMaps bool) (err error) {
}

case r.Map:
if it, e := enc.Mapper(v); e != nil {
if it, e := enc.Mapper.StartCollection(v); e != nil {
err = e
} else if it != nil {
err = enc.WriteMapping(it, wasMaps)
Expand All @@ -149,12 +150,6 @@ func (enc *Encoder) WriteValue(v r.Value, wasMaps bool) (err error) {
return
}

type sequenceAdapter struct{ SequenceIter }

func (sq sequenceAdapter) GetKey() string { return dashing }

const dashing = "-"

var mappingType = r.TypeOf((*TellMapping)(nil)).Elem()
var sequenceType = r.TypeOf((*TellSequence)(nil)).Elem()

Expand All @@ -169,29 +164,30 @@ func getValue(v interface{ GetValue() any }) (ret r.Value) {
return
}

func (enc *Encoder) WriteMapping(it MappingIter, wasMaps bool) (err error) {
return enc.writeCollection(it, wasMaps, true)
func (enc *Encoder) WriteMapping(it Iterator, wasMaps bool) (err error) {
return enc.writeCollection(it, enc.MapComments, wasMaps, true)
}

func (enc *Encoder) WriteSequence(it SequenceIter, wasMaps bool) (err error) {
a := sequenceAdapter{it}
return enc.writeCollection(a, wasMaps, false)
func (enc *Encoder) WriteSequence(it Iterator, wasMaps bool) (err error) {
return enc.writeCollection(it, enc.SequenceComments, wasMaps, false)
}

func (enc *Encoder) writeCollection(it MappingIter, wasMaps, maps bool) (err error) {
func (enc *Encoder) writeCollection(it Iterator, cmts Commenting, wasMaps, maps bool) (err error) {
tab := &enc.Tabs
hasNext := it.Next() // dance around the possibly blank first element
if !hasNext {
return
}

// setup a comment iterator:
var cit CommentIter = noComments{} // expect none by default
if c := enc.Commenter; c != nil {
var cit Comments
if cmts == nil {
cit = noComments{} // expect none by default
} else {
key, val := it.GetKey(), getValue(it)
if !maps || len(key) == 0 {
cit, err = c(val)
hasNext = err == nil && it.Next()
cit, err = cmts(val)
hasNext = err == nil && it.Next() // skip this comment value.
}
}
if !hasNext && err == nil {
Expand Down
58 changes: 28 additions & 30 deletions encode/encodeCollect.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,36 @@ package encode

import r "reflect"

// controls serialization when implemented by an encoding value
// see also MappingFactory
type Collection interface {
// for sequences, value is guaranteed to be a reflect.Slice
// for mappings, value is guaranteed to be a reflect.Map
StartCollection(r.Value) (Iterator, error)
}

// controls serialization when implemented by a value that's being encoded
type TellMapping interface {
TellMapping() MappingIter
TellMapping() Iterator
}

// controls serialization when implemented by an encoding value
// see also SequenceFactory
// controls serialization when implemented by a value that's being encoded
type TellSequence interface {
TellSequence() SequenceIter
TellSequence() Iterator
}

// factory function for serializing native maps
// r.Value is guaranteed to a kind of reflect.Map
// the default encoder uses SortedMap or SortedMapFactory.
// returning a nil iterator skips the value
type MappingFactory func(r.Value) (MappingIter, error)

// factory function for serializing slices and arrays.
// the default encoder uses Sequence.
// returning a nil iterator skips the value
type SequenceFactory func(r.Value) (SequenceIter, error)
// the key for sequences
const Dashing = "-"

// turns a value representing one or more comments
// into an iterator. the encoder uses the iterator to generate comments for collections.
type CommentFactory func(r.Value) (CommentIter, error)

type MappingIter interface {
Next() bool // called before every element, false if there are no more elements
// walk the elements of a collection
type Iterator interface {
// return true if there are elements left
Next() bool
// return "-" for sequences
GetKey() string
GetValue() any // valid after next returns true
// can panic if there are no remaining elements
GetValue() any
}

type SequenceIter interface {
Next() bool // called before every element, false if there are no more elements
GetValue() any // valid after next returns true
}

// if implemented by the implementation of MappingIter or SequenceIter
// if implemented by one of the iterators
// will be used instead of GetValue()
type GetReflectedValue interface {
GetReflectedValue() r.Value
Expand All @@ -53,7 +44,14 @@ type Comment struct {
}

// comment access for collections
type CommentIter interface {
type Comments interface {
Next() bool // called before every element, false if there are no more elements
GetComment() Comment // valid after next returns true
}

// implements StartCollection using a function
type sequenceStarter func(r.Value) (Iterator, error)

func (q sequenceStarter) StartCollection(v r.Value) (Iterator, error) {
return q(v)
}
28 changes: 19 additions & 9 deletions encode/encodeComments.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@ import (
"github.com/ionous/tell/runes"
)

// an implementation of CommentFactory that walks the passed slice.
func Comments(els []Comment) CommentIter {
// turns a value representing one or more comments into a comment iterator.
// the encoder uses the iterator to generate comments for collections.
type Commenting func(r.Value) (Comments, error)

// an implementation of Commenting that walks the passed slice.
func CommentSlice(els []Comment) Comments {
return &commentSlice{next: els}
}

// an implementation of CommentFactory which generates no comments.
func DiscardComments(r.Value) (CommentIter, error) {
// implies that there are comments;
// the encoder should simply skip them.
func DiscardComments(r.Value) (Comments, error) {
return nil, nil
}

// implement CommentFactory.
// expects that the value is a kind of string
// containing a standard tell comment block
func CommentBlock(v r.Value) (ret CommentIter, err error) {
func CommentBlock(v r.Value) (ret Comments, err error) {
if str, e := ExtractString(v); e != nil {
err = fmt.Errorf("comment factory %s", e)
} else {
Expand All @@ -32,10 +36,16 @@ func CommentBlock(v r.Value) (ret CommentIter, err error) {

// a helper which, given a reflected string value returns that string.
func ExtractString(el r.Value) (ret string, err error) {
if el.Kind() != r.String {
err = fmt.Errorf("expected an underlying string; got %s(%s)", el.Kind(), el.Type())
} else {
switch k := el.Kind(); k {
case r.String:
ret = el.String()
// when GetReflectedValue isnt implemented;
// strings can be wrapped by interfaces due to GetValue()
// which returns an `any`
case r.Interface:
ret, err = ExtractString(el.Elem())
default:
err = fmt.Errorf("expected an underlying string; got %s(%s)", k, el.Type())
}
return
}
Expand Down
48 changes: 7 additions & 41 deletions encode/encodeMap.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ import (
// customization for serializing native maps
// r.Value is guaranteed to a kind of reflect.Map
type MapTransform struct {
keyLess func(a, b string) bool
keyTransform func(r.Value) string
commentFactory CommentFactory
keyLess func(a, b string) bool
keyTransform func(r.Value) string
}

// return a factory function for the encoder
func (m *MapTransform) Mapper() MappingFactory {
return m.makeMapping
func (m *MapTransform) Mapper() Collection {
return sequenceStarter(m.makeMapping)
}

// sort keys; by default keys are written sorted as per standard go string rules.
Expand All @@ -32,15 +31,6 @@ func (m *MapTransform) KeyTransform(t func(keys r.Value) string) *MapTransform {
return m
}

// the factory is handed the whichever value matches the blank string key.
// the default handler assumes a tell standard comment block
// and errors if the the value isn't an interface with an underlying string value.
// ie. it matches map[string]any{"": "comment"}
func (m *MapTransform) CommentFactory(t func(r.Value) (CommentIter, error)) *MapTransform {
m.commentFactory = t
return m
}

// fix: change to support error?
func keyTransform(v r.Value) (ret string) {
if k := v.Kind(); k != r.String {
Expand All @@ -52,7 +42,7 @@ func keyTransform(v r.Value) (ret string) {
return
}

func (m *MapTransform) makeMapping(src r.Value) (ret MappingIter, err error) {
func (m *MapTransform) makeMapping(src r.Value) (retIt Iterator, err error) {
keyLess := m.keyLess
if keyLess == nil {
keyLess = func(a, b string) bool { return a < b }
Expand All @@ -61,13 +51,8 @@ func (m *MapTransform) makeMapping(src r.Value) (ret MappingIter, err error) {
if xform == nil {
xform = keyTransform
}
newComments := m.commentFactory
if newComments == nil {
newComments = DiscardComments
}

var mk mapKeys
var cit CommentIter
if keys := src.MapKeys(); len(keys) > 0 {
// ugly, but simple:
str := make([]string, len(keys))
Expand All @@ -76,15 +61,9 @@ func (m *MapTransform) makeMapping(src r.Value) (ret MappingIter, err error) {
}
mk = mapKeys{str: str, val: keys, keyLess: keyLess}
sort.Sort(&mk)
// the sort should have forced the comment key (if any) to the first slot
if keyZero := mk.str[0]; keyZero == "" {
cmt := src.MapIndex(mk.val[0]) // the comment block
mk.str, mk.val = mk.str[1:], mk.val[1:] // remove the comment from map iteration
cit, err = newComments(cmt) // iterator for the comment block
}
}
if err == nil {
ret = &mapIter{src: src, mapKeys: mk, comments: cit}
retIt = &mapIter{src: src, mapKeys: mk}
}
return
}
Expand All @@ -96,17 +75,11 @@ var blank = r.ValueOf(anyBlank).Index(0)
type mapIter struct {
src r.Value // the native map
mapKeys mapKeys
//
next int
comments CommentIter
next int
}

func (m *mapIter) Next() (okay bool) {
if okay = m.next < m.mapKeys.Len(); okay {
// advance comments, but dont force them to have the same number of elements
if m.comments != nil {
m.comments.Next() // alt: could swap to emptyComments when done.
}
m.next++
}
return
Expand All @@ -128,10 +101,3 @@ func (m *mapIter) GetReflectedValue() r.Value {
key := m.getKey()
return m.src.MapIndex(key)
}

func (m *mapIter) GetComment() (ret Comment) {
if m.comments != nil {
ret = m.comments.GetComment()
}
return
}
Loading

0 comments on commit dcef29b

Please sign in to comment.