commit ac6d8342ee5f47b4e58c71c2737b2ab54e76426b
parent 5f9f83bc5e0b7f8e283c96ddf3332e882959fef3
Author: lash <dev@holbrook.no>
Date: Tue, 25 Apr 2023 09:28:58 +0100
WIP factor out page split
Diffstat:
7 files changed, 456 insertions(+), 88 deletions(-)
diff --git a/render/menu.go b/render/menu.go
@@ -44,7 +44,8 @@ type Menu struct {
pageCount uint16 // number of pages the menu should represent.
canNext bool // availability flag for the "next" browse option.
canPrevious bool // availability flag for the "previous" browse option.
- outputSize uint16 // maximum size constraint for the menu.
+ //outputSize uint16 // maximum size constraint for the menu.
+ sink bool
}
// NewMenu creates a new Menu with an explicit page count.
@@ -58,16 +59,23 @@ func(m *Menu) WithPageCount(pageCount uint16) *Menu {
return m
}
-// WithSize defines the maximum byte size of the rendered menu.
-func(m *Menu) WithOutputSize(outputSize uint16) *Menu {
- m.outputSize = outputSize
+func(m *Menu) WithPages() *Menu {
+ if m.pageCount == 0 {
+ m.pageCount = 1
+ }
return m
}
+// WithSize defines the maximum byte size of the rendered menu.
+//func(m *Menu) WithOutputSize(outputSize uint16) *Menu {
+// m.outputSize = outputSize
+// return m
+//}
+
// GetOutputSize returns the defined heuristic menu size.
-func(m *Menu) GetOutputSize() uint32 {
- return uint32(m.outputSize)
-}
+//func(m *Menu) GetOutputSize() uint32 {
+// return uint32(m.outputSize)
+//}
// WithBrowseConfig defines the criteria for page browsing.
func(m *Menu) WithBrowseConfig(cfg BrowseConfig) *Menu {
@@ -87,8 +95,33 @@ func(m *Menu) Put(selector string, title string) error {
}
// ReservedSize returns the maximum render byte size of the menu.
-func(m *Menu) ReservedSize() uint16 {
- return m.outputSize
+//func(m *Menu) ReservedSize() uint16 {
+// return m.outputSize
+//}
+
+ // mainSize, prevsize, nextsize, nextsize+prevsize
+func(m *Menu) Sizes() ([4]uint32, error) {
+ var menuSizes [4]uint32
+ cfg := m.GetBrowseConfig()
+ tmpm := NewMenu().WithBrowseConfig(cfg)
+ v, err := tmpm.Render(0)
+ if err != nil {
+ return menuSizes, err
+ }
+ menuSizes[0] = uint32(len(v))
+ tmpm = tmpm.WithPageCount(2)
+ v, err = tmpm.Render(0)
+ if err != nil {
+ return menuSizes, err
+ }
+ menuSizes[1] = uint32(len(v)) - menuSizes[0]
+ v, err = tmpm.Render(1)
+ if err != nil {
+ return menuSizes, err
+ }
+ menuSizes[2] = uint32(len(v)) - menuSizes[0]
+ menuSizes[3] = menuSizes[1] + menuSizes[2]
+ return menuSizes, nil
}
// Render returns the full current state of the menu as a string.
diff --git a/render/page.go b/render/page.go
@@ -34,18 +34,18 @@ func NewPage(cache cache.Memory, rs resource.Resource) *Page {
// WithMenu sets a menu renderer for the page.
func(pg *Page) WithMenu(menu *Menu) *Page {
pg.menu = menu
- if pg.sizer != nil {
- pg.sizer = pg.sizer.WithMenuSize(pg.menu.ReservedSize())
- }
+ //if pg.sizer != nil {
+ // pg.sizer = pg.sizer.WithMenuSize(pg.menu.ReservedSize())
+ //}
return pg
}
// WithSizer sets a size constraints definition for the page.
func(pg *Page) WithSizer(sizer *Sizer) *Page {
pg.sizer = sizer
- if pg.menu != nil {
- pg.sizer = pg.sizer.WithMenuSize(pg.menu.ReservedSize())
- }
+ //if pg.menu != nil {
+ //pg.sizer = pg.sizer.WithMenuSize(pg.menu.ReservedSize())
+ //}
return pg
}
@@ -77,9 +77,9 @@ func(pg *Page) Usage() (uint32, uint32, error) {
}
r := uint32(l)
rsv := uint32(c)-r
- if pg.menu != nil {
- r += uint32(pg.menu.ReservedSize())
- }
+ //if pg.menu != nil {
+ // r += uint32(pg.menu.ReservedSize())
+ //}
return r, rsv, nil
}
@@ -206,79 +206,50 @@ func(pg *Page) Reset() {
}
}
-
-// render menu and all syms except sink, split sink into display chunks
-// TODO: Function too long, split up
-func(pg *Page) prepare(ctx context.Context, sym string, values map[string]string, idx uint16) (map[string]string, error) {
+// extract sink values to separate array, and set the content of sink in values map to zero-length string.
+//
+// this allows render of page with emptry content the sink symbol to discover remaining capacity.
+func(pg *Page) split(sym string, values map[string]string) (map[string]string, string, []string, error) {
var sink string
-
- if pg.sizer == nil {
- return values, nil
- }
-
var sinkValues []string
noSinkValues := make(map[string]string)
+
for k, v := range values {
sz, err := pg.cache.ReservedSize(k)
if err != nil {
- return nil, err
+ return nil, "", nil, err
}
if sz == 0 {
sink = k
sinkValues = strings.Split(v, "\n")
v = ""
- Logg.Infof("found sink", "sym", k, "fields", len(sinkValues))
+ Logg.Infof("found sink", "sym", sym, "sink", k)
}
noSinkValues[k] = v
}
if sink == "" {
Logg.Tracef("no sink found", "sym", sym)
- return values, nil
- }
-
- pg.sizer.AddCursor(0)
- s, err := pg.render(ctx, sym, noSinkValues, 0)
- if err != nil {
- return nil, err
- }
- // remaining includes core menu
- remaining, ok := pg.sizer.Check(s)
- if !ok {
- return nil, fmt.Errorf("capacity exceeded")
- }
-
- var menuSizes [4]uint32 // mainSize, prevsize, nextsize, nextsize+prevsize
- if pg.menu != nil {
- cfg := pg.menu.GetBrowseConfig()
- tmpm := NewMenu().WithBrowseConfig(cfg)
- v, err := tmpm.Render(0)
- if err != nil {
- return nil, err
- }
- menuSizes[0] = uint32(len(v))
- tmpm = tmpm.WithPageCount(2)
- v, err = tmpm.Render(0)
- if err != nil {
- return nil, err
- }
- menuSizes[1] = uint32(len(v)) - menuSizes[0]
- v, err = tmpm.Render(1)
- if err != nil {
- return nil, err
- }
- menuSizes[2] = uint32(len(v)) - menuSizes[0]
- menuSizes[3] = menuSizes[1] + menuSizes[2]
+ return values, "", nil, nil
}
+ return noSinkValues, sink, sinkValues, nil
+}
- Logg.Debugf("calculated pre-navigation allocation", "bytes", remaining)
-
+// flatten the sink values array into a paged string.
+//
+// newlines (within the same page) render are defined by NUL (0x00).
+//
+// pages are separated by LF (0x0a).
+func(pg *Page) joinSink(sinkValues []string, remaining uint32, menuSizes [4]uint32) (string, uint16, error) {
l := 0
var count uint16
tb := strings.Builder{}
rb := strings.Builder{}
+ // remaining is remaining less one LF
netRemaining := remaining - 1
+
+ // BUG: this reserves the previous browse before we know we need it
if len(sinkValues) > 1 {
netRemaining -= menuSizes[1] - 1
}
@@ -288,7 +259,7 @@ func(pg *Page) prepare(ctx context.Context, sym string, values map[string]string
Logg.Tracef("processing sink", "idx", i, "value", v)
if uint32(l) > netRemaining - 1 {
if tb.Len() == 0 {
- return nil, fmt.Errorf("capacity insufficient for sink field %v", i)
+ return "", 0, fmt.Errorf("capacity insufficient for sink field %v", i)
}
rb.WriteString(tb.String())
rb.WriteRune('\n')
@@ -315,14 +286,61 @@ func(pg *Page) prepare(ctx context.Context, sym string, values map[string]string
r := rb.String()
r = strings.TrimRight(r, "\n")
+ return r, count, nil
+}
+
+// render menu and all syms except sink, split sink into display chunks
+func(pg *Page) prepare(ctx context.Context, sym string, values map[string]string, idx uint16) (map[string]string, error) {
+ if pg.sizer == nil {
+ return values, nil
+ }
+
+ // extract sink values
+ noSinkValues, sink, sinkValues, err := pg.split(sym, values)
+
+ // check if menu is sink aswell, fail if it is.
+ if pg.menu != nil {
+ // if pg.menu.ReservedSize()
+ }
+
+ // pre-render template without sink
+ // this includes the menu before any browsing options have been added
+ pg.sizer.AddCursor(0)
+ s, err := pg.render(ctx, sym, noSinkValues, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ // this is the available bytes left for sink content and browse menu
+ remaining, ok := pg.sizer.Check(s)
+ if !ok {
+ return nil, fmt.Errorf("capacity exceeded")
+ }
- noSinkValues[sink] = r
+ // pre-calculate the menu sizes for all browse conditions
+ var menuSizes [4]uint32
+ if pg.menu != nil {
+ menuSizes, err = pg.menu.Sizes()
+ if err != nil {
+ return nil, err
+ }
+ }
+ Logg.Debugf("calculated pre-navigation allocation", "bytes", remaining, "menusizes", menuSizes)
+
+ // process sink values array into newline-separated string
+ sinkString, count, err := pg.joinSink(sinkValues, remaining, menuSizes)
+ if err != nil {
+ return nil, err
+ }
+ noSinkValues[sink] = sinkString
+ // update the page count of the menu
if pg.menu != nil {
pg.menu = pg.menu.WithPageCount(count)
}
- for i, v := range strings.Split(r, "\n") {
+ // write all sink values to log.
+ for i, v := range strings.Split(sinkString, "\n") {
Logg.Tracef("nosinkvalue", "idx", i, "value", v)
}
diff --git a/render/page_test.go b/render/page_test.go
@@ -55,7 +55,7 @@ func TestPageCurrentSize(t *testing.T) {
t.Errorf("expected remaining length 34, got %v", c)
}
- mn := NewMenu().WithOutputSize(32)
+ mn := NewMenu() //.WithOutputSize(32)
pg = pg.WithMenu(mn)
l, c, err = pg.Usage()
if err != nil {
@@ -124,7 +124,7 @@ func TestWithError(t *testing.T) {
pg := NewPage(ca, rs)
ca.Push()
- mn := NewMenu().WithOutputSize(32)
+ mn := NewMenu() //.WithOutputSize(32)
err := mn.Put("0", "aiee")
if err != nil {
t.Fatal(err)
diff --git a/render/size.go b/render/size.go
@@ -9,7 +9,7 @@ import (
// Sizer splits dynamic contents into individual segments for browseable pages.
type Sizer struct {
outputSize uint32 // maximum output for a single page.
- menuSize uint16 // actual menu size for the dynamic page being sized
+// menuSize uint16 // actual menu size for the dynamic page being sized
memberSizes map[string]uint16 // individual byte sizes of all content to be rendered by template.
totalMemberSize uint32 // total byte size of all content to be rendered by template (sum of memberSizes)
crsrs []uint32 // byte offsets in the sink content for browseable pages indices.
@@ -25,10 +25,10 @@ func NewSizer(outputSize uint32) *Sizer {
}
// WithMenuSize sets the size of the menu being used in the rendering context.
-func(szr *Sizer) WithMenuSize(menuSize uint16) *Sizer {
- szr.menuSize = menuSize
- return szr
-}
+//func(szr *Sizer) WithMenuSize(menuSize uint16) *Sizer {
+// szr.menuSize = menuSize
+// return szr
+//}
// Set adds a content symbol in the state it will be used by the renderer.
func(szr *Sizer) Set(key string, size uint16) error {
@@ -56,11 +56,12 @@ func(szr *Sizer) Check(s string) (uint32, bool) {
// String implements the String interface.
func(szr *Sizer) String() string {
- var diff uint32
- if szr.outputSize > 0 {
- diff = szr.outputSize - szr.totalMemberSize - uint32(szr.menuSize)
- }
- return fmt.Sprintf("output: %v, member: %v, menu: %v, diff: %v", szr.outputSize, szr.totalMemberSize, szr.menuSize, diff)
+// var diff uint32
+// if szr.outputSize > 0 {
+// diff = szr.outputSize - szr.totalMemberSize - uint32(szr.menuSize)
+// }
+// return fmt.Sprintf("output: %v, member: %v, menu: %v, diff: %v", szr.outputSize, szr.totalMemberSize, szr.menuSize, diff)
+ return fmt.Sprintf("output: %v, member: %v", szr.outputSize, szr.totalMemberSize)
}
// Size gives the byte size of content for a single symbol.
@@ -75,9 +76,9 @@ func(szr *Sizer) Size(s string) (uint16, error) {
}
// Menusize returns the currently defined menu size.
-func(szr *Sizer) MenuSize() uint16 {
- return szr.menuSize
-}
+//func(szr *Sizer) MenuSize() uint16 {
+// return szr.menuSize
+//}
// AddCursor adds a pagination cursor for the paged sink content.
func(szr *Sizer) AddCursor(c uint32) {
diff --git a/render/size_test.go b/render/size_test.go
@@ -129,7 +129,7 @@ func TestSizeCheck(t *testing.T) {
func TestSizeLimit(t *testing.T) {
st := state.NewState(0)
ca := cache.NewCache()
- mn := NewMenu().WithOutputSize(32)
+ mn := NewMenu() //.WithOutputSize(32)
//mrs := NewMenuResource() //.WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate)
//rs := TestSizeResource{
// mrs,
@@ -182,7 +182,7 @@ func TestSizeLimit(t *testing.T) {
func TestSizePages(t *testing.T) {
st := state.NewState(0)
ca := cache.NewCache()
- mn := NewMenu().WithOutputSize(32)
+ mn := NewMenu() //.WithOutputSize(32)
//mrs := NewMenuResource() //.WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate)
//rs := TestSizeResource{
// mrs,
@@ -243,7 +243,7 @@ func TestManySizes(t *testing.T) {
for i := 60; i < 160; i++ {
st := state.NewState(0)
ca := cache.NewCache()
- mn := NewMenu().WithOutputSize(32)
+ mn := NewMenu() //.WithOutputSize(32)
rs := NewTestSizeResource() //.WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate)
//rs := TestSizeResource{
// mrs,
@@ -273,7 +273,7 @@ func TestManySizesMenued(t *testing.T) {
for i := 60; i < 160; i++ {
st := state.NewState(0)
ca := cache.NewCache()
- mn := NewMenu().WithOutputSize(32)
+ mn := NewMenu() //.WithOutputSize(32)
rs := NewTestSizeResource()
szr := NewSizer(uint32(i))
pg := NewPage(ca, rs).WithSizer(szr).WithMenu(mn)
diff --git a/render/split.go b/render/split.go
@@ -0,0 +1,207 @@
+package render
+
+import (
+ "fmt"
+ "log"
+ "strings"
+)
+
+
+func bookmark(values []string) []uint32 {
+ var c int
+ var bookmarks []uint32 = []uint32{0}
+
+ for _, v := range values {
+ c += len(v) + 1
+ bookmarks = append(bookmarks, uint32(c))
+ }
+ return bookmarks
+}
+//
+//func paginate(bookmarks []uint32, capacity uint32) ([][]uint32, error) {
+// if len(bookmarks) == 0 {
+// return nil, fmt.Errorf("empty bookmark array")
+// }
+// var c uint32
+// var pages [][]uint32
+// var prev uint32
+//
+// pages = append(pages, []uint32{})
+// currentPage := 0
+// lookAhead := bookmarks[1:]
+//
+// for i, v := range lookAhead {
+// lc := v - c
+// if lc > capacity {
+// c = prev
+// if lc - c > capacity {
+// return nil, fmt.Errorf("value at %v alone exceeds capacity", i)
+// }
+// currentPage += 1
+// pages = append(pages, []uint32{})
+// }
+// pages[currentPage] = append(pages[currentPage], bookmarks[i])
+// prev = v
+// }
+//
+// pages[currentPage] = append(pages[currentPage], bookmarks[len(bookmarks)-1])
+// return pages, nil
+//}
+
+func isLast(cursor uint32, end uint32, capacity uint32) bool {
+ l := end - cursor
+ remaining := capacity
+ return l <= remaining
+}
+
+func paginate(bookmarks []uint32, capacity uint32, nextSize uint32, prevSize uint32) ([][]uint32, error) {
+ if len(bookmarks) == 0 {
+ return nil, fmt.Errorf("empty page array")
+ }
+
+ var pages [][]uint32
+ var c uint32
+ lastIndex := len(bookmarks) - 1
+ last := bookmarks[lastIndex]
+ var haveMore bool
+
+ if isLast(0, last, capacity) {
+ pages = append(pages, bookmarks)
+ return pages, nil
+ }
+
+ lookAhead := bookmarks[1:]
+ pages = append(pages, []uint32{})
+ var i int
+
+ haveMore = true
+ for haveMore {
+ remaining := int(capacity)
+ if i > 0 {
+ remaining -= int(prevSize)
+ }
+ if remaining < 0 {
+ return nil, fmt.Errorf("underrun in item %v:%v (%v) index %v prevsize %v remain %v cap %v", bookmarks[i], lookAhead[i], lookAhead[i] - bookmarks[i], i, prevSize, remaining, capacity)
+ }
+ if isLast(c, last, uint32(remaining)) {
+ haveMore = false
+ } else {
+ remaining -= int(nextSize)
+ }
+ if remaining < 0 {
+ return nil, fmt.Errorf("underrun in item %v:%v (%v) index %v prevsize %v nextsize %v remain %v cap %v", bookmarks[i], lookAhead[i], lookAhead[i] - bookmarks[i], i, prevSize, nextSize, remaining, capacity)
+ }
+
+ var z int
+ currentPage := len(pages) - 1
+ for i < lastIndex {
+ log.Printf("have item %v:%v (%v) index %v prevsize %v nextsize %v remain %v cap %v", bookmarks[i], lookAhead[i], lookAhead[i] - bookmarks[i], i, prevSize, nextSize, remaining, capacity)
+
+ v := lookAhead[i]
+ delta := int((v - c) + 1)
+ if z == 0 {
+ if delta > remaining {
+ return nil, fmt.Errorf("single value at %v exceeds capacity", i)
+ }
+ }
+ z += delta
+ if z > remaining {
+ break
+ }
+ pages[currentPage] = append(pages[currentPage], bookmarks[i])
+ c = v
+ i += 1
+ }
+ log.Printf("more %v remaining %v c %v last %v pages %v", haveMore, remaining, c, last, pages)
+
+ if haveMore {
+ pages = append(pages, []uint32{})
+ }
+ }
+
+ l := len(pages)-1
+ pages[l] = append(pages[l], last)
+ return pages, nil
+}
+
+func explode(values []string, pages [][]uint32) string {
+ s := strings.Join(values, "")
+ s += "\n"
+ sb := strings.Builder{}
+
+ var start uint32
+ var end uint32
+ var lastPage int
+ var z uint32
+ for i, page := range pages {
+ for _, c := range page {
+ if c == 0 {
+ continue
+ }
+ z += 1
+ if i != lastPage {
+ sb.WriteRune('\n')
+ } else if c > 0 {
+ sb.WriteByte(byte(0x00))
+ }
+ end = c - z
+ v := s[start:end]
+ log.Printf("page %v part %v %v %s", i, start, end, v)
+ v = s[start:end]
+ sb.WriteString(v)
+ start = end
+ }
+ lastPage = i
+ }
+ r := sb.String()
+ r = strings.TrimRight(r, "\n")
+ return r
+}
+
+// if lastCursor <= capacity {
+// return pages, nil
+// }
+//
+// var flatPages [][]uint32
+//
+// pages = append(pages, []uint32{})
+// for _, v := range bookmarks {
+// for _, vv := range v {
+// pages[0] = append(pages[0], vv)
+// }
+// }
+//
+// var c uint32
+// var prev uint32
+// currentPage := 0
+//
+// for i, page := range pages {
+// var delta uint32
+// if i == 0 {
+// delta = nextSize
+// } else if i == len(pages) - 1 {
+// delta = prevSize
+// } else {
+// delta = nextSize + prevSize
+// }
+// remaining := capacity - delta
+// log.Printf("processing page %v", page)
+// lookAhead := page[1:]
+//
+// for j, v := range lookAhead {
+// lc := v - c
+// log.Printf("currentpage j %v lc %v v %v remain %v", j, lc, v, remaining)
+// if lc > remaining {
+// c = prev
+// if lc - c > remaining {
+// return nil, fmt.Errorf("value at page %v idx %v alone exceeds capacity", i, v)
+// }
+// currentPage += 1
+// page = append(page, []uint32{})
+// }
+// page[currentPage] = append(page[currentPage], page[j])
+// prev = v
+// }
+// }
+// return page, nil
+//}
diff --git a/render/split_test.go b/render/split_test.go
@@ -0,0 +1,109 @@
+package render
+
+import (
+// "bytes"
+// "log"
+ "testing"
+)
+
+func TestSplitBookmark(t *testing.T) {
+ vals := []string{"inky", "pinky", "blinky", "clyde"}
+ r := bookmark(vals)
+ expect := []uint32{0, 5, 11, 18, 24}
+ for i, v := range expect {
+ if r[i] != v {
+ t.Fatalf("expected val %v cursor %v, got %v", i, v, r[i])
+ }
+ }
+}
+
+func TestSplitPaginate(t *testing.T) {
+ vals := []string{"inky", "pinky", "blinky", "clyde"}
+ v := bookmark(vals)
+ r, err := paginate(v, 15, 0, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(r) != 2 {
+ t.Fatalf("expected bookmark len 2, got %v", len(r))
+ }
+ expect := []uint32{0, 5}
+ if len(r[0]) != len(expect) {
+ t.Fatalf("expected page 1 len %v, got %v", len(expect), len(r[0]))
+ }
+ for i, v := range expect {
+ if r[0][i] != v {
+ t.Fatalf("expected page 1 val %v cursor %v, got %v", i, v, r[0][i])
+ }
+ }
+ expect = []uint32{11, 18, 24}
+ if len(r[1]) != len(expect) {
+ t.Fatalf("expected page 2 len %v, got %v", len(expect), len(r[1]))
+ }
+ for i, v := range expect {
+ if r[1][i] != v {
+ t.Fatalf("expected page 2 val %v cursor %v, got %v", i, v, r[1][i])
+ }
+ }
+}
+
+//func TestSplitMenuPaginate(t *testing.T) {
+// menuCfg := DefaultBrowseConfig()
+// menu := NewMenu().WithBrowseConfig(menuCfg)
+// menu.Put("0", "foo")
+// menu.Put("1", "bar")
+//
+// vals := []string{"inky", "pinky", "blinky", "clyde", "tinkywinky", "dipsy", "lala", "pu"}
+// v := bookmark(vals)
+//// vv, err := paginate(v, 15, 0, 0)
+//// if err != nil {
+//// t.Fatal(err)
+//// }
+//
+// menu = menu.WithPages()
+// menuSizes, err := menu.Sizes()
+// log.Printf("sizes %v", menuSizes)
+// if err != nil {
+// t.Fatal(err)
+// }
+// r, err := paginate(v, 30, menuSizes[1], menuSizes[2])
+// if err != nil {
+// t.Fatal(err)
+// }
+// expect := [][]uint32{
+// []uint32{0, 5, 11},
+// []uint32{18},
+// []uint32{24},
+// []uint32{35, 41, 46},
+// }
+// if len(r) != len(expect) {
+// t.Fatalf("expected page 1 len %v, got %v", len(expect), len(r))
+// }
+// for i, v := range expect {
+// for j, vv := range v {
+// if r[i][j] != vv {
+// t.Fatalf("value mismatch in [%v][%v]", i, j)
+// }
+// }
+// }
+//
+// s := explode(vals, r)
+// expectBytes := append([]byte("inky"), byte(0x00))
+// expectBytes = append(expectBytes, []byte("pinky")...)
+// expectBytes = append(expectBytes, byte(0x00))
+// expectBytes = append(expectBytes, []byte("blinky")...)
+// expectBytes = append(expectBytes, byte(0x0a))
+// expectBytes = append(expectBytes, []byte("clyde")...)
+// expectBytes = append(expectBytes, byte(0x0a))
+// expectBytes = append(expectBytes, []byte("tinkywinky")...)
+// expectBytes = append(expectBytes, byte(0x0a))
+// expectBytes = append(expectBytes, []byte("dipsy")...)
+// expectBytes = append(expectBytes, byte(0x00))
+// expectBytes = append(expectBytes, []byte("lala")...)
+// expectBytes = append(expectBytes, byte(0x00))
+// expectBytes = append(expectBytes, []byte("pu")...)
+//
+// if !bytes.Equal([]byte(s), expectBytes) {
+// t.Fatalf("expected:\n\t%s\ngot:\n\t%x\n", expectBytes, s)
+// }
+//}