diff --git a/go/cache/cache.go b/go/cache/cache.go index 94005e6..ef41658 100644 --- a/go/cache/cache.go +++ b/go/cache/cache.go @@ -9,11 +9,10 @@ type Cache struct { CacheSize uint32 // Total allowed cumulative size of values (not code) in cache CacheUseSize uint32 // Currently used bytes by all values (not code) in cache Cache []map[string]string // All loaded cache items - CacheMap map[string]string // Mapped - menuSize uint16 // Max size of menu - outputSize uint32 // Max size of output + //CacheMap map[string]string // Mapped + //outputSize uint32 // Max size of output sizes map[string]uint16 // Size limits for all loaded symbols. - sink *string + //sink *string } // NewCache creates a new ready-to-use cache object @@ -22,7 +21,7 @@ func NewCache() *Cache { Cache: []map[string]string{make(map[string]string)}, sizes: make(map[string]uint16), } - ca.resetCurrent() + //ca.resetCurrent() return ca } @@ -32,12 +31,6 @@ func(ca *Cache) WithCacheSize(cacheSize uint32) *Cache { return ca } -// WithCacheSize applies a cumulative cache size limitation for all cached items. -func(ca *Cache) WithOutputSize(outputSize uint32) *Cache { - ca.outputSize = outputSize - return ca -} - // Add adds a cache value under a cache symbol key. // // Also stores the size limitation of for key for later updates. @@ -72,6 +65,15 @@ func(ca *Cache) Add(key string, value string, sizeLimit uint16) error { return nil } +// ReservedSize returns the maximum byte size available for the given symbol. +func(ca *Cache) ReservedSize(key string) (uint16, error) { + v, ok := ca.sizes[key] + if !ok { + return 0, fmt.Errorf("unknown symbol: %s", key) + } + return v, nil +} + // Update sets a new value for an existing key. // // Uses the size limitation from when the key was added. @@ -95,9 +97,9 @@ func(ca *Cache) Update(key string, value string) error { r := ca.Cache[checkFrame][key] l := uint32(len(r)) ca.Cache[checkFrame][key] = "" - if ca.CacheMap[key] != "" { - ca.CacheMap[key] = value - } + //if ca.CacheMap[key] != "" { + // ca.CacheMap[key] = value + //} ca.CacheUseSize -= l sz := ca.checkCapacity(value) if sz == 0 { @@ -117,58 +119,6 @@ func(ca *Cache) Get() (map[string]string, error) { return ca.Cache[len(ca.Cache)-1], nil } -func(ca *Cache) Sizes() (map[string]uint16, error) { - if len(ca.Cache) == 0 { - return nil, fmt.Errorf("get at top frame") - } - sizes := make(map[string]uint16) - var haveSink bool - for k, _ := range ca.CacheMap { - l, ok := ca.sizes[k] - if !ok { - panic(fmt.Sprintf("missing size for %v", k)) - } - if l == 0 { - if haveSink { - panic(fmt.Sprintf("duplicate sink for %v", k)) - } - haveSink = true - } - sizes[k] = l - } - return sizes, nil -} - -// Map marks the given key for retrieval. -// -// After this, Val() will return the value for the key, and Size() will include the value size and limitations in its calculations. -// -// Only one symbol with no size limitation may be mapped at the current level. -func(ca *Cache) Map(key string) error { - m, err := ca.Get() - if err != nil { - return err - } - l := ca.sizes[key] - if l == 0 { - if ca.sink != nil { - return fmt.Errorf("sink already set to symbol '%v'", *ca.sink) - } - ca.sink = &key - } - ca.CacheMap[key] = m[key] - return nil -} - -// Fails if key is not mapped. -func(ca *Cache) Val(key string) (string, error) { - r := ca.CacheMap[key] - if len(r) == 0 { - return "", fmt.Errorf("key %v not mapped", key) - } - return r, nil -} - // Reset flushes all state contents below the top level. func(ca *Cache) Reset() { if len(ca.Cache) == 0 { @@ -179,36 +129,11 @@ func(ca *Cache) Reset() { return } -// Size returns size used by values and menu, and remaining size available -func(ca *Cache) Usage() (uint32, uint32) { - var l int - var c uint16 - for k, v := range ca.CacheMap { - l += len(v) - c += ca.sizes[k] - } - r := uint32(l) - r += uint32(ca.menuSize) - return r, uint32(c)-r -} - -// return 0-indexed frame number where key is defined. -1 if not defined -func(ca *Cache) frameOf(key string) int { - for i, m := range ca.Cache { - for k, _ := range m { - if k == key { - return i - } - } - } - return -1 -} - // Push adds a new level to the cache. func (ca *Cache) Push() error { m := make(map[string]string) ca.Cache = append(ca.Cache, m) - ca.resetCurrent() + //ca.resetCurrent() return nil } @@ -228,7 +153,7 @@ func (ca *Cache) Pop() error { log.Printf("free frame %v key %v value size %v", l, k, sz) } ca.Cache = ca.Cache[:l] - ca.resetCurrent() + //ca.resetCurrent() return nil } @@ -238,10 +163,10 @@ func(ca *Cache) Check(key string) bool { } // flush relveant properties for level change -func(ca *Cache) resetCurrent() { - ca.sink = nil - ca.CacheMap = make(map[string]string) -} +//func(ca *Cache) resetCurrent() { +// ca.sink = nil +// ca.CacheMap = make(map[string]string) +//} // bytes that will be added to cache use size for string // returns 0 if capacity would be exceeded @@ -255,3 +180,15 @@ func(ca *Cache) checkCapacity(v string) uint32 { } return sz } + +// return 0-indexed frame number where key is defined. -1 if not defined +func(ca *Cache) frameOf(key string) int { + for i, m := range ca.Cache { + for k, _ := range m { + if k == key { + return i + } + } + } + return -1 +} diff --git a/go/cache/cache_test.go b/go/cache/cache_test.go index 8eda761..3b5d2b5 100644 --- a/go/cache/cache_test.go +++ b/go/cache/cache_test.go @@ -4,7 +4,6 @@ import ( "testing" ) - func TestNewCache(t *testing.T) { ca := NewCache() if ca.CacheSize != 0 { @@ -34,7 +33,6 @@ func TestStateCacheUse(t *testing.T) { } } - func TestStateDownUp(t *testing.T) { ca := NewCache() err := ca.Push() @@ -104,83 +102,3 @@ func TestCacheLoadDup(t *testing.T) { } } -func TestCacheCurrentSize(t *testing.T) { - ca := NewCache() - err := ca.Push() - if err != nil { - t.Error(err) - } - err = ca.Add("foo", "inky", 0) - if err != nil { - t.Error(err) - } - err = ca.Push() - err = ca.Add("bar", "pinky", 10) - if err != nil { - t.Error(err) - } - err = ca.Map("bar") - if err != nil { - t.Error(err) - } - err = ca.Add("baz", "tinkywinkydipsylalapoo", 51) - if err != nil { - t.Error(err) - } - err = ca.Map("baz") - if err != nil { - t.Error(err) - } - - l, c := ca.Usage() - if l != 27 { - t.Errorf("expected actual length 27, got %v", l) - } - if c != 34 { - t.Errorf("expected remaining length 34, got %v", c) - } -} - -func TestStateMapSink(t *testing.T) { - ca := NewCache() - ca.Push() - err := ca.Add("foo", "bar", 0) - if err != nil { - t.Error(err) - } - ca.Push() - err = ca.Add("bar", "xyzzy", 6) - if err != nil { - t.Error(err) - } - err = ca.Add("baz", "bazbaz", 18) - if err != nil { - t.Error(err) - } - err = ca.Add("xyzzy", "plugh", 0) - if err != nil { - t.Error(err) - } - err = ca.Map("foo") - if err != nil { - t.Error(err) - } - err = ca.Map("xyzzy") - if err == nil { - t.Errorf("Expected fail on duplicate sink") - } - err = ca.Map("baz") - if err != nil { - t.Error(err) - } - ca.Push() - err = ca.Map("foo") - if err != nil { - t.Error(err) - } - ca.Pop() - err = ca.Map("foo") - if err != nil { - t.Error(err) - } -} diff --git a/go/menu/menu.go b/go/render/menu.go similarity index 84% rename from go/menu/menu.go rename to go/render/menu.go index 3f05420..126df0f 100644 --- a/go/menu/menu.go +++ b/go/render/menu.go @@ -1,8 +1,7 @@ -package menu +package render import ( "fmt" - "log" ) // BrowseConfig defines the availability and display parameters for page browsing. @@ -33,6 +32,7 @@ type Menu struct { pageCount uint16 canNext bool canPrevious bool + outputSize uint16 } // NewMenu creates a new Menu with an explicit page count. @@ -46,17 +46,21 @@ 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 + return m +} + // WithBrowseConfig defines the criteria for page browsing. func(m *Menu) WithBrowseConfig(cfg BrowseConfig) *Menu { m.browse = cfg - return m } // Put adds a menu option to the menu rendering. func(m *Menu) Put(selector string, title string) error { m.menu = append(m.menu, [2]string{selector, title}) - log.Printf("menu %v", m.menu) return nil } @@ -64,6 +68,11 @@ func(m *Menu) Put(selector string, title string) error { // // After this has been executed, the state of the menu will be empty. func(m *Menu) Render(idx uint16) (string, error) { + var menuCopy [][2]string + for _, v := range m.menu { + menuCopy = append(menuCopy, v) + } + err := m.applyPage(idx) if err != nil { return "", err @@ -81,28 +90,30 @@ func(m *Menu) Render(idx uint16) (string, error) { } r += fmt.Sprintf("%s:%s", choice, title) } + m.menu = menuCopy return r, nil } // add available browse options. func(m *Menu) applyPage(idx uint16) error { if m.pageCount == 0 { + if idx > 0 { + return fmt.Errorf("index %v > 0 for non-paged menu", idx) + } return nil } else if idx >= m.pageCount { return fmt.Errorf("index %v out of bounds (%v)", idx, m.pageCount) } - if m.browse.NextAvailable { - m.canNext = true - } - if m.browse.PreviousAvailable { - m.canPrevious = true - } + + m.reset() + if idx == m.pageCount - 1 { m.canNext = false } if idx == 0 { m.canPrevious = false } + if m.canNext { err := m.Put(m.browse.NextSelector, m.browse.NextTitle) if err != nil { @@ -129,4 +140,15 @@ func(m *Menu) shiftMenu() (string, string, error) { return r[0], r[1], nil } +func(m *Menu) reset() { + if m.browse.NextAvailable { + m.canNext = true + } + if m.browse.PreviousAvailable { + m.canPrevious = true + } +} +func(m *Menu) ReservedSize() uint16 { + return m.outputSize +} diff --git a/go/menu/menu_test.go b/go/render/menu_test.go similarity index 82% rename from go/menu/menu_test.go rename to go/render/menu_test.go index 3ea4402..faf753e 100644 --- a/go/menu/menu_test.go +++ b/go/render/menu_test.go @@ -1,4 +1,4 @@ -package menu +package render import ( "testing" @@ -23,6 +23,12 @@ func TestMenuInit(t *testing.T) { if r != expect { t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r) } + + r, err = m.Render(1) + if err == nil { + t.Fatalf("expected render fail") + } + } func TestMenuBrowse(t *testing.T) { @@ -48,14 +54,6 @@ func TestMenuBrowse(t *testing.T) { t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r) } - err = m.Put("1", "foo") - if err != nil { - t.Fatal(err) - } - err = m.Put("2", "bar") - if err != nil { - t.Fatal(err) - } r, err = m.Render(1) if err != nil { t.Fatal(err) @@ -68,14 +66,6 @@ func TestMenuBrowse(t *testing.T) { t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r) } - err = m.Put("1", "foo") - if err != nil { - t.Fatal(err) - } - err = m.Put("2", "bar") - if err != nil { - t.Fatal(err) - } r, err = m.Render(2) if err != nil { t.Fatal(err) diff --git a/go/render/page.go b/go/render/page.go new file mode 100644 index 0000000..02e34a6 --- /dev/null +++ b/go/render/page.go @@ -0,0 +1,289 @@ +package render + +import ( + "bytes" + "fmt" + "log" + "strings" + "text/template" + + "git.defalsify.org/festive/cache" + "git.defalsify.org/festive/resource" +) + +type Page struct { + cacheMap map[string]string // Mapped + cache *cache.Cache + resource resource.Resource + menu *Menu + sink *string + sinkSize uint16 + sizer *Sizer + sinkProcessed bool +} + +func NewPage(cache *cache.Cache, rs resource.Resource) *Page { + return &Page{ + cache: cache, + cacheMap: make(map[string]string), + resource: rs, + } +} + +func(pg *Page) WithMenu(menu *Menu) *Page { + pg.menu = menu + if pg.sizer != nil { + pg.sizer = pg.sizer.WithMenuSize(pg.menu.ReservedSize()) + } + return pg +} + +func(pg *Page) WithSizer(sizer *Sizer) *Page { + pg.sizer = sizer + if pg.menu != nil { + pg.sizer = pg.sizer.WithMenuSize(pg.menu.ReservedSize()) + } + return pg +} + +// Size returns size used by values and menu, and remaining size available +func(pg *Page) Usage() (uint32, uint32, error) { + var l int + var c uint16 + for k, v := range pg.cacheMap { + l += len(v) + sz, err := pg.cache.ReservedSize(k) + if err != nil { + return 0, 0, err + } + c += sz + } + r := uint32(l) + if pg.menu != nil { + r += uint32(pg.menu.ReservedSize()) + } + return r, uint32(c)-r, nil +} + +// Map marks the given key for retrieval. +// +// After this, Val() will return the value for the key, and Size() will include the value size and limitations in its calculations. +// +// Only one symbol with no size limitation may be mapped at the current level. +func(pg *Page) Map(key string) error { + m, err := pg.cache.Get() + if err != nil { + return err + } + l, err := pg.cache.ReservedSize(key) + if err != nil { + return err + } + if l == 0 { + if pg.sink != nil { + return fmt.Errorf("sink already set to symbol '%v'", *pg.sink) + } + pg.sink = &key + } + pg.cacheMap[key] = m[key] + if pg.sizer != nil { + err := pg.sizer.Set(key, l) + if err != nil { + return err + } + } + return nil +} + +// Fails if key is not mapped. +func(pg *Page) Val(key string) (string, error) { + r := pg.cacheMap[key] + if len(r) == 0 { + return "", fmt.Errorf("key %v not mapped", key) + } + return r, nil +} + +// Moved from cache, MAP should hook to this object +func(pg *Page) Sizes() (map[string]uint16, error) { + sizes := make(map[string]uint16) + var haveSink bool + for k, _ := range pg.cacheMap { + l, err := pg.cache.ReservedSize(k) + if err != nil { + return nil, err + } + if l == 0 { + if haveSink { + panic(fmt.Sprintf("duplicate sink for %v", k)) + } + haveSink = true + } + pg.sinkSize = l + } + return sizes, nil +} + +// DefaultRenderTemplate is an adapter to implement the builtin golang text template renderer as resource.RenderTemplate. +func(pg *Page) RenderTemplate(sym string, values map[string]string, idx uint16) (string, error) { + tpl, err := pg.resource.GetTemplate(sym) + if err != nil { + return "", err + } + if pg.sizer != nil { + values, err = pg.sizer.GetAt(values, idx) + if err != nil { + return "", err + } + } else if idx > 0 { + return "", fmt.Errorf("sizer needed for indexed render") + } + log.Printf("render for index: %v", idx) + + tp, err := template.New("tester").Option("missingkey=error").Parse(tpl) + if err != nil { + return "", err + } + + b := bytes.NewBuffer([]byte{}) + err = tp.Execute(b, values) + if err != nil { + return "", err + } + return b.String(), err +} + +// render menu and all syms except sink, split sink into display chunks +func(pg *Page) prepare(sym string, values map[string]string, idx uint16) (map[string]string, error) { + var sink string + 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 + } + if sz == 0 { + sink = k + sinkValues = strings.Split(v, "\n") + v = "" + log.Printf("found sink %s with field count %v", k, len(sinkValues)) + } + noSinkValues[k] = v + } + + if sink == "" { + log.Printf("no sink found for sym %s", sym) + return values, nil + } + + pg.sizer.AddCursor(0) + s, err := pg.render(sym, noSinkValues, 0) + if err != nil { + return nil, err + } + + remaining, ok := pg.sizer.Check(s) + if !ok { + return nil, fmt.Errorf("capacity exceeded") + } + + log.Printf("%v bytes available for sink split", remaining) + + l := 0 + var count uint16 + tb := strings.Builder{} + rb := strings.Builder{} + + for i, v := range sinkValues { + log.Printf("processing sinkvalue %v: %s", i, v) + l += len(v) + if uint32(l) > remaining { + if tb.Len() == 0 { + return nil, fmt.Errorf("capacity insufficient for sink field %v", i) + } + rb.WriteString(tb.String()) + rb.WriteRune('\n') + c := uint32(rb.Len()) + pg.sizer.AddCursor(c) + tb.Reset() + l = 0 + count += 1 + } + if tb.Len() > 0 { + tb.WriteByte(byte(0x00)) + } + tb.WriteString(v) + } + + if tb.Len() > 0 { + rb.WriteString(tb.String()) + count += 1 + } + + r := rb.String() + r = strings.TrimRight(r, "\n") + + noSinkValues[sink] = r + + if pg.menu != nil { + pg.menu = pg.menu.WithPageCount(count) + } + + for i, v := range strings.Split(r, "\n") { + log.Printf("nosinkvalue %v: %s", i, v) + } + + return noSinkValues, nil +} + +func(pg *Page) render(sym string, values map[string]string, idx uint16) (string, error) { + var ok bool + r := "" + s, err := pg.RenderTemplate(sym, values, idx) + if err != nil { + return "", err + } + log.Printf("rendered %v bytes for template", len(s)) + r += s + if pg.sizer != nil { + _, ok = pg.sizer.Check(r) + if !ok { + return "", fmt.Errorf("limit exceeded: %v", pg.sizer) + } + } + s, err = pg.menu.Render(idx) + if err != nil { + return "", err + } + log.Printf("rendered %v bytes for menu", len(s)) + r += "\n" + s + if pg.sizer != nil { + _, ok = pg.sizer.Check(r) + if !ok { + return "", fmt.Errorf("limit exceeded: %v", pg.sizer) + } + } + return r, nil +} + +func(pg *Page) Render(sym string, values map[string]string, idx uint16) (string, error) { + var err error + + values, err = pg.prepare(sym, values, idx) + if err != nil { + return "", err + } + + log.Printf("nosink %v", values) + return pg.render(sym, values, idx) +} + +func(pg *Page) Reset() { + pg.sink = nil + pg.sinkSize = 0 + pg.sinkProcessed = false + pg.cacheMap = make(map[string]string) +} + + diff --git a/go/resource/size.go b/go/render/size.go similarity index 76% rename from go/resource/size.go rename to go/render/size.go index 99da5ce..96e3ee4 100644 --- a/go/resource/size.go +++ b/go/render/size.go @@ -1,12 +1,10 @@ -package resource +package render import ( "bytes" "fmt" "log" "strings" - - "git.defalsify.org/festive/state" ) type Sizer struct { @@ -18,28 +16,34 @@ type Sizer struct { sink string } -func SizerFromState(st *state.State) (Sizer, error){ - sz := Sizer{ - outputSize: st.GetOutputSize(), - menuSize: st.GetMenuSize(), +func NewSizer(outputSize uint32) *Sizer { + return &Sizer{ + outputSize: outputSize, memberSizes: make(map[string]uint16), } +} - sizes, err := st.Sizes() - if err != nil { - return sz, err +func(szr *Sizer) WithMenuSize(menuSize uint16) *Sizer { + szr.menuSize = menuSize + return szr +} + +func(szr *Sizer) Set(key string, size uint16) error { + var ok bool + _, ok = szr.memberSizes[key] + if ok { + return fmt.Errorf("already have key %s", key) } - for k, v := range sizes { - if v == 0 { - sz.sink = k - } - sz.memberSizes[k] = v - sz.totalMemberSize += uint32(v) + szr.memberSizes[key] = size + if size == 0 { + szr.sink = key } - return sz, nil + szr.totalMemberSize += uint32(size) + return nil } func(szr *Sizer) Check(s string) (uint32, bool) { + log.Printf("sizercheck %s", s) l := uint32(len(s)) if szr.outputSize > 0 { if l > szr.outputSize { diff --git a/go/render/size_test.go b/go/render/size_test.go new file mode 100644 index 0000000..4857a13 --- /dev/null +++ b/go/render/size_test.go @@ -0,0 +1,181 @@ +package render + +import ( + "context" + "fmt" + "testing" + + "git.defalsify.org/festive/state" + "git.defalsify.org/festive/resource" + "git.defalsify.org/festive/cache" +) + +type TestSizeResource struct { + *resource.MenuResource +} + +func getTemplate(sym string) (string, error) { + var tpl string + switch sym { + case "small": + tpl = "one {{.foo}} two {{.bar}} three {{.baz}}" + case "toobig": + tpl = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus in mattis lorem. Aliquam erat volutpat. Ut vitae metus." + case "pages": + tpl = "one {{.foo}} two {{.bar}} three {{.baz}}\n{{.xyzzy}}" + } + return tpl, nil +} + +func funcFor(sym string) (resource.EntryFunc, error) { + switch sym { + case "foo": + return getFoo, nil + case "bar": + return getBar, nil + case "baz": + return getBaz, nil + case "xyzzy": + return getXyzzy, nil + } + return nil, fmt.Errorf("unknown func: %s", sym) +} + +func getFoo(ctx context.Context) (string, error) { + return "inky", nil +} + +func getBar(ctx context.Context) (string, error) { + return "pinky", nil +} + +func getBaz(ctx context.Context) (string, error) { + return "blinky", nil +} + +func getXyzzy(ctx context.Context) (string, error) { + return "inky pinky\nblinky clyde sue\ntinkywinky dipsy\nlala poo\none two three four five six seven\neight nine ten\neleven twelve", nil +} + +func TestSizeCheck(t *testing.T) { + szr := NewSizer(16) + l, ok := szr.Check("foobar") + if !ok { + t.Fatalf("expected ok") + } + if l != 10 { + t.Fatalf("expected 10, got %v", l) + } + + l, ok = szr.Check("inkypinkyblinkyclyde") + if ok { + t.Fatalf("expected not ok") + } + if l != 0 { + t.Fatalf("expected 0, got %v", l) + } +} + +func TestSizeLimit(t *testing.T) { + st := state.NewState(0) + ca := cache.NewCache() + mn := NewMenu().WithOutputSize(32) + mrs := resource.NewMenuResource().WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate) + rs := TestSizeResource{ + mrs, + } + szr := NewSizer(128) + pg := NewPage(ca, rs).WithMenu(mn).WithSizer(szr) + ca.Push() + st.Down("test") + ca.Add("foo", "inky", 4) + ca.Add("bar", "pinky", 10) + ca.Add("baz", "blinky", 0) + pg.Map("foo") + pg.Map("bar") + pg.Map("baz") + + mn.Put("1", "foo the foo") + mn.Put("2", "go to bar") + + vals, err := ca.Get() + if err != nil { + t.Fatal(err) + } + + _, err = pg.Render("small", vals, 0) + if err != nil { + t.Fatal(err) + } + + mn.Put("1", "foo the foo") + mn.Put("2", "go to bar") + + _, err = pg.Render("toobig", vals, 0) + if err == nil { + t.Fatalf("expected size exceeded") + } +} + +func TestSizePages(t *testing.T) { + st := state.NewState(0) + ca := cache.NewCache() + mn := NewMenu().WithOutputSize(32) + mrs := resource.NewMenuResource().WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate) + rs := TestSizeResource{ + mrs, + } + szr := NewSizer(128) + pg := NewPage(ca, rs).WithSizer(szr).WithMenu(mn) + ca.Push() + st.Down("test") + ca.Add("foo", "inky", 4) + ca.Add("bar", "pinky", 10) + ca.Add("baz", "blinky", 20) + ca.Add("xyzzy", "inky pinky\nblinky clyde sue\ntinkywinky dipsy\nlala poo\none two three four five six seven\neight nine ten\neleven twelve", 0) + pg.Map("foo") + pg.Map("bar") + pg.Map("baz") + pg.Map("xyzzy") + + vals, err := ca.Get() + if err != nil { + t.Fatal(err) + } + + mn.Put("1", "foo the foo") + mn.Put("2", "go to bar") + + r, err := pg.Render("pages", vals, 0) + if err != nil { + t.Fatal(err) + } + + expect := `one inky two pinky three blinky +inky pinky +blinky clyde sue +tinkywinky dipsy +lala poo +1:foo the foo +2:go to bar` + + + if r != expect { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r) + } + r, err = pg.Render("pages", vals, 1) + if err != nil { + t.Fatal(err) + } + + expect = `one inky two pinky three blinky +one two three four five six seven +eight nine ten +eleven twelve +1:foo the foo +2:go to bar` + if r != expect { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r) + } + +} diff --git a/go/resource/fs.go b/go/resource/fs.go index 4031604..5e88ca3 100644 --- a/go/resource/fs.go +++ b/go/resource/fs.go @@ -23,7 +23,7 @@ func NewFsResource(path string) (FsResource) { } } -func(fs FsResource) GetTemplate(sym string, sizer *Sizer) (string, error) { +func(fs FsResource) GetTemplate(sym string) (string, error) { fp := path.Join(fs.Path, sym) r, err := ioutil.ReadFile(fp) s := string(r) diff --git a/go/resource/render.go b/go/resource/render.go index 75e254f..9ff9923 100644 --- a/go/resource/render.go +++ b/go/resource/render.go @@ -1,40 +1,40 @@ package resource -import ( - "bytes" - "fmt" - "log" - "text/template" -) +//import ( +// "bytes" +// "fmt" +// "log" +// "text/template" +//) -// DefaultRenderTemplate is an adapter to implement the builtin golang text template renderer as resource.RenderTemplate. -func DefaultRenderTemplate(r Resource, sym string, values map[string]string, idx uint16, sizer *Sizer) (string, error) { - v, err := r.GetTemplate(sym, nil) - if err != nil { - return "", err - } - - if sizer != nil { - values, err = sizer.GetAt(values, idx) - } else if idx > 0 { - return "", fmt.Errorf("sizer needed for indexed render") - } - log.Printf("render for index: %v", idx) - - if err != nil { - return "", err - } - - tp, err := template.New("tester").Option("missingkey=error").Parse(v) - if err != nil { - return "", err - } - - b := bytes.NewBuffer([]byte{}) - err = tp.Execute(b, values) - if err != nil { - return "", err - } - return b.String(), err -} +//// DefaultRenderTemplate is an adapter to implement the builtin golang text template renderer as resource.RenderTemplate. +//func DefaultRenderTemplate(r Resource, sym string, values map[string]string, idx uint16, sizer *Sizer) (string, error) { +// v, err := r.GetTemplate(sym, nil) +// if err != nil { +// return "", err +// } +// +// if sizer != nil { +// values, err = sizer.GetAt(values, idx) +// } else if idx > 0 { +// return "", fmt.Errorf("sizer needed for indexed render") +// } +// log.Printf("render for index: %v", idx) +// +// if err != nil { +// return "", err +// } +// +// tp, err := template.New("tester").Option("missingkey=error").Parse(v) +// if err != nil { +// return "", err +// } +// +// b := bytes.NewBuffer([]byte{}) +// err = tp.Execute(b, values) +// if err != nil { +// return "", err +// } +// return b.String(), err +//} diff --git a/go/resource/resource.go b/go/resource/resource.go index 337d4f1..a739afa 100644 --- a/go/resource/resource.go +++ b/go/resource/resource.go @@ -2,23 +2,18 @@ package resource import ( "context" - "fmt" - "log" - "strings" ) // EntryFunc is a function signature for retrieving value for a key type EntryFunc func(ctx context.Context) (string, error) type CodeFunc func(sym string) ([]byte, error) -type TemplateFunc func(sym string, sizer *Sizer) (string, error) +type TemplateFunc func(sym string) (string, error) type FuncForFunc func(sym string) (EntryFunc, error) // Resource implementation are responsible for retrieving values and templates for symbols, and can render templates from value dictionaries. type Resource interface { - GetTemplate(sym string, sizer *Sizer) (string, error) // Get the template for a given symbol. + GetTemplate(sym string) (string, error) // Get the template for a given symbol. GetCode(sym string) ([]byte, error) // Get the bytecode for the given symbol. - RenderTemplate(sym string, values map[string]string, idx uint16, sizer *Sizer) (string, error) // Render the given data map using the template of the symbol. - Render(sym string, values map[string]string, idx uint16, sizer *Sizer) (string, error) // Render full output. FuncFor(sym string) (EntryFunc, error) // Resolve symbol content point for. } @@ -52,87 +47,6 @@ func(m *MenuResource) WithTemplateGetter(templateGetter TemplateFunc) *MenuResou return m } -// render menu and all syms except sink, split sink into display chunks -func(m *MenuResource) prepare(sym string, values map[string]string, idx uint16, sizer *Sizer) (map[string]string, error) { - var sink string - var sinkValues []string - noSinkValues := make(map[string]string) - for k, v := range values { - sz, err := sizer.Size(k) - if err != nil { - return nil, err - } - if sz == 0 { - sink = k - sinkValues = strings.Split(v, "\n") - v = "" - log.Printf("found sink %s with field count %v", k, len(sinkValues)) - } - noSinkValues[k] = v - } - - if sink == "" { - log.Printf("no sink found for sym %s", sym) - return values, nil - } - - s, err := m.render(sym, noSinkValues, 0, nil) - if err != nil { - return nil, err - } - - remaining, ok := sizer.Check(s) - if !ok { - return nil, fmt.Errorf("capacity exceeded") - } - - log.Printf("%v bytes available for sink split", remaining) - - l := 0 - tb := strings.Builder{} - rb := strings.Builder{} - - sizer.AddCursor(0) - for i, v := range sinkValues { - log.Printf("processing sinkvalue %v: %s", i, v) - l += len(v) - if uint32(l) > remaining { - if tb.Len() == 0 { - return nil, fmt.Errorf("capacity insufficient for sink field %v", i) - } - rb.WriteString(tb.String()) - rb.WriteRune('\n') - c := uint32(rb.Len()) - sizer.AddCursor(c) - tb.Reset() - l = 0 - } - if tb.Len() > 0 { - tb.WriteByte(byte(0x00)) - } - tb.WriteString(v) - } - - if tb.Len() > 0 { - rb.WriteString(tb.String()) - } - - r := rb.String() - r = strings.TrimRight(r, "\n") - - noSinkValues[sink] = r - - for i, v := range strings.Split(r, "\n") { - log.Printf("nosinkvalue %v: %s", i, v) - } - - return noSinkValues, nil -} - -func(m *MenuResource) RenderTemplate(sym string, values map[string]string, idx uint16, sizer *Sizer) (string, error) { - return DefaultRenderTemplate(m, sym, values, idx, sizer) -} - func(m *MenuResource) FuncFor(sym string) (EntryFunc, error) { return m.funcFunc(sym) } @@ -141,47 +55,7 @@ func(m *MenuResource) GetCode(sym string) ([]byte, error) { return m.codeFunc(sym) } -func(m *MenuResource) GetTemplate(sym string, sizer *Sizer) (string, error) { - return m.templateFunc(sym, sizer) +func(m *MenuResource) GetTemplate(sym string) (string, error) { + return m.templateFunc(sym) } -func(m *MenuResource) render(sym string, values map[string]string, idx uint16, sizer *Sizer) (string, error) { - var ok bool - r := "" - s, err := m.RenderTemplate(sym, values, idx, sizer) - if err != nil { - return "", err - } - log.Printf("rendered %v bytes for template", len(s)) - r += s - if sizer != nil { - _, ok = sizer.Check(r) - if !ok { - return "", fmt.Errorf("limit exceeded: %v", sizer) - } - } - s, err = m.RenderMenu(idx) - if err != nil { - return "", err - } - log.Printf("rendered %v bytes for menu", len(s)) - r += s - if sizer != nil { - _, ok = sizer.Check(r) - if !ok { - return "", fmt.Errorf("limit exceeded: %v", sizer) - } - } - return r, nil -} - -func(m *MenuResource) Render(sym string, values map[string]string, idx uint16, sizer *Sizer) (string, error) { - var err error - - values, err = m.prepare(sym, values, idx, sizer) - if err != nil { - return "", err - } - - return m.render(sym, values, idx, sizer) -} diff --git a/go/resource/resource_test.go b/go/resource/resource_test.go index 71e9f53..0a2ed92 100644 --- a/go/resource/resource_test.go +++ b/go/resource/resource_test.go @@ -10,7 +10,7 @@ type TestSizeResource struct { *MenuResource } -func getTemplate(sym string, sizer *Sizer) (string, error) { +func getTemplate(sym string) (string, error) { var tpl string switch sym { case "small": diff --git a/go/resource/size_test.go b/go/resource/size_test.go deleted file mode 100644 index 5edd5ef..0000000 --- a/go/resource/size_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package resource - -import ( - "testing" - - "git.defalsify.org/festive/state" -) - -func TestSizeLimit(t *testing.T) { - st := state.NewState(0).WithOutputSize(128) - mrs := NewMenuResource().WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate) - rs := TestSizeResource{ - mrs, - } - st.Down("test") - st.Add("foo", "inky", 4) - st.Add("bar", "pinky", 10) - st.Add("baz", "blinky", 0) - st.Map("foo") - st.Map("bar") - st.Map("baz") - st.SetMenuSize(32) - szr, err := SizerFromState(&st) - if err != nil { - t.Fatal(err) - } - - rs.PutMenu("1", "foo the foo") - rs.PutMenu("2", "go to bar") - - tpl, err := rs.GetTemplate("small", &szr) - if err != nil { - t.Fatal(err) - } - - vals, err := st.Get() - if err != nil { - t.Fatal(err) - } - _ = tpl - - _, err = rs.Render("small", vals, 0, &szr) - if err != nil { - t.Fatal(err) - } - - rs.PutMenu("1", "foo the foo") - rs.PutMenu("2", "go to bar") - - _, err = rs.Render("toobig", vals, 0, &szr) - if err == nil { - t.Fatalf("expected size exceeded") - } -} - -func TestSizePages(t *testing.T) { - st := state.NewState(0).WithOutputSize(128) - mrs := NewMenuResource().WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate) - rs := TestSizeResource{ - mrs, - } - st.Down("test") - st.Add("foo", "inky", 4) - st.Add("bar", "pinky", 10) - st.Add("baz", "blinky", 20) - st.Add("xyzzy", "inky pinky\nblinky clyde sue\ntinkywinky dipsy\nlala poo\none two three four five six seven\neight nine ten\neleven twelve", 0) - st.Map("foo") - st.Map("bar") - st.Map("baz") - st.Map("xyzzy") - st.SetMenuSize(32) - szr, err := SizerFromState(&st) - if err != nil { - t.Fatal(err) - } - - vals, err := st.Get() - if err != nil { - t.Fatal(err) - } - - rs.PutMenu("1", "foo the foo") - rs.PutMenu("2", "go to bar") - - r, err := rs.Render("pages", vals, 0, &szr) - if err != nil { - t.Fatal(err) - } - - expect := `one inky two pinky three blinky -inky pinky -blinky clyde sue -tinkywinky dipsy -lala poo` - - - if r != expect { - t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r) - } - - rs.PutMenu("1", "foo the foo") - rs.PutMenu("2", "go to bar") - - szr, err = SizerFromState(&st) - if err != nil { - t.Fatal(err) - } - r, err = rs.Render("pages", vals, 1, &szr) - if err != nil { - t.Fatal(err) - } - - expect = `one inky two pinky three blinky -one two three four five six seven -eight nine ten -eleven twelve` - if r != expect { - t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r) - } - -} diff --git a/go/resource/state.go b/go/resource/state.go index c535ff3..4c7b4c8 100644 --- a/go/resource/state.go +++ b/go/resource/state.go @@ -27,18 +27,3 @@ func(sr *StateResource) WithState(st *state.State) *StateResource { sr.st = st return sr } - -func(sr *StateResource) SetMenuBrowse(selector string, title string, back bool) error { - var err error - next, prev := sr.st.Sides() - - if back { - if prev { - err = sr.Resource.SetMenuBrowse(selector, title, true) - } - } else if next { - err = sr.Resource.SetMenuBrowse(selector, title, false) - - } - return err -} diff --git a/go/resource/state_test.go b/go/resource/state_test.go index c4d9a08..b743958 100644 --- a/go/resource/state_test.go +++ b/go/resource/state_test.go @@ -12,90 +12,3 @@ func TestStateResourceInit(t *testing.T) { _ = ToStateResource(rs).WithState(&st) _ = NewStateResource(&st) } - -func TestStateBrowseNoSink(t *testing.T) { - st := state.NewState(0) - st.Down("root") - - rs := NewStateResource(&st) - rs.PutMenu("1", "foo") - rs.PutMenu("2", "bar") - err := rs.SetMenuBrowse("11", "next", false) - if err != nil { - t.Fatal(err) - } - err = rs.SetMenuBrowse("22", "prev", true) - if err != nil { - t.Fatal(err) - } - s, err := rs.RenderMenu(0) - if err != nil { - t.Fatal(err) - } - - expect := `1:foo -2:bar` - if s != expect { - t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, s) - } -} - - -func TestStateBrowseSink(t *testing.T) { - st := state.NewState(0) - st.Down("root") - - rs := NewStateResource(&st) - rs.PutMenu("1", "foo") - rs.PutMenu("2", "bar") - err := rs.SetMenuBrowse("11", "next", false) - if err != nil { - t.Fatal(err) - } - err = rs.SetMenuBrowse("22", "prev", true) - if err != nil { - t.Fatal(err) - } - s, err := rs.RenderMenu(0) - if err != nil { - t.Fatal(err) - } - - expect := `1:foo -2:bar -11:next` - if s != expect { - t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, s) - } - - idx, err := st.Next() - if err != nil { - t.Fatal(err) - } - if idx != 1 { - t.Fatalf("expected idx 1, got %v", idx) - } - rs = NewStateResource(&st) - rs.PutMenu("1", "foo") - rs.PutMenu("2", "bar") - err = rs.SetMenuBrowse("11", "next", false) - if err != nil { - t.Fatal(err) - } - err = rs.SetMenuBrowse("22", "prev", true) - if err != nil { - t.Fatal(err) - } - s, err = rs.RenderMenu(idx) - if err != nil { - t.Fatal(err) - } - - expect = `1:foo -2:bar -11:next -22:prev` - if s != expect { - t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, s) - } -}