WIP Add size checker to resource render

This commit is contained in:
lash 2023-04-06 15:21:26 +01:00
parent 397985f1ae
commit 4a91439320
Signed by untrusted user who does not match committer: lash
GPG Key ID: 21D2E7BB88C2A746
9 changed files with 279 additions and 13 deletions

View File

@ -16,7 +16,7 @@ var (
inputRegexStr = "^[a-zA-Z0-9].*$" inputRegexStr = "^[a-zA-Z0-9].*$"
inputRegex = regexp.MustCompile(inputRegexStr) inputRegex = regexp.MustCompile(inputRegexStr)
) )
//
//type Config struct { //type Config struct {
// FlagCount uint32 // FlagCount uint32
// CacheSize uint32 // CacheSize uint32

View File

@ -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) fp := path.Join(fs.Path, sym)
r, err := ioutil.ReadFile(fp) r, err := ioutil.ReadFile(fp)
s := string(r) s := string(r)
return strings.TrimSpace(s), err 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) { func(fs FsResource) GetCode(sym string) ([]byte, error) {
fb := sym + ".bin" fb := sym + ".bin"
fp := path.Join(fs.Path, fb) fp := path.Join(fs.Path, fb)

View File

@ -5,9 +5,10 @@ import (
"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) (string, error) { 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 { if err != nil {
return "", err return "", err
} }
@ -23,4 +24,3 @@ func DefaultRenderTemplate(r Resource, sym string, values map[string]string) (st
} }
return b.String(), err return b.String(), err
} }

View File

@ -8,16 +8,20 @@ import (
// 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 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. // 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) (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. GetCode(sym string) ([]byte, error) // Get the bytecode for the given symbol.
PutMenu(string, string) error // Add a menu item. PutMenu(string, string) error // Add a menu item.
ShiftMenu() (string, string, error) // Remove and return the first menu item in list. ShiftMenu() (string, string, error) // Remove and return the first menu item in list.
SetMenuBrowse(string, string, bool) error // Set menu browser display details. 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. 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 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. FuncFor(sym string) (EntryFunc, error) // Resolve symbol content point for.
} }
@ -25,6 +29,28 @@ type MenuResource struct {
menu [][2]string menu [][2]string
next [2]string next [2]string
prev [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. // SetMenuBrowse defines the how pagination menu options should be displayed.
@ -79,3 +105,44 @@ func(m *MenuResource) RenderMenu() (string, error) {
} }
return r, nil 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
}

View File

@ -0,0 +1,9 @@
package resource
import (
"testing"
)
func TestRenderLimit(t *testing.T) {
}

53
go/resource/size.go Normal file
View 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
View 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")
}
}

View File

@ -28,13 +28,15 @@ type State struct {
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
input []byte // Last input input []byte // Last input
code []byte // Pending bytecode to execute code []byte // Pending bytecode to execute
execPath []string // Command symbols stack execPath []string // Command symbols stack
arg *string // Optional argument. Nil if not set. arg *string // Optional argument. Nil if not set.
sizes map[string]uint16 // Size limits for all loaded symbols. 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 bitSize uint32 // size of (32-bit capacity) bit flag byte array
sink *string
//sizeIdx uint16 //sizeIdx uint16
} }
@ -195,6 +197,12 @@ func(st State) WithCacheSize(cacheSize uint32) State {
return st 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. // Where returns the current active rendering symbol.
func(st State) Where() string { func(st State) Where() string {
if len(st.execPath) == 0 { if len(st.execPath) == 0 {
@ -268,7 +276,7 @@ func(st *State) Add(key string, value string, sizeLimit uint16) error {
if sz == 0 { if sz == 0 {
return fmt.Errorf("Cache capacity exceeded %v of %v", st.CacheUseSize + sz, st.CacheSize) 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.Cache[len(st.Cache)-1][key] = value
st.CacheUseSize += sz st.CacheUseSize += sz
st.sizes[key] = sizeLimit st.sizes[key] = sizeLimit
@ -346,6 +354,42 @@ func(st *State) Get() (map[string]string, error) {
return st.Cache[len(st.Cache)-1], nil 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 // Val returns value for key
// //
// Fails if key is not mapped. // Fails if key is not mapped.
@ -372,7 +416,7 @@ func(st *State) Check(key string) bool {
return st.frameOf(key) == -1 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) { func(st *State) Size() (uint32, uint32) {
var l int var l int
var c uint16 var c uint16
@ -381,6 +425,7 @@ func(st *State) Size() (uint32, uint32) {
c += st.sizes[k] c += st.sizes[k]
} }
r := uint32(l) r := uint32(l)
r += uint32(st.menuSize)
return r, uint32(c)-r return r, uint32(c)-r
} }

View File

@ -34,7 +34,7 @@ type TestStatefulResolver struct {
state *state.State state *state.State
} }
func (r *TestResource) GetTemplate(sym string) (string, error) { func (r *TestResource) GetTemplate(sym string, sizer *resource.Sizer) (string, error) {
switch sym { switch sym {
case "foo": case "foo":
return "inky pinky blinky clyde", nil return "inky pinky blinky clyde", nil