diff --git a/go/engine/engine.go b/go/engine/engine.go index 18dc782..7c55520 100644 --- a/go/engine/engine.go +++ b/go/engine/engine.go @@ -119,12 +119,12 @@ func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) { // - the template for the given node point is note available for retrieval using the resource.Resource implementer. // - the supplied writer fails to process the writes. func(en *Engine) WriteResult(w io.Writer) error { - location := en.st.Where() + location, idx := en.st.Where() v, err := en.st.Get() if err != nil { return err } - r, err := en.rs.RenderTemplate(location, v) + r, err := en.rs.RenderTemplate(location, v, idx, nil) if err != nil { return err } diff --git a/go/engine/engine_test.go b/go/engine/engine_test.go index 1cad397..3c9b830 100644 --- a/go/engine/engine_test.go +++ b/go/engine/engine_test.go @@ -31,8 +31,8 @@ func NewFsWrapper(path string, st *state.State) FsWrapper { } } -func (r FsWrapper) RenderTemplate(sym string, values map[string]string) (string, error) { - return resource.DefaultRenderTemplate(r, sym, values) +func (r FsWrapper) RenderTemplate(sym string, values map[string]string, idx uint16, sizer *resource.Sizer) (string, error) { + return resource.DefaultRenderTemplate(r, sym, values, idx, sizer) } func(fs FsWrapper) one(ctx context.Context) (string, error) { @@ -101,7 +101,7 @@ func TestEngineInit(t *testing.T) { if err != nil { t.Fatal(err) } - r := st.Where() + r, _ := st.Where() if r != "foo" { t.Fatalf("expected where-string 'foo', got %s", r) } diff --git a/go/resource/render.go b/go/resource/render.go index debf701..75e254f 100644 --- a/go/resource/render.go +++ b/go/resource/render.go @@ -2,16 +2,30 @@ package resource 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) (string, error) { +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 diff --git a/go/resource/resource.go b/go/resource/resource.go index a99732a..c2a8a58 100644 --- a/go/resource/resource.go +++ b/go/resource/resource.go @@ -20,9 +20,9 @@ type Resource interface { 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. + RenderTemplate(sym string, values map[string]string, idx uint16, sizer *Sizer) (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. + 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. } @@ -30,6 +30,7 @@ type MenuResource struct { menu [][2]string next [2]string prev [2]string + sinkValues []string codeFunc CodeFunc templateFunc TemplateFunc funcFunc FuncForFunc @@ -108,8 +109,8 @@ func(m *MenuResource) RenderMenu() (string, error) { } // render menu and all syms except sink, split sink into display chunks -func(m *MenuResource) prepare(sym string, values map[string]string, sizer *Sizer) (map[string]string, error) { - var sink *string +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 { @@ -118,7 +119,7 @@ func(m *MenuResource) prepare(sym string, values map[string]string, sizer *Sizer return nil, err } if sz == 0 { - sink = &k + sink = k sinkValues = strings.Split(v, "\n") v = "" log.Printf("found sink %s with field count %v", k, len(sinkValues)) @@ -126,12 +127,12 @@ func(m *MenuResource) prepare(sym string, values map[string]string, sizer *Sizer noSinkValues[k] = v } - if sink == nil { + if sink == "" { log.Printf("no sink found for sym %s", sym) return values, nil } - s, err := m.render(sym, noSinkValues, sizer) + s, err := m.render(sym, noSinkValues, 0, nil) if err != nil { return nil, err } @@ -141,35 +142,51 @@ func(m *MenuResource) prepare(sym string, values map[string]string, sizer *Sizer return nil, fmt.Errorf("capacity exceeded") } - l := 0 - r := "" - r_tmp := "" + 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 r_tmp == "" { + if tb.Len() == 0 { return nil, fmt.Errorf("capacity insufficient for sink field %v", i) } - r += r_tmp + "\n" - r_tmp = "" + rb.WriteString(tb.String()) + rb.WriteRune('\n') + c := uint32(rb.Len()) + sizer.AddCursor(c) + tb.Reset() l = 0 } - r_tmp += v + if tb.Len() > 0 { + tb.WriteByte(byte(0x00)) + } + tb.WriteString(v) } - r += r_tmp - - if r[len(r)-1] != '\n' { - r += "\n" + 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) } - noSinkValues[*sink] = r return noSinkValues, nil } -func(m *MenuResource) RenderTemplate(sym string, values map[string]string) (string, error) { - return DefaultRenderTemplate(m, sym, values) +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) { @@ -184,18 +201,20 @@ 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) { +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) + s, err := m.RenderTemplate(sym, values, idx, sizer) if err != nil { return "", err } log.Printf("rendered %v bytes for template", len(s)) r += s - _, ok = sizer.Check(r) - if !ok { - return "", fmt.Errorf("limit exceeded: %v", sizer) + if sizer != nil { + _, ok = sizer.Check(r) + if !ok { + return "", fmt.Errorf("limit exceeded: %v", sizer) + } } s, err = m.RenderMenu() if err != nil { @@ -203,20 +222,22 @@ func(m *MenuResource) render(sym string, values map[string]string, sizer *Sizer) } log.Printf("rendered %v bytes for menu", len(s)) r += s - _, ok = sizer.Check(r) - if !ok { - return "", fmt.Errorf("limit exceeded: %v", sizer) + 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, sizer *Sizer) (string, error) { +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, sizer) + values, err = m.prepare(sym, values, idx, sizer) if err != nil { return "", err } - return m.render(sym, values, sizer) + return m.render(sym, values, idx, sizer) } diff --git a/go/resource/size.go b/go/resource/size.go index 42748a4..99da5ce 100644 --- a/go/resource/size.go +++ b/go/resource/size.go @@ -1,8 +1,10 @@ package resource import ( + "bytes" "fmt" "log" + "strings" "git.defalsify.org/festive/state" ) @@ -12,6 +14,7 @@ type Sizer struct { menuSize uint16 memberSizes map[string]uint16 totalMemberSize uint32 + crsrs []uint32 sink string } @@ -21,6 +24,7 @@ func SizerFromState(st *state.State) (Sizer, error){ menuSize: st.GetMenuSize(), memberSizes: make(map[string]uint16), } + sizes, err := st.Sizes() if err != nil { return sz, err @@ -56,12 +60,39 @@ func(szr *Sizer) String() string { } func(szr *Sizer) Size(s string) (uint16, error) { - if szr.sink == s { - return 0, nil - } r, ok := szr.memberSizes[s] if !ok { return 0, fmt.Errorf("unknown member: %s", s) } return r, nil } + +func(szr *Sizer) AddCursor(c uint32) { + log.Printf("added cursor: %v", c) + szr.crsrs = append(szr.crsrs, c) +} + +func(szr *Sizer) GetAt(values map[string]string, idx uint16) (map[string]string, error) { + if szr.sink == "" { + return values, nil + } + outValues := make(map[string]string) + for k, v := range values { + if szr.sink == k { + if idx >= uint16(len(szr.crsrs)) { + return nil, fmt.Errorf("no more values in index") + } + c := szr.crsrs[idx] + v = v[c:] + nl := strings.Index(v, "\n") + log.Printf("k %v v %v c %v nl %v", k, v, c, nl) + if nl > 0 { + v = v[:nl] + } + b := bytes.ReplaceAll([]byte(v), []byte{0x00}, []byte{0x0a}) + v = string(b) + } + outValues[k] = v + } + return outValues, nil +} diff --git a/go/resource/size_test.go b/go/resource/size_test.go index 5958a26..fd1eca0 100644 --- a/go/resource/size_test.go +++ b/go/resource/size_test.go @@ -19,7 +19,8 @@ func getTemplate(sym string, sizer *Sizer) (string, error) { 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 } @@ -32,6 +33,8 @@ func funcFor(sym string) (EntryFunc, error) { return getBar, nil case "baz": return getBaz, nil + case "xyzzy": + return getXyzzy, nil } return nil, fmt.Errorf("unknown func: %s", sym) } @@ -48,6 +51,9 @@ 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 TestSizeLimit(t *testing.T) { st := state.NewState(0).WithOutputSize(128) @@ -81,7 +87,7 @@ func TestSizeLimit(t *testing.T) { } _ = tpl - _, err = rs.Render("small", vals, &szr) + _, err = rs.Render("small", vals, 0, &szr) if err != nil { t.Fatal(err) } @@ -89,8 +95,74 @@ func TestSizeLimit(t *testing.T) { rs.PutMenu("1", "foo the foo") rs.PutMenu("2", "go to bar") - _, err = rs.Render("toobig", vals, &szr) + _, 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.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/state/state.go b/go/state/state.go index 09ead09..d593ea9 100644 --- a/go/state/state.go +++ b/go/state/state.go @@ -37,7 +37,7 @@ type State struct { sizes map[string]uint16 // Size limits for all loaded symbols. bitSize uint32 // size of (32-bit capacity) bit flag byte array sink *string - //sizeIdx uint16 + sizeIdx uint16 } func toByteSize(bitSize uint32) uint8 { @@ -204,12 +204,12 @@ func(st State) WithOutputSize(outputSize uint32) State { } // Where returns the current active rendering symbol. -func(st State) Where() string { +func(st State) Where() (string, uint16) { if len(st.execPath) == 0 { - return "" + return "", 0 } l := len(st.execPath) - return st.execPath[l-1] + return st.execPath[l-1], st.sizeIdx } // Down adds the given symbol to the command stack. diff --git a/go/vm/runner.go b/go/vm/runner.go index eb0c391..4ae2468 100644 --- a/go/vm/runner.go +++ b/go/vm/runner.go @@ -89,7 +89,7 @@ func RunDeadCheck(b []byte, st *state.State, rs resource.Resource, ctx context.C if r { return b, nil } - location := st.Where() + location, _ := st.Where() if location == "" { return b, fmt.Errorf("dead runner with no current location") } @@ -180,7 +180,7 @@ func RunMove(b []byte, st *state.State, rs resource.Resource, ctx context.Contex } if sym == "_" { st.Up() - sym = st.Where() + sym, _ = st.Where() } else { st.Down(sym) } diff --git a/go/vm/runner_test.go b/go/vm/runner_test.go index bc6519c..5be3bd7 100644 --- a/go/vm/runner_test.go +++ b/go/vm/runner_test.go @@ -51,8 +51,8 @@ func (r *TestResource) GetTemplate(sym string, sizer *resource.Sizer) (string, e return "", fmt.Errorf("unknown symbol %s", sym) } -func (r *TestResource) RenderTemplate(sym string, values map[string]string) (string, error) { - return resource.DefaultRenderTemplate(r, sym, values) +func (r *TestResource) RenderTemplate(sym string, values map[string]string, idx uint16, sizer *resource.Sizer) (string, error) { + return resource.DefaultRenderTemplate(r, sym, values, idx, sizer) } func (r *TestResource) FuncFor(sym string) (resource.EntryFunc, error) { @@ -117,7 +117,7 @@ func TestRunLoadRender(t *testing.T) { if err != nil { t.Error(err) } - r, err := rs.RenderTemplate("foo", m) + r, err := rs.RenderTemplate("foo", m, 0, nil) if err != nil { t.Error(err) } @@ -126,7 +126,7 @@ func TestRunLoadRender(t *testing.T) { t.Errorf("Expected %v, got %v", []byte(expect), []byte(r)) } - r, err = rs.RenderTemplate("bar", m) + r, err = rs.RenderTemplate("bar", m, 0, nil) if err == nil { t.Errorf("expected error for render of bar: %v" ,err) } @@ -147,7 +147,7 @@ func TestRunLoadRender(t *testing.T) { if err != nil { t.Error(err) } - r, err = rs.RenderTemplate("bar", m) + r, err = rs.RenderTemplate("bar", m, 0, nil) if err != nil { t.Error(err) } @@ -219,7 +219,7 @@ func TestHalt(t *testing.T) { if err != nil { t.Error(err) } - r := st.Where() + r, _ := st.Where() if r == "foo" { t.Fatalf("Expected where-symbol not to be 'foo'") } @@ -245,7 +245,7 @@ func TestRunArg(t *testing.T) { if l != 0 { t.Errorf("expected empty remainder, got length %v: %v", l, b) } - r := st.Where() + r, _ := st.Where() if r != "baz" { t.Errorf("expected where-state baz, got %v", r) } @@ -270,7 +270,7 @@ func TestRunInputHandler(t *testing.T) { if err != nil { t.Fatal(err) } - r := st.Where() + r, _ := st.Where() if r != "foo" { t.Fatalf("expected where-sym 'foo', got '%v'", r) } @@ -291,7 +291,7 @@ func TestRunArgInvalid(t *testing.T) { if err != nil { t.Fatal(err) } - r := st.Where() + r, _ := st.Where() if r != "_catch" { t.Fatalf("expected where-state _catch, got %v", r) }