commit 6a1611a0c87098f2e627843de9f319ad6e6e8bca
parent 629cae8a5590fc5529e424e690439b74e4953f06
Author: lash <dev@holbrook.no>
Date: Sat, 8 Apr 2023 22:32:17 +0100
Factor out menu handling
Diffstat:
A | go/menu/menu.go | | | 132 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | go/menu/menu_test.go | | | 94 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | go/resource/resource.go | | | 102 | ------------------------------------------------------------------------------- |
3 files changed, 226 insertions(+), 102 deletions(-)
diff --git a/go/menu/menu.go b/go/menu/menu.go
@@ -0,0 +1,132 @@
+package menu
+
+import (
+ "fmt"
+ "log"
+)
+
+// BrowseConfig defines the availability and display parameters for page browsing.
+type BrowseConfig struct {
+ NextAvailable bool
+ NextSelector string
+ NextTitle string
+ PreviousAvailable bool
+ PreviousSelector string
+ PreviousTitle string
+}
+
+// Default browse settings for convenience.
+func DefaultBrowseConfig() BrowseConfig {
+ return BrowseConfig{
+ NextAvailable: true,
+ NextSelector: "11",
+ NextTitle: "next",
+ PreviousAvailable: true,
+ PreviousSelector: "22",
+ PreviousTitle: "previous",
+ }
+}
+
+type Menu struct {
+ menu [][2]string
+ browse BrowseConfig
+ pageCount uint16
+ canNext bool
+ canPrevious bool
+}
+
+// NewMenu creates a new Menu with an explicit page count.
+func NewMenu() *Menu {
+ return &Menu{}
+}
+
+// WithBrowseConfig defines the criteria for page browsing.
+func(m *Menu) WithPageCount(pageCount uint16) *Menu {
+ m.pageCount = pageCount
+ return m
+}
+
+// WithBrowseConfig defines the criteria for page browsing.
+func(m *Menu) WithBrowseConfig(cfg BrowseConfig) *Menu {
+ m.browse = cfg
+
+ return m
+}
+
+// Put adds a menu option to the menu rendering.
+func(m *Menu) Put(selector string, title string) error {
+ m.menu = append(m.menu, [2]string{selector, title})
+ log.Printf("menu %v", m.menu)
+ return nil
+}
+
+// Render returns the full current state of the menu as a string.
+//
+// After this has been executed, the state of the menu will be empty.
+func(m *Menu) Render(idx uint16) (string, error) {
+ err := m.applyPage(idx)
+ if err != nil {
+ return "", err
+ }
+
+ r := ""
+ for true {
+ l := len(r)
+ choice, title, err := m.shiftMenu()
+ if err != nil {
+ break
+ }
+ if l > 0 {
+ r += "\n"
+ }
+ r += fmt.Sprintf("%s:%s", choice, title)
+ }
+ return r, nil
+}
+
+// add available browse options.
+func(m *Menu) applyPage(idx uint16) error {
+ if m.pageCount == 0 {
+ return nil
+ } else if idx >= m.pageCount {
+ return fmt.Errorf("index %v out of bounds (%v)", idx, m.pageCount)
+ }
+ if m.browse.NextAvailable {
+ m.canNext = true
+ }
+ if m.browse.PreviousAvailable {
+ m.canPrevious = true
+ }
+ if idx == m.pageCount - 1 {
+ m.canNext = false
+ }
+ if idx == 0 {
+ m.canPrevious = false
+ }
+ if m.canNext {
+ err := m.Put(m.browse.NextSelector, m.browse.NextTitle)
+ if err != nil {
+ return err
+ }
+ }
+ if m.canPrevious {
+ err := m.Put(m.browse.PreviousSelector, m.browse.PreviousTitle)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// removes and returns the first of remaining menu options.
+// fails if menu is empty.
+func(m *Menu) shiftMenu() (string, string, error) {
+ if len(m.menu) == 0 {
+ return "", "", fmt.Errorf("menu is empty")
+ }
+ r := m.menu[0]
+ m.menu = m.menu[1:]
+ return r[0], r[1], nil
+}
+
+
diff --git a/go/menu/menu_test.go b/go/menu/menu_test.go
@@ -0,0 +1,94 @@
+package menu
+
+import (
+ "testing"
+)
+
+func TestMenuInit(t *testing.T) {
+ m := NewMenu()
+ 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(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expect := `1:foo
+2:bar`
+ if r != expect {
+ t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r)
+ }
+}
+
+func TestMenuBrowse(t *testing.T) {
+ cfg := DefaultBrowseConfig()
+ m := NewMenu().WithPageCount(3).WithBrowseConfig(cfg)
+ 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(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expect := `1:foo
+2:bar
+11:next`
+ if r != expect {
+ 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)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expect = `1:foo
+2:bar
+11:next
+22:previous`
+ if r != expect {
+ 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)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expect = `1:foo
+2:bar
+22:previous`
+ if r != expect {
+ t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r)
+ }
+
+ _, err = m.Render(3)
+ if err == nil {
+ t.Fatalf("expected render fail")
+ }
+}
diff --git a/go/resource/resource.go b/go/resource/resource.go
@@ -17,20 +17,12 @@ type FuncForFunc func(sym string) (EntryFunc, error)
type Resource interface {
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.
- SetMenuBrowse(string, string, bool) error // Set menu browser display details.
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(idx uint16) (string, error) // Render the current state of menu
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.
}
type MenuResource struct {
- menu [][2]string
- next [2]string
- prev [2]string
- canNext bool
- canPrev bool
sinkValues []string
codeFunc CodeFunc
templateFunc TemplateFunc
@@ -60,100 +52,6 @@ func(m *MenuResource) WithTemplateGetter(templateGetter TemplateFunc) *MenuResou
return m
}
-// SetMenuBrowse defines the how pagination menu options should be displayed.
-//
-// The selector is the expected user input, and the title is the description string.
-//
-// If back is set, the option will be defined for returning to a previous page.
-func(m *MenuResource) SetMenuBrowse(selector string, title string, back bool) error {
- entry := [2]string{selector, title}
- if back {
- m.prev = entry
- m.canPrev = true
- } else {
- m.next = entry
- m.canNext = true
- }
- return nil
-}
-
-//func(m *MenuResource) putNext() error {
-// return m.PutMenu(m.next[0], m.next[1])
-//}
-//
-//func(m *MenuResource) putPrevious() error {
-// return m.PutMenu(m.prev[0], m.prev[1])
-//}
-
-// PutMenu adds a menu option to the menu rendering.
-func(m *MenuResource) PutMenu(selector string, title string) error {
- m.menu = append(m.menu, [2]string{selector, title})
- log.Printf("menu %v", m.menu)
- return nil
-}
-
-// removes and returns the first of remaining menu options.
-// fails if menu is empty.
-func(m *MenuResource) shiftMenu() (string, string, error) {
- if len(m.menu) == 0 {
- return "", "", fmt.Errorf("menu is empty")
- }
- r := m.menu[0]
- m.menu = m.menu[1:]
- return r[0], r[1], nil
-}
-
-// add available browse options.
-func(m *MenuResource) applyPage(idx uint16) error {
- l := uint16(len(m.sinkValues))
- if l == 0 {
- return nil
- }
- if idx == l - 1 {
- m.canNext = false
- }
- if idx == 0 {
- m.canPrev = false
- }
- if m.canNext {
- err := m.PutMenu(m.next[0], m.next[1])
- if err != nil {
- return err
- }
- }
- if m.canPrev {
- err := m.PutMenu(m.prev[0], m.prev[1])
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-// RenderMenu returns the full current state of the menu as a string.
-//
-// After this has been executed, the state of the menu will be empty.
-func(m *MenuResource) RenderMenu(idx uint16) (string, error) {
- err := m.applyPage(idx)
- if err != nil {
- return "", err
- }
-
- r := ""
- for true {
- l := len(r)
- choice, title, err := m.shiftMenu()
- if err != nil {
- break
- }
- if l > 0 {
- r += "\n"
- }
- r += fmt.Sprintf("%s:%s", choice, title)
- }
- return r, nil
-}
-
// 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