WIP Factor page, sizer, menu to render package, cachemap to page
This commit is contained in:
parent
6a1611a0c8
commit
7e01f2725d
131
go/cache/cache.go
vendored
131
go/cache/cache.go
vendored
@ -9,11 +9,10 @@ type Cache struct {
|
|||||||
CacheSize uint32 // Total allowed cumulative size of values (not code) in cache
|
CacheSize uint32 // Total allowed cumulative size of values (not code) in cache
|
||||||
CacheUseSize uint32 // Currently used bytes by all 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
|
Cache []map[string]string // All loaded cache items
|
||||||
CacheMap map[string]string // Mapped
|
//CacheMap map[string]string // Mapped
|
||||||
menuSize uint16 // Max size of menu
|
//outputSize uint32 // Max size of output
|
||||||
outputSize uint32 // Max size of output
|
|
||||||
sizes map[string]uint16 // Size limits for all loaded symbols.
|
sizes map[string]uint16 // Size limits for all loaded symbols.
|
||||||
sink *string
|
//sink *string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCache creates a new ready-to-use cache object
|
// NewCache creates a new ready-to-use cache object
|
||||||
@ -22,7 +21,7 @@ func NewCache() *Cache {
|
|||||||
Cache: []map[string]string{make(map[string]string)},
|
Cache: []map[string]string{make(map[string]string)},
|
||||||
sizes: make(map[string]uint16),
|
sizes: make(map[string]uint16),
|
||||||
}
|
}
|
||||||
ca.resetCurrent()
|
//ca.resetCurrent()
|
||||||
return ca
|
return ca
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,12 +31,6 @@ func(ca *Cache) WithCacheSize(cacheSize uint32) *Cache {
|
|||||||
return ca
|
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.
|
// Add adds a cache value under a cache symbol key.
|
||||||
//
|
//
|
||||||
// Also stores the size limitation of for key for later updates.
|
// 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
|
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.
|
// Update sets a new value for an existing key.
|
||||||
//
|
//
|
||||||
// Uses the size limitation from when the key was added.
|
// 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]
|
r := ca.Cache[checkFrame][key]
|
||||||
l := uint32(len(r))
|
l := uint32(len(r))
|
||||||
ca.Cache[checkFrame][key] = ""
|
ca.Cache[checkFrame][key] = ""
|
||||||
if ca.CacheMap[key] != "" {
|
//if ca.CacheMap[key] != "" {
|
||||||
ca.CacheMap[key] = value
|
// ca.CacheMap[key] = value
|
||||||
}
|
//}
|
||||||
ca.CacheUseSize -= l
|
ca.CacheUseSize -= l
|
||||||
sz := ca.checkCapacity(value)
|
sz := ca.checkCapacity(value)
|
||||||
if sz == 0 {
|
if sz == 0 {
|
||||||
@ -117,58 +119,6 @@ func(ca *Cache) Get() (map[string]string, error) {
|
|||||||
return ca.Cache[len(ca.Cache)-1], nil
|
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.
|
// Reset flushes all state contents below the top level.
|
||||||
func(ca *Cache) Reset() {
|
func(ca *Cache) Reset() {
|
||||||
if len(ca.Cache) == 0 {
|
if len(ca.Cache) == 0 {
|
||||||
@ -179,36 +129,11 @@ func(ca *Cache) Reset() {
|
|||||||
return
|
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.
|
// Push adds a new level to the cache.
|
||||||
func (ca *Cache) Push() error {
|
func (ca *Cache) Push() error {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
ca.Cache = append(ca.Cache, m)
|
ca.Cache = append(ca.Cache, m)
|
||||||
ca.resetCurrent()
|
//ca.resetCurrent()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +153,7 @@ func (ca *Cache) Pop() error {
|
|||||||
log.Printf("free frame %v key %v value size %v", l, k, sz)
|
log.Printf("free frame %v key %v value size %v", l, k, sz)
|
||||||
}
|
}
|
||||||
ca.Cache = ca.Cache[:l]
|
ca.Cache = ca.Cache[:l]
|
||||||
ca.resetCurrent()
|
//ca.resetCurrent()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,10 +163,10 @@ func(ca *Cache) Check(key string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// flush relveant properties for level change
|
// flush relveant properties for level change
|
||||||
func(ca *Cache) resetCurrent() {
|
//func(ca *Cache) resetCurrent() {
|
||||||
ca.sink = nil
|
// ca.sink = nil
|
||||||
ca.CacheMap = make(map[string]string)
|
// ca.CacheMap = make(map[string]string)
|
||||||
}
|
//}
|
||||||
|
|
||||||
// bytes that will be added to cache use size for string
|
// bytes that will be added to cache use size for string
|
||||||
// returns 0 if capacity would be exceeded
|
// returns 0 if capacity would be exceeded
|
||||||
@ -255,3 +180,15 @@ func(ca *Cache) checkCapacity(v string) uint32 {
|
|||||||
}
|
}
|
||||||
return sz
|
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
|
||||||
|
}
|
||||||
|
82
go/cache/cache_test.go
vendored
82
go/cache/cache_test.go
vendored
@ -4,7 +4,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func TestNewCache(t *testing.T) {
|
func TestNewCache(t *testing.T) {
|
||||||
ca := NewCache()
|
ca := NewCache()
|
||||||
if ca.CacheSize != 0 {
|
if ca.CacheSize != 0 {
|
||||||
@ -34,7 +33,6 @@ func TestStateCacheUse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestStateDownUp(t *testing.T) {
|
func TestStateDownUp(t *testing.T) {
|
||||||
ca := NewCache()
|
ca := NewCache()
|
||||||
err := ca.Push()
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package menu
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BrowseConfig defines the availability and display parameters for page browsing.
|
// BrowseConfig defines the availability and display parameters for page browsing.
|
||||||
@ -33,6 +32,7 @@ type Menu struct {
|
|||||||
pageCount uint16
|
pageCount uint16
|
||||||
canNext bool
|
canNext bool
|
||||||
canPrevious bool
|
canPrevious bool
|
||||||
|
outputSize uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMenu creates a new Menu with an explicit page count.
|
// NewMenu creates a new Menu with an explicit page count.
|
||||||
@ -46,17 +46,21 @@ func(m *Menu) WithPageCount(pageCount uint16) *Menu {
|
|||||||
return m
|
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.
|
// WithBrowseConfig defines the criteria for page browsing.
|
||||||
func(m *Menu) WithBrowseConfig(cfg BrowseConfig) *Menu {
|
func(m *Menu) WithBrowseConfig(cfg BrowseConfig) *Menu {
|
||||||
m.browse = cfg
|
m.browse = cfg
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put adds a menu option to the menu rendering.
|
// Put adds a menu option to the menu rendering.
|
||||||
func(m *Menu) Put(selector string, title string) error {
|
func(m *Menu) Put(selector string, title string) error {
|
||||||
m.menu = append(m.menu, [2]string{selector, title})
|
m.menu = append(m.menu, [2]string{selector, title})
|
||||||
log.Printf("menu %v", m.menu)
|
|
||||||
return nil
|
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.
|
// After this has been executed, the state of the menu will be empty.
|
||||||
func(m *Menu) Render(idx uint16) (string, error) {
|
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)
|
err := m.applyPage(idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -81,28 +90,30 @@ func(m *Menu) Render(idx uint16) (string, error) {
|
|||||||
}
|
}
|
||||||
r += fmt.Sprintf("%s:%s", choice, title)
|
r += fmt.Sprintf("%s:%s", choice, title)
|
||||||
}
|
}
|
||||||
|
m.menu = menuCopy
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// add available browse options.
|
// add available browse options.
|
||||||
func(m *Menu) applyPage(idx uint16) error {
|
func(m *Menu) applyPage(idx uint16) error {
|
||||||
if m.pageCount == 0 {
|
if m.pageCount == 0 {
|
||||||
|
if idx > 0 {
|
||||||
|
return fmt.Errorf("index %v > 0 for non-paged menu", idx)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
} else if idx >= m.pageCount {
|
} else if idx >= m.pageCount {
|
||||||
return fmt.Errorf("index %v out of bounds (%v)", idx, m.pageCount)
|
return fmt.Errorf("index %v out of bounds (%v)", idx, m.pageCount)
|
||||||
}
|
}
|
||||||
if m.browse.NextAvailable {
|
|
||||||
m.canNext = true
|
m.reset()
|
||||||
}
|
|
||||||
if m.browse.PreviousAvailable {
|
|
||||||
m.canPrevious = true
|
|
||||||
}
|
|
||||||
if idx == m.pageCount - 1 {
|
if idx == m.pageCount - 1 {
|
||||||
m.canNext = false
|
m.canNext = false
|
||||||
}
|
}
|
||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
m.canPrevious = false
|
m.canPrevious = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.canNext {
|
if m.canNext {
|
||||||
err := m.Put(m.browse.NextSelector, m.browse.NextTitle)
|
err := m.Put(m.browse.NextSelector, m.browse.NextTitle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -129,4 +140,15 @@ func(m *Menu) shiftMenu() (string, string, error) {
|
|||||||
return r[0], r[1], nil
|
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
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package menu
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -23,6 +23,12 @@ func TestMenuInit(t *testing.T) {
|
|||||||
if r != expect {
|
if r != expect {
|
||||||
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r)
|
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) {
|
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)
|
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)
|
r, err = m.Render(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
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)
|
r, err = m.Render(2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
289
go/render/page.go
Normal file
289
go/render/page.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
|||||||
package resource
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.defalsify.org/festive/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Sizer struct {
|
type Sizer struct {
|
||||||
@ -18,28 +16,34 @@ type Sizer struct {
|
|||||||
sink string
|
sink string
|
||||||
}
|
}
|
||||||
|
|
||||||
func SizerFromState(st *state.State) (Sizer, error){
|
func NewSizer(outputSize uint32) *Sizer {
|
||||||
sz := Sizer{
|
return &Sizer{
|
||||||
outputSize: st.GetOutputSize(),
|
outputSize: outputSize,
|
||||||
menuSize: st.GetMenuSize(),
|
|
||||||
memberSizes: make(map[string]uint16),
|
memberSizes: make(map[string]uint16),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sizes, err := st.Sizes()
|
func(szr *Sizer) WithMenuSize(menuSize uint16) *Sizer {
|
||||||
if err != nil {
|
szr.menuSize = menuSize
|
||||||
return sz, err
|
return szr
|
||||||
}
|
}
|
||||||
for k, v := range sizes {
|
|
||||||
if v == 0 {
|
func(szr *Sizer) Set(key string, size uint16) error {
|
||||||
sz.sink = k
|
var ok bool
|
||||||
|
_, ok = szr.memberSizes[key]
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("already have key %s", key)
|
||||||
}
|
}
|
||||||
sz.memberSizes[k] = v
|
szr.memberSizes[key] = size
|
||||||
sz.totalMemberSize += uint32(v)
|
if size == 0 {
|
||||||
|
szr.sink = key
|
||||||
}
|
}
|
||||||
return sz, nil
|
szr.totalMemberSize += uint32(size)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func(szr *Sizer) Check(s string) (uint32, bool) {
|
func(szr *Sizer) Check(s string) (uint32, bool) {
|
||||||
|
log.Printf("sizercheck %s", s)
|
||||||
l := uint32(len(s))
|
l := uint32(len(s))
|
||||||
if szr.outputSize > 0 {
|
if szr.outputSize > 0 {
|
||||||
if l > szr.outputSize {
|
if l > szr.outputSize {
|
181
go/render/size_test.go
Normal file
181
go/render/size_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)
|
fp := path.Join(fs.Path, sym)
|
||||||
r, err := ioutil.ReadFile(fp)
|
r, err := ioutil.ReadFile(fp)
|
||||||
s := string(r)
|
s := string(r)
|
||||||
|
@ -1,40 +1,40 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
//import (
|
||||||
"bytes"
|
// "bytes"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"log"
|
// "log"
|
||||||
"text/template"
|
// "text/template"
|
||||||
)
|
//)
|
||||||
|
|
||||||
|
|
||||||
// DefaultRenderTemplate is an adapter to implement the builtin golang text template renderer as resource.RenderTemplate.
|
//// 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) {
|
//func DefaultRenderTemplate(r Resource, sym string, values map[string]string, idx uint16, sizer *Sizer) (string, error) {
|
||||||
v, err := r.GetTemplate(sym, nil)
|
// v, err := r.GetTemplate(sym, nil)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if sizer != nil {
|
// if sizer != nil {
|
||||||
values, err = sizer.GetAt(values, idx)
|
// values, err = sizer.GetAt(values, idx)
|
||||||
} else if idx > 0 {
|
// } else if idx > 0 {
|
||||||
return "", fmt.Errorf("sizer needed for indexed render")
|
// return "", fmt.Errorf("sizer needed for indexed render")
|
||||||
}
|
// }
|
||||||
log.Printf("render for index: %v", idx)
|
// log.Printf("render for index: %v", idx)
|
||||||
|
//
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
tp, err := template.New("tester").Option("missingkey=error").Parse(v)
|
// tp, err := template.New("tester").Option("missingkey=error").Parse(v)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
b := bytes.NewBuffer([]byte{})
|
// b := bytes.NewBuffer([]byte{})
|
||||||
err = tp.Execute(b, values)
|
// err = tp.Execute(b, values)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
return b.String(), err
|
// return b.String(), err
|
||||||
}
|
//}
|
||||||
|
@ -2,23 +2,18 @@ package resource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EntryFunc is a function signature for retrieving value for a key
|
// EntryFunc is a function signature for retrieving value for a key
|
||||||
type EntryFunc func(ctx context.Context) (string, error)
|
type EntryFunc func(ctx context.Context) (string, error)
|
||||||
type CodeFunc func(sym string) ([]byte, 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)
|
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.
|
// Resource implementation are responsible for retrieving values and templates for symbols, and can render templates from value dictionaries.
|
||||||
type Resource interface {
|
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.
|
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.
|
FuncFor(sym string) (EntryFunc, error) // Resolve symbol content point for.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,87 +47,6 @@ func(m *MenuResource) WithTemplateGetter(templateGetter TemplateFunc) *MenuResou
|
|||||||
return m
|
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) {
|
func(m *MenuResource) FuncFor(sym string) (EntryFunc, error) {
|
||||||
return m.funcFunc(sym)
|
return m.funcFunc(sym)
|
||||||
}
|
}
|
||||||
@ -141,47 +55,7 @@ func(m *MenuResource) GetCode(sym string) ([]byte, error) {
|
|||||||
return m.codeFunc(sym)
|
return m.codeFunc(sym)
|
||||||
}
|
}
|
||||||
|
|
||||||
func(m *MenuResource) GetTemplate(sym string, sizer *Sizer) (string, error) {
|
func(m *MenuResource) GetTemplate(sym string) (string, error) {
|
||||||
return m.templateFunc(sym, sizer)
|
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)
|
|
||||||
}
|
|
||||||
|
@ -10,7 +10,7 @@ type TestSizeResource struct {
|
|||||||
*MenuResource
|
*MenuResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTemplate(sym string, sizer *Sizer) (string, error) {
|
func getTemplate(sym string) (string, error) {
|
||||||
var tpl string
|
var tpl string
|
||||||
switch sym {
|
switch sym {
|
||||||
case "small":
|
case "small":
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -27,18 +27,3 @@ func(sr *StateResource) WithState(st *state.State) *StateResource {
|
|||||||
sr.st = st
|
sr.st = st
|
||||||
return sr
|
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
|
|
||||||
}
|
|
||||||
|
@ -12,90 +12,3 @@ func TestStateResourceInit(t *testing.T) {
|
|||||||
_ = ToStateResource(rs).WithState(&st)
|
_ = ToStateResource(rs).WithState(&st)
|
||||||
_ = NewStateResource(&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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user