commit 654c9b4e62528a4415010b3a44b7bb09ee6b0eee
parent f034631227cefb6e5dd879eb9ab1744af7a78dbe
Author: lash <dev@holbrook.no>
Date: Tue, 2 May 2023 09:05:09 +0100
Copy working example description to readme
Diffstat:
11 files changed, 348 insertions(+), 57 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
@@ -1,3 +1,6 @@
+- 0.1.15
+ * Add comprehensive documentation with texinfo.
+ * Prevent cursor leak in sizer.
- 0.1.14
* Add language support for menus.
* Support for multi-page menus (only works for template renders without a sink).
diff --git a/README.md b/README.md
@@ -1,52 +1,261 @@
# vise: A Constrained Size Output Virtual Machine
-An attempt at defining a small VM to handle menu interaction for size-constrained clients.
+Consider the following interaction:
-Original motivation for this project was to create a simple templating renderer for USSD clients, enhanced with a pluggable and agnostic interface for external data-retrieval.
+```
+This is the root page
+You have visited 1 time.
+0:foo
+1:bar
-## Features
+$ 1
+Please visit foo first.
+Any input to return.
-### Implemented
+$ x
+This is the root page.
+You have visited 2 times.
+0:foo
+1:bar
-* Define and enforce max output size for every individual output.
-* Allow one single data entry to fill remaining available size capacity.
-* An assembly-like mini-language to define:
- - external code execution.
- - input validation and routing.
- - menu definitions.
- - flow control.
- - exception handling.
-* templated output from results of external code execution.
-* generate and navigate pages where data symbol contents are too long to display on a single page.
-* pluggable function design for handling external code execution calls.
-* Dedicated error string to prepend to template (e.g. on catch)
+$ 0
+Welcome to page foo.
+Please write seomthing.
+$ blah blah blah
+This is the root page.
+You have visited 3 times.
+0:foo
+1:bar
-### Pending
+$ 1
+Thanks for visiting foo and bar.
+You have written:
+blah blah blah
+```
-* Node Walking Audit Tool (NWAT) to ensure all nodes produce output within constraints.
-* Input generator engine for the NWAT.
-* State error flag debugger representation, builtin as well as user-defined.
-* Stepwise debug view on log/stderr of state mutations.
-* Toolset to assist bootstrapping/recovering (failed) state from spec.
+## Components
+This simple interface above involves four different menu nodes.
-### Possibly useful
+In order to engineer these using vise, three types of components are involved:
-* Breakpoints.
-* Key/value database reference example.
+* An assembly-like menu handling script.
+* A display template.
+* External code handlers for the counter and the "something" input.
-## Documentation
+## Nodes
-Please refer to `doc/build/index.html`.
+* `root` - The first page
+* `foo` - The "foo" page
+* `bar` - The "bar" page after "foo" has been visited
+* `ouch` - The "bar" page before "foo" has been visited
-Docs can be rebuilt using `make -B doc`.
-Documentation sources are found in `doc/texinfo/`
+## Templates
+Each page has a template that may or may not contain dynamic elements.
+In this example the root and bar nodes contains dynamic content.
-## Examples
+### root
-Build examples with `make -B examples`
+```
+This is the root page
+You have visited {{.count}}.
+```
+
+
+### foo
+
+```
+Welcome to page foo.
+Please write something.
+```
+
+
+### bar
+
+```
+Thanks for visiting foo and bar.
+You have written:
+{{.something}}
+```
+
+
+### ouch
+
+```
+Please visit foo first.
+Any input to return.
+```
+
+
+## Scripts
+
+The scripts are responsible for defining menus, handling navigation flow control, and triggering external code handlers.
+
+### root
+
+```
+LOAD count 8 # trigger external code handler "count"
+LOAD something 0 # trigger external code handler "something"
+RELOAD count # explicitly trigger "count" every time this code is executed.
+MAP count # make the result from "count" available to the template renderer
+MOUT foo 0 # menu item
+MOUT bar 1 # menu item
+HALT # render template and wait for input
+INCMP foo 0 # match menu selection 0, move to node "foo" on match
+INCMP bar 1 # match menu selection 1, move to node "bar" on match
+```
+
+### foo
+
+```
+HALT # render template and wait for input
+RELOAD something # pass input to the "something" external code handler.
+ # The input will be appended to the stored value.
+ # The "HAVESOMETHING" flag (8) will be set.
+MOVE _ # move up one level
+```
+
+
+### bar
+
+```
+CATCH ouch 8 0 # if the "HAVESOMETHING" (8) flag has NOT (0) been set, move to "ouch"
+MNEXT next 11 # menu choice to display for advancing one page
+MPREV back 22 # menu choice to display for going back to the previous page
+MAP something # make the result from "something" available to the template renderer
+HALT # render template and wait for input
+INCMP > 11 # handle the "next" menu choice
+INCMP < 22 # handle to "back" menu choice
+INCMP ^ * # move to the root node on any input
+```
+
+
+### ouch
+
+```
+HALT # render template and wait for input
+INCMP ^ * # move to the root node on any input
+```
+
+## External code handlers
+
+The script code contains `LOAD` instructions for two different methods.
+
+```
+import (
+ "context"
+ "fmt"
+ "path"
+ "strings"
+
+ testdataloader "github.com/peteole/testdata-loader"
+
+ "git.defalsify.org/vise.git/state"
+ "git.defalsify.org/vise.git/resource"
+)
+
+const (
+ USERFLAG_HAVESOMETHING = iota + state.FLAG_USERSTART
+)
+
+var (
+ baseDir = testdataloader.GetBasePath()
+ scriptDir = path.Join(baseDir, "examples", "intro")
+)
+
+type introResource struct {
+ *resource.FsResource
+ c int64
+ v []string
+}
+
+func newintroResource() introResource {
+ fs := resource.NewFsResource(scriptDir)
+ return introResource{fs, 0, []string{}}
+}
+
+// increment counter.
+// return a string representing the current value of the counter.
+func(c *introResource) count(ctx context.Context, sym string, input []byte) (resource.Result, error) {
+ s := "%v time"
+ if c.c != 1 {
+ s += "s"
+ }
+ r := resource.Result{
+ Content: fmt.Sprintf(s, c.c),
+ }
+ c.c += 1
+ return r, nil
+}
+
+// if input is suppled, append it to the stored string vector and set the HAVESOMETHING flag.
+// return the stored string vector value, one string per line.
+func(c *introResource) something(ctx context.Context, sym string, input []byte) (resource.Result, error) {
+ c.v = append(c.v, string(input))
+ r := resource.Result{
+ Content: strings.Join(c.v, "\n"),
+ }
+ if len(input) > 0 {
+ r.FlagSet = []uint32{USERFLAG_HAVESOMETHING}
+ }
+ return r, nil
+}
+```
+
+## Handling long values
+
+In the above example, the more times the `foo` page is supplied with a value, the longer the vector of values that need to be displayed by the `bar` page will be.
+
+A core feature of `vise` is to magically create browseable pages from these values from a pre-defined maximum output capacity for each page.
+
+Consider the case where the contents of the `something` symbol has become:
+
+```
+foo bar
+baz bazbaz
+inky pinky
+blinky
+clyde
+```
+
+Given a size constaint of 90 characters, the display will be split into two pages:
+
+```
+Thanks for visiting foo and bar.
+You have written:
+foo bar
+baz bazbaz
+up:any
+11:next
+```
+
+```
+Thanks for visiting foo and bar.
+You have written:
+inky pinky
+blinky
+clyde
+up:any
+22:back
+```
+
+
+## Working example
+
+In the source code repository, a full working example of this menu can be found in `examples/intro`.
+
+To run it:
+
+```
+make -B intro
+go run ./examples/intro
+```
+
+Use `go run -tags logtrace ...` to peek at what is going on under the hood.
+
+To play the "Handling long values" case above, limit the output size by adding `-s 90`.
diff --git a/doc/build/index.html b/doc/build/index.html
@@ -65,7 +65,8 @@ Next: <a href="overview.html" accesskey="n" rel="next">Overview</a> [<a h
<li><a id="toc-ouch-1" href="sim.html#ouch-1">2.2.4 ouch</a></li>
</ul></li>
<li><a id="toc-External-code-handlers" href="sim.html#External-code-handlers">2.3 External code handlers</a></li>
- <li><a id="toc-Working-example" href="sim.html#Working-example">2.4 Working example</a></li>
+ <li><a id="toc-Handling-long-values" href="sim.html#Handling-long-values">2.4 Handling long values</a></li>
+ <li><a id="toc-Working-example" href="sim.html#Working-example">2.5 Working example</a></li>
</ul></li>
<li><a id="toc-Nomenclature" href="nomenclature.html">3 Nomenclature</a>
<ul class="toc-numbered-mark">
diff --git a/doc/texinfo/sim.texi b/doc/texinfo/sim.texi
@@ -32,7 +32,8 @@ You have visited 3 times.
$ 1
Thanks for visiting foo and bar.
-You wrote "blah blah blah" in foo.
+You have written:
+blah blah blah
@end example
The simple interface above involves four different menu nodes.
@@ -118,7 +119,9 @@ INCMP bar 1 # match menu selection 1, move to node "bar" on match
@verbatim
HALT # render template and wait for input
-RELOAD something # pass input to the "something" external code handler. The "HAVESOMETHING" flag (8) will be set
+RELOAD something # pass input to the "something" external code handler.
+ # The input will be appended to the stored value.
+ # The "HAVESOMETHING" flag (8) will be set.
MOVE _ # move up one level
@end verbatim
@@ -126,10 +129,14 @@ MOVE _ # move up one level
@subsection bar
@verbatim
-CATCH ouch 8 0 # if the "HAVESOMETHING" (8) flag has NOT (0) been set, move to "ouch"
-MAP something # make the result from "something" available to the template renderer
-HALT # render template and wait for input
-INCMP ^ * # move to the root node on any input
+CATCH ouch 8 0 # if the "HAVESOMETHING" (8) flag has NOT (0) been set, move to "ouch"
+MNEXT next 11 # menu choice to display for advancing one page
+MPREV back 22 # menu choice to display for going back to the previous page
+MAP something # make the result from "something" available to the template renderer
+HALT # render template and wait for input
+INCMP > 11 # handle the "next" menu choice
+INCMP < 22 # handle to "back" menu choice
+INCMP ^ * # move to the root node on any input
@end verbatim
@@ -147,6 +154,12 @@ The script code contains @code{LOAD} instructions for two different methods.
@verbatim
import (
+ "context"
+ "fmt"
+ "path"
+ "strings"
+
+
testdataloader "github.com/peteole/testdata-loader"
"git.defalsify.org/vise.git/state"
@@ -162,18 +175,20 @@ var (
scriptDir = path.Join(baseDir, "examples", "intro")
)
-type Counter struct {
+type introResource struct {
*resource.FsResource
c int64
- v string
+ v []string
}
-func newCounter() Counter {
+func newintroResource() introResource {
fs := resource.NewFsResource(scriptDir)
- return Counter{fs, 0, ""}
+ return introResource{fs, 0, []string{}}
}
-func(c *Counter) count(ctx context.Context, sym string, input []byte) (resource.Result, error) {
+// increment counter.
+// return a string representing the current value of the counter.
+func(c *introResource) count(ctx context.Context, sym string, input []byte) (resource.Result, error) {
s := "%v time"
if c.c != 1 {
s += "s"
@@ -185,10 +200,12 @@ func(c *Counter) count(ctx context.Context, sym string, input []byte) (resource.
return r, nil
}
-func(c *Counter) something(ctx context.Context, sym string, input []byte) (resource.Result, error) {
- c.v = string(input)
+// if input is suppled, append it to the stored string vector and set the HAVESOMETHING flag.
+// return the stored string vector value, one string per line.
+func(c *introResource) something(ctx context.Context, sym string, input []byte) (resource.Result, error) {
+ c.v = append(c.v, string(input))
r := resource.Result{
- Content: c.v,
+ Content: strings.Join(c.v, "\n"),
}
if len(input) > 0 {
r.FlagSet = []uint32{USERFLAG_HAVESOMETHING}
@@ -198,6 +215,45 @@ func(c *Counter) something(ctx context.Context, sym string, input []byte) (resou
@end verbatim
+@anchor{long_values}
+@section Handling long values
+
+In the above example, the more times the @code{foo} page is supplied with a value, the longer the vector of values that need to be displayed by the @code{bar} page will be.
+
+A core feature of @code{vise} is to magically create browseable pages from these values, from a pre-defined maximum output capacity for each page.
+
+Consider the case where the contents of the @code{something} symbol has become:
+
+@verbatim
+foo bar
+baz bazbaz
+inky pinky
+blinky
+clyde
+@end verbatim
+
+Given a size constaint of 90 characters, the display will be split into two pages:
+
+@verbatim
+Thanks for visiting foo and bar.
+You have written:
+foo bar
+baz bazbaz
+up:any
+11:next
+@end verbatim
+
+@verbatim
+Thanks for visiting foo and bar.
+You have written:
+inky pinky
+blinky
+clyde
+up:any
+22:back
+@end verbatim
+
+
@section Working example
In the source code repository, a full working example of this menu can be found in @file{examples/intro}.
@@ -210,3 +266,5 @@ go run ./examples/intro
@end example
Use @code{go run -tags logtrace ...} to peek at what is going on under the hood.
+
+To play the @ref{long_values, Long Values} case above, limit the output size by adding @code{-s 90}.
diff --git a/engine/engine.go b/engine/engine.go
@@ -205,6 +205,7 @@ func(en *Engine) WriteResult(ctx context.Context, w io.Writer) (int, error) {
if en.st.Language != nil {
ctx = context.WithValue(ctx, "Language", *en.st.Language)
}
+ Logg.TraceCtxf(ctx, "render with state", "state", en.st)
r, err := en.vm.Render(ctx)
if err != nil {
return 0, err
diff --git a/examples/intro/bar b/examples/intro/bar
@@ -1,2 +1,3 @@
Thanks for visiting foo and bar.
-You wrote "{{.something}}" in foo.
+You have written:
+{{.something}}
diff --git a/examples/intro/bar.vis b/examples/intro/bar.vis
@@ -1,4 +1,9 @@
CATCH ouch 8 0
MAP something
+MNEXT next 11
+MPREV back 22
+MOUT up any
HALT
+INCMP > 11
+INCMP < 22
INCMP ^ *
diff --git a/examples/intro/main.go b/examples/intro/main.go
@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path"
+ "strings"
testdataloader "github.com/peteole/testdata-loader"
@@ -24,18 +25,20 @@ var (
scriptDir = path.Join(baseDir, "examples", "intro")
)
-type Counter struct {
+type introResource struct {
*resource.FsResource
c int64
- v string
+ v []string
}
-func newCounter() Counter {
+func newintroResource() introResource {
fs := resource.NewFsResource(scriptDir)
- return Counter{fs, 0, ""}
+ return introResource{fs, 0, []string{}}
}
-func(c *Counter) count(ctx context.Context, sym string, input []byte) (resource.Result, error) {
+// increment counter.
+// return a string representing the current value of the counter.
+func(c *introResource) count(ctx context.Context, sym string, input []byte) (resource.Result, error) {
s := "%v time"
if c.c != 1 {
s += "s"
@@ -47,10 +50,12 @@ func(c *Counter) count(ctx context.Context, sym string, input []byte) (resource.
return r, nil
}
-func(c *Counter) something(ctx context.Context, sym string, input []byte) (resource.Result, error) {
- c.v = string(input)
+// if input is suppled, append it to the stored string vector and set the HAVESOMETHING flag.
+// return the stored string vector value, one string per line.
+func(c *introResource) something(ctx context.Context, sym string, input []byte) (resource.Result, error) {
+ c.v = append(c.v, string(input))
r := resource.Result{
- Content: c.v,
+ Content: strings.Join(c.v, "\n"),
}
if len(input) > 0 {
r.FlagSet = []uint32{USERFLAG_HAVESOMETHING}
@@ -70,7 +75,7 @@ func main() {
fmt.Fprintf(os.Stderr, "starting session at symbol '%s' using resource dir: %s\n", root, dir)
st := state.NewState(3)
- rs := newCounter()
+ rs := newintroResource()
rs.AddLocalFunc("count", rs.count)
rs.AddLocalFunc("something", rs.something)
ca := cache.NewCache()
diff --git a/render/menu.go b/render/menu.go
@@ -54,7 +54,7 @@ type Menu struct {
}
func(m Menu) String() string {
- return fmt.Sprintf("pagecount: %v sink: %v next: %v prev: %v", m.pageCount, m.sink, m.canNext, m.canPrevious)
+ return fmt.Sprintf("pagecount: %v menusink: %v next: %v prev: %v", m.pageCount, m.sink, m.canNext, m.canPrevious)
}
// NewMenu creates a new Menu with an explicit page count.
diff --git a/render/page.go b/render/page.go
@@ -206,6 +206,9 @@ func(pg *Page) Reset() {
if pg.menu != nil {
pg.menu.Reset()
}
+ if pg.sizer != nil {
+ pg.sizer.Reset()
+ }
}
// extract sink values to separate array, and set the content of sink in values map to zero-length string.
diff --git a/render/size.go b/render/size.go
@@ -95,6 +95,7 @@ func(szr *Sizer) GetAt(values map[string]string, idx uint16) (map[string]string,
}
outValues := make(map[string]string)
for k, v := range values {
+ Logg.Tracef("check values", "k", k, "v", v, "idx", idx, "cursors", szr.crsrs)
if szr.sink == k {
if idx >= uint16(len(szr.crsrs)) {
return nil, fmt.Errorf("no more values in index")
@@ -112,3 +113,7 @@ func(szr *Sizer) GetAt(values map[string]string, idx uint16) (map[string]string,
}
return outValues, nil
}
+
+func(szr *Sizer) Reset() {
+ szr.crsrs = []uint32{}
+}