WIP Add size checker to resource render
This commit is contained in:
parent
397985f1ae
commit
4a91439320
@ -16,7 +16,7 @@ var (
|
||||
inputRegexStr = "^[a-zA-Z0-9].*$"
|
||||
inputRegex = regexp.MustCompile(inputRegexStr)
|
||||
)
|
||||
//
|
||||
|
||||
//type Config struct {
|
||||
// FlagCount uint32
|
||||
// CacheSize uint32
|
||||
|
@ -23,17 +23,13 @@ func NewFsResource(path string) (FsResource) {
|
||||
}
|
||||
}
|
||||
|
||||
func(fs FsResource) GetTemplate(sym string) (string, error) {
|
||||
func(fs FsResource) GetTemplate(sym string, sizer *Sizer) (string, error) {
|
||||
fp := path.Join(fs.Path, sym)
|
||||
r, err := ioutil.ReadFile(fp)
|
||||
s := string(r)
|
||||
return strings.TrimSpace(s), err
|
||||
}
|
||||
|
||||
func(fs FsResource) RenderTemplate(sym string, values map[string]string) (string, error) {
|
||||
return DefaultRenderTemplate(&fs, sym, values)
|
||||
}
|
||||
|
||||
func(fs FsResource) GetCode(sym string) ([]byte, error) {
|
||||
fb := sym + ".bin"
|
||||
fp := path.Join(fs.Path, fb)
|
||||
|
@ -5,9 +5,10 @@ import (
|
||||
"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) (string, error) {
|
||||
v, err := r.GetTemplate(sym)
|
||||
v, err := r.GetTemplate(sym, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -23,4 +24,3 @@ func DefaultRenderTemplate(r Resource, sym string, values map[string]string) (st
|
||||
}
|
||||
return b.String(), err
|
||||
}
|
||||
|
||||
|
@ -8,16 +8,20 @@ import (
|
||||
|
||||
// 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 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) (string, error) // Get the template for a given symbol.
|
||||
GetTemplate(sym string, sizer *Sizer) (string, error) // Get the template for a given symbol.
|
||||
GetCode(sym string) ([]byte, error) // Get the bytecode for the given symbol.
|
||||
PutMenu(string, string) error // Add a menu item.
|
||||
ShiftMenu() (string, string, error) // Remove and return the first menu item in list.
|
||||
SetMenuBrowse(string, string, bool) error // Set menu browser display details.
|
||||
RenderTemplate(sym string, values map[string]string) (string, error) // Render the given data map using the template of the symbol.
|
||||
RenderMenu() (string, error) // Render the current state of menu
|
||||
Render(sym string, values map[string]string, sizer *Sizer) (string, error) // Render full output.
|
||||
FuncFor(sym string) (EntryFunc, error) // Resolve symbol content point for.
|
||||
}
|
||||
|
||||
@ -25,6 +29,28 @@ type MenuResource struct {
|
||||
menu [][2]string
|
||||
next [2]string
|
||||
prev [2]string
|
||||
codeFunc CodeFunc
|
||||
templateFunc TemplateFunc
|
||||
funcFunc FuncForFunc
|
||||
}
|
||||
|
||||
func NewMenuResource() *MenuResource {
|
||||
return &MenuResource{}
|
||||
}
|
||||
|
||||
func(m *MenuResource) WithCodeGetter(codeGetter CodeFunc) *MenuResource {
|
||||
m.codeFunc = codeGetter
|
||||
return m
|
||||
}
|
||||
|
||||
func(m *MenuResource) WithEntryFuncGetter(entryFuncGetter FuncForFunc) *MenuResource {
|
||||
m.funcFunc = entryFuncGetter
|
||||
return m
|
||||
}
|
||||
|
||||
func(m *MenuResource) WithTemplateGetter(templateGetter TemplateFunc) *MenuResource {
|
||||
m.templateFunc = templateGetter
|
||||
return m
|
||||
}
|
||||
|
||||
// SetMenuBrowse defines the how pagination menu options should be displayed.
|
||||
@ -79,3 +105,44 @@ func(m *MenuResource) RenderMenu() (string, error) {
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func(m *MenuResource) RenderTemplate(sym string, values map[string]string) (string, error) {
|
||||
return DefaultRenderTemplate(m, sym, values)
|
||||
}
|
||||
|
||||
func(m *MenuResource) FuncFor(sym string) (EntryFunc, error) {
|
||||
return m.funcFunc(sym)
|
||||
}
|
||||
|
||||
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) Render(sym string, values map[string]string, sizer *Sizer) (string, error) {
|
||||
r := ""
|
||||
s, err := m.RenderTemplate(sym, values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Printf("rendered %v bytes for template", len(s))
|
||||
r += s
|
||||
l, ok := sizer.Check(r)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("rendered size %v exceeds limits: %v", l, sizer)
|
||||
}
|
||||
s, err = m.RenderMenu()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Printf("rendered %v bytes for menu", len(s))
|
||||
r += s
|
||||
l, ok = sizer.Check(r)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("rendered size %v exceeds limits: %v", l, sizer)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
9
go/resource/resource_test.go
Normal file
9
go/resource/resource_test.go
Normal file
@ -0,0 +1,9 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenderLimit(t *testing.T) {
|
||||
|
||||
}
|
53
go/resource/size.go
Normal file
53
go/resource/size.go
Normal file
@ -0,0 +1,53 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.defalsify.org/festive/state"
|
||||
)
|
||||
|
||||
type Sizer struct {
|
||||
outputSize uint32
|
||||
menuSize uint16
|
||||
memberSizes map[string]uint16
|
||||
totalMemberSize uint32
|
||||
sink string
|
||||
}
|
||||
|
||||
func SizerFromState(st *state.State) (Sizer, error){
|
||||
sz := Sizer{
|
||||
outputSize: st.GetOutputSize(),
|
||||
menuSize: st.GetMenuSize(),
|
||||
memberSizes: make(map[string]uint16),
|
||||
}
|
||||
sizes, err := st.Sizes()
|
||||
if err != nil {
|
||||
return sz, err
|
||||
}
|
||||
for k, v := range sizes {
|
||||
if v == 0 {
|
||||
sz.sink = k
|
||||
}
|
||||
sz.memberSizes[k] = v
|
||||
sz.totalMemberSize += uint32(v)
|
||||
}
|
||||
return sz, nil
|
||||
}
|
||||
|
||||
func(szr *Sizer) Check(s string) (uint32, bool) {
|
||||
l := uint32(len(s))
|
||||
if szr.outputSize > 0 && l > szr.outputSize {
|
||||
log.Printf("sizer check fails with length %v: %s", l, szr)
|
||||
return l, false
|
||||
}
|
||||
return l, true
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
96
go/resource/size_test.go
Normal file
96
go/resource/size_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/festive/state"
|
||||
)
|
||||
|
||||
type TestSizeResource struct {
|
||||
*MenuResource
|
||||
}
|
||||
|
||||
func getTemplate(sym string, sizer *Sizer) (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."
|
||||
|
||||
}
|
||||
return tpl, nil
|
||||
}
|
||||
|
||||
func funcFor(sym string) (EntryFunc, error) {
|
||||
switch sym {
|
||||
case "foo":
|
||||
return getFoo, nil
|
||||
case "bar":
|
||||
return getBar, nil
|
||||
case "baz":
|
||||
return getBaz, 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 TestSizeLimit(t *testing.T) {
|
||||
st := state.NewState(0).WithOutputSize(128)
|
||||
mrs := NewMenuResource().WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate)
|
||||
rs := TestSizeResource{
|
||||
mrs,
|
||||
}
|
||||
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, &szr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rs.PutMenu("1", "foo the foo")
|
||||
rs.PutMenu("2", "go to bar")
|
||||
|
||||
_, err = rs.Render("toobig", vals, &szr)
|
||||
if err == nil {
|
||||
t.Fatalf("expected size exceeded")
|
||||
}
|
||||
}
|
@ -28,13 +28,15 @@ type State struct {
|
||||
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
|
||||
input []byte // Last input
|
||||
code []byte // Pending bytecode to execute
|
||||
execPath []string // Command symbols stack
|
||||
arg *string // Optional argument. Nil if not set.
|
||||
sizes map[string]uint16 // Size limits for all loaded symbols.
|
||||
sink *string // Sink symbol set for level
|
||||
bitSize uint32 // size of (32-bit capacity) bit flag byte array
|
||||
sink *string
|
||||
//sizeIdx uint16
|
||||
}
|
||||
|
||||
@ -195,6 +197,12 @@ func(st State) WithCacheSize(cacheSize uint32) State {
|
||||
return st
|
||||
}
|
||||
|
||||
// WithCacheSize applies a cumulative cache size limitation for all cached items.
|
||||
func(st State) WithOutputSize(outputSize uint32) State {
|
||||
st.outputSize = outputSize
|
||||
return st
|
||||
}
|
||||
|
||||
// Where returns the current active rendering symbol.
|
||||
func(st State) Where() string {
|
||||
if len(st.execPath) == 0 {
|
||||
@ -268,7 +276,7 @@ func(st *State) Add(key string, value string, sizeLimit uint16) error {
|
||||
if sz == 0 {
|
||||
return fmt.Errorf("Cache capacity exceeded %v of %v", st.CacheUseSize + sz, st.CacheSize)
|
||||
}
|
||||
log.Printf("add key %s value size %v", key, sz)
|
||||
log.Printf("add key %s value size %v limit %v", key, sz, sizeLimit)
|
||||
st.Cache[len(st.Cache)-1][key] = value
|
||||
st.CacheUseSize += sz
|
||||
st.sizes[key] = sizeLimit
|
||||
@ -346,6 +354,42 @@ func(st *State) Get() (map[string]string, error) {
|
||||
return st.Cache[len(st.Cache)-1], nil
|
||||
}
|
||||
|
||||
func(st *State) Sizes() (map[string]uint16, error) {
|
||||
if len(st.Cache) == 0 {
|
||||
return nil, fmt.Errorf("get at top frame")
|
||||
}
|
||||
sizes := make(map[string]uint16)
|
||||
var haveSink bool
|
||||
for k, _ := range st.CacheMap {
|
||||
l, ok := st.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
|
||||
}
|
||||
|
||||
func(st *State) SetMenuSize(size uint16) error {
|
||||
st.menuSize = size
|
||||
log.Printf("menu size changed to %v", st.menuSize)
|
||||
return nil
|
||||
}
|
||||
|
||||
func(st *State) GetMenuSize() uint16 {
|
||||
return st.menuSize
|
||||
}
|
||||
|
||||
func(st *State) GetOutputSize() uint32 {
|
||||
return st.outputSize
|
||||
}
|
||||
|
||||
// Val returns value for key
|
||||
//
|
||||
// Fails if key is not mapped.
|
||||
@ -372,7 +416,7 @@ func(st *State) Check(key string) bool {
|
||||
return st.frameOf(key) == -1
|
||||
}
|
||||
|
||||
// Size returns size used by values, and remaining size available
|
||||
// Size returns size used by values and menu, and remaining size available
|
||||
func(st *State) Size() (uint32, uint32) {
|
||||
var l int
|
||||
var c uint16
|
||||
@ -381,6 +425,7 @@ func(st *State) Size() (uint32, uint32) {
|
||||
c += st.sizes[k]
|
||||
}
|
||||
r := uint32(l)
|
||||
r += uint32(st.menuSize)
|
||||
return r, uint32(c)-r
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ type TestStatefulResolver struct {
|
||||
state *state.State
|
||||
}
|
||||
|
||||
func (r *TestResource) GetTemplate(sym string) (string, error) {
|
||||
func (r *TestResource) GetTemplate(sym string, sizer *resource.Sizer) (string, error) {
|
||||
switch sym {
|
||||
case "foo":
|
||||
return "inky pinky blinky clyde", nil
|
||||
|
Loading…
Reference in New Issue
Block a user