From ce1c0229d97735bc716f9384e676eb680bc460e1 Mon Sep 17 00:00:00 2001 From: Tony Worm <1390600+verdverm@users.noreply.github.com> Date: Mon, 18 Sep 2023 00:34:07 -0400 Subject: [PATCH] hof/tui: support running flows in the playground (#349) --- go.mod | 8 +- go.sum | 8 + lib/structural/input.go | 178 --------------- lib/structural/pick_test.go | 47 ---- lib/tui/components/cue/browser/browser.go | 8 +- lib/tui/components/cue/browser/handle.go | 1 + lib/tui/components/cue/browser/handlers.go | 31 ++- lib/tui/components/cue/browser/rebuild.go | 37 ++-- lib/tui/components/cue/chain/chain.go | 2 - lib/tui/components/cue/flower/codec.go | 18 -- lib/tui/components/cue/flower/events.go | 49 ----- lib/tui/components/cue/flower/flower.go | 136 ------------ lib/tui/components/cue/flower/rebuild.go | 205 ------------------ .../components/cue/helpers/source-config.go | 14 +- lib/tui/components/cue/playground/codec.go | 7 + lib/tui/components/cue/playground/events.go | 7 + lib/tui/components/cue/playground/flow.go | 38 ++++ .../cue/playground/handle-action.go | 7 + .../components/cue/playground/playground.go | 19 +- lib/tui/components/cue/playground/rebuild.go | 81 ++++--- lib/tui/modules/eval/argparse.go | 3 +- lib/tui/modules/eval/help.go | 16 +- lib/tui/tview/textview.go | 46 ++-- 23 files changed, 229 insertions(+), 737 deletions(-) delete mode 100644 lib/structural/input.go delete mode 100644 lib/structural/pick_test.go delete mode 100644 lib/tui/components/cue/chain/chain.go delete mode 100644 lib/tui/components/cue/flower/codec.go delete mode 100644 lib/tui/components/cue/flower/events.go delete mode 100644 lib/tui/components/cue/flower/flower.go delete mode 100644 lib/tui/components/cue/flower/rebuild.go create mode 100644 lib/tui/components/cue/playground/flow.go diff --git a/go.mod b/go.mod index bcd9982c2..d4047d0d6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/hofstadter-io/hof go 1.19 require ( - cuelang.org/go v0.6.1-0.20230825121609-3165a5e1aaec + cuelang.org/go v0.6.1-0.20230914115114-8709d8aa9009 dagger.io/dagger v0.8.4 github.com/AlecAivazis/survey/v2 v2.3.7 github.com/BurntSushi/toml v1.3.2 @@ -42,7 +42,7 @@ require ( github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.25.0 golang.org/x/mod v0.12.0 - golang.org/x/text v0.12.0 + golang.org/x/text v0.13.0 gopkg.in/errgo.v2 v2.1.0 gopkg.in/inconshreveable/log15.v2 v2.16.0 gopkg.in/irc.v3 v3.1.4 @@ -65,7 +65,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudflare/circl v1.3.3 // indirect - github.com/cockroachdb/apd/v3 v3.2.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect @@ -139,7 +139,7 @@ require ( golang.org/x/sys v0.11.0 // indirect golang.org/x/term v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.12.0 // indirect + golang.org/x/tools v0.12.1-0.20230831182430-914b218fc34e // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 5d7eda041..3bf8ed27f 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ cuelang.org/go v0.6.0 h1:dJhgKCog+FEZt7OwAYV1R+o/RZPmE8aqFoptmxSWyr8= cuelang.org/go v0.6.0/go.mod h1:9CxOX8aawrr3BgSdqPj7V0RYoXo7XIb+yDFC6uESrOQ= cuelang.org/go v0.6.1-0.20230825121609-3165a5e1aaec h1:eHTn+cwWCuEh85i0YHKzAsqviWA3DMBWclHVjY9bVlw= cuelang.org/go v0.6.1-0.20230825121609-3165a5e1aaec/go.mod h1:wa/Zpkjd2eUdCKwiRjfKSHpg43H2l9Xt1ARCG/Pv6kQ= +cuelang.org/go v0.6.1-0.20230914115114-8709d8aa9009 h1:o4qbXmMH3p06p8R7wTP6Kt3RgWgl6klnvgIJ+Nvl9dI= +cuelang.org/go v0.6.1-0.20230914115114-8709d8aa9009/go.mod h1:o4D6mLEcJA0vEeMkkj+BbDxxxrs99BFI9PQcpPkr/k4= dagger.io/dagger v0.8.4 h1:2zNu40cBvPZc/CSgMnLRfayfQj5VtbJlDtWJyWGwGSQ= dagger.io/dagger v0.8.4/go.mod h1:Nwl7WI8YETaZhGjPJvkiOZnKLJXBaJOkSarp5m4+FxA= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= @@ -104,6 +106,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/apd/v3 v3.2.0 h1:79kHCn4tO0VGu3W0WujYrMjBDk8a2H4KEUYcXf7whcg= github.com/cockroachdb/apd/v3 v3.2.0/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/codemodus/kace v0.5.1 h1:4OCsBlE2c/rSJo375ggfnucv9eRzge/U5LrrOZd47HA= github.com/codemodus/kace v0.5.1/go.mod h1:coddaHoX1ku1YFSe4Ip0mL9kQjJvKkzb9CfIdG1YR04= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= @@ -639,6 +643,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -696,6 +702,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.12.1-0.20230831182430-914b218fc34e h1:LCH4vhLjJj+bC1UcCsMV5PgdNURPVrAd46hk0A9R6Uw= +golang.org/x/tools v0.12.1-0.20230831182430-914b218fc34e/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/lib/structural/input.go b/lib/structural/input.go deleted file mode 100644 index eaf8c949c..000000000 --- a/lib/structural/input.go +++ /dev/null @@ -1,178 +0,0 @@ -package structural - -import ( - "fmt" - "io" - "os" - "path/filepath" - "sort" - - "cuelang.org/go/cue" - "cuelang.org/go/cue/ast" - "cuelang.org/go/cue/load" - "cuelang.org/go/encoding/json" - "cuelang.org/go/encoding/yaml" -) - -type Input struct { - Original string - Entrypoints []string - Filename string - Filetype string // yaml, json, cue... toml? - Expression string // cue expression to select within document - Content []byte - Value cue.Value -} - -var debug = false - -// Loads the entrypoints using the context provided -// returns the value from the load after validating it -func LoadCueInputs(entrypoints []string, ctx *cue.Context, cfg *load.Config) (cue.Value, error) { - - if cfg == nil { - cfg = &load.Config{ - DataFiles: true, - } - } - - bis := load.Instances(entrypoints, cfg) - - bi := bis[0] - // check for errors on the instance - // these are typically parsing errors - if bi.Err != nil { - return cue.Value{}, bi.Err - } - - // add back any orphaned files (json/yaml) - for _, f := range bi.OrphanedFiles { - d, err := os.ReadFile(f.Filename) - if err != nil { - fmt.Println("error: ", err) - os.Exit(1) - } - - switch f.Encoding { - - case "json": - A, err := json.Extract(f.Filename, d) - if err != nil { - fmt.Println("error: ", err) - os.Exit(1) - } - - F := &ast.File{ - Filename: f.Filename, - Decls: []ast.Decl{A}, - } - bi.AddSyntax(F) - - case "yaml": - F, err := yaml.Extract(f.Filename, d) - if err != nil { - fmt.Println("error: ", err) - os.Exit(1) - } - bi.AddSyntax(F) - - default: - if debug { - fmt.Println("unknown encoding for", f.Filename, f.Encoding) - } - } - } - - // Use cue.Context to turn build.Instance to cue.Instance - value := ctx.BuildInstance(bi) - if value.Err() != nil { - return cue.Value{}, value.Err() - } - - // Validate the value - err := value.Validate( - cue.ResolveReferences(false), - cue.Concrete(false), - cue.Definitions(true), - cue.Hidden(true), - cue.Optional(true), - cue.Attributes(true), - cue.Docs(false), - ) - if err != nil { - return cue.Value{}, err - } - - return value, nil -} - -func LoadGlobs(globs []string) ([]Input, error) { - // no globs, then stdin - if len(globs) == 0 { - globs = []string{"-"} - } - - // handle special stdin case - if len(globs) == 1 && globs[0] == "-" { - b, err := io.ReadAll(os.Stdin) - if err != nil { - return nil, err - } - i := []Input{Input{Filename: "-", Content: b}} - return i, nil - } - - // handle general case - // we will load into a map to remove duplicates - // and then extract and sort in a slice - inputs := make(map[string]Input) - for _, g := range globs { - // need to check for expression syntax here - - matches, err := filepath.Glob(g) - if err != nil { - return nil, err - } - - for _, m := range matches { - // continue on duplicate - if _, ok := inputs[m]; ok { - continue - } - - d, err := os.ReadFile(m) - if err != nil { - return nil, err - } - - // handle input types - ext := filepath.Ext(m) - switch ext { - case ".yml", ".yaml": - s := fmt.Sprintf(yamlMod, string(d)) - d = []byte(s) - } - - inputs[m] = Input{Filename: m, Content: d} - } - } - - ret := make([]Input, 0) - for _, i := range inputs { - ret = append(ret, i) - } - - sort.Slice(ret, func(i, j int) bool { - return ret[i].Filename < ret[j].Filename - }) - - return ret, nil -} - -const yamlMod = ` -import "encoding/yaml" -#content: #""" -%s -"""# -yaml.Unmarshal(#content) -` diff --git a/lib/structural/pick_test.go b/lib/structural/pick_test.go deleted file mode 100644 index 4b6ed4344..000000000 --- a/lib/structural/pick_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package structural_test - -import ( - "testing" - - "cuelang.org/go/cue" - "cuelang.org/go/cue/cuecontext" - - "github.com/hofstadter-io/hof/lib/structural" -) - -func TestPick(t *testing.T) { - ctx := cuecontext.New() - - V, err := structural.LoadCueInputs([]string{"testdata/pick-cases.cue"}, ctx, nil) - if err != nil { - s := structural.FormatCueError(err) - t.Fatalf("Error: %s", s) - } - - cases := V.LookupPath(cue.ParsePath("pick_cases")) - iter, _ := cases.List() - - for iter.Next() { - curr := iter.Value() - value := curr.LookupPath(cue.ParsePath("value")) - pick := curr.LookupPath(cue.ParsePath("pick")) - // expect := curr.LookupPath(cue.ParsePath("expect")) - - result, err := structural.PickValue(pick, value, nil) - if err != nil { - t.Fatal(err) - } - if result.Err() != nil { - t.Fatalf("in %q: error: %v\n", iter.Selector(), result.Err()) - } - - //same := expect.Unify(result) - //if !same.Exists() || same.Err() != nil { - //t.Fatalf("in %q: expected:\n%v\n\ngot:\n%v\n", iter.Selector(), expect, result) - //} - //o, err := cuetils.FormatOutput(r, "cue") - //if err != nil { - //t.Fatalf("failed to format output in TestPick %d", i) - //} - } -} diff --git a/lib/tui/components/cue/browser/browser.go b/lib/tui/components/cue/browser/browser.go index 04e7e8901..3dad4e4e8 100644 --- a/lib/tui/components/cue/browser/browser.go +++ b/lib/tui/components/cue/browser/browser.go @@ -149,6 +149,10 @@ func (C *Browser) GetValue() cue.Value { return C.value } +func (C *Browser) SetValue(v cue.Value) { + C.value = v +} + func (VB *Browser) GetValueExpr(expr string) func () cue.Value { // tui.Log("trace", fmt.Sprintf("View.GetConnValueExpr from: %s/%s %s", VB.Id(), VB.Name(), expr)) p := cue.ParsePath(expr) @@ -225,13 +229,15 @@ func (VB *Browser) SetupKeybinds() { VB.nextMode = "cue" case 'T': VB.nextMode = "tree" + case 't': + VB.nextMode = "text" // info about CUE value? (stats) //case 'I': // VB.nextMode = "info" // show settings, hidden? - + case 'X': VB.nextMode = "settings" // todo, dive values, and walk back up diff --git a/lib/tui/components/cue/browser/handle.go b/lib/tui/components/cue/browser/handle.go index b184b7c47..b2c807cd3 100644 --- a/lib/tui/components/cue/browser/handle.go +++ b/lib/tui/components/cue/browser/handle.go @@ -11,6 +11,7 @@ type HandlerFunc func (B *Browser, action string, args []string, context map[str // action registry var actions = map[string]HandlerFunc{ + "clear": handleClear, "create": handleSet, "set": handleSet, "add": handleAdd, diff --git a/lib/tui/components/cue/browser/handlers.go b/lib/tui/components/cue/browser/handlers.go index 9bd6a5561..6ec346d58 100644 --- a/lib/tui/components/cue/browser/handlers.go +++ b/lib/tui/components/cue/browser/handlers.go @@ -1,13 +1,27 @@ package browser import ( - "fmt" "strconv" - "github.com/hofstadter-io/hof/lib/tui" "github.com/hofstadter-io/hof/lib/tui/components/cue/helpers" ) +func handleClear(B *Browser, action string, args []string, context map[string]any) (bool, error) { + + for _, cfg := range B.sources { + if cfg.WatchTime > 0 { + cfg.WatchTime = 0 + cfg.StopWatch() + } + } + + B.sources = []*helpers.SourceConfig{} + B.RebuildValue() + B.Rebuild() + + return true, nil +} + func handleSet(B *Browser, action string, args []string, context map[string]any) (bool, error) { sc, err := helpers.CreateFrom(args, context) if err != nil { @@ -15,8 +29,8 @@ func handleSet(B *Browser, action string, args []string, context map[string]any) } B.sources = []*helpers.SourceConfig{sc} - B.setThinking(true) - defer B.setThinking(false) + B.SetThinking(true) + defer B.SetThinking(false) B.RebuildValue() B.Rebuild() @@ -26,13 +40,12 @@ func handleSet(B *Browser, action string, args []string, context map[string]any) func handleAdd(B *Browser, action string, args []string, context map[string]any) (bool, error) { sc, err := helpers.CreateFrom(args, context) if err != nil { - tui.Log("alert", fmt.Sprint("GOT HERE: ", err)) return true, err } B.sources = append(B.sources, sc) - B.setThinking(true) - defer B.setThinking(false) + B.SetThinking(true) + defer B.SetThinking(false) B.RebuildValue() B.Rebuild() @@ -47,8 +60,8 @@ func handleWatchConfig(B *Browser, action string, args []string, context map[str return h, err } cfg.WatchFunc = func() { - B.setThinking(true) - defer B.setThinking(false) + B.SetThinking(true) + defer B.SetThinking(false) B.RebuildValue() B.Rebuild() } diff --git a/lib/tui/components/cue/browser/rebuild.go b/lib/tui/components/cue/browser/rebuild.go index cd5b6cca7..b8939ad58 100644 --- a/lib/tui/components/cue/browser/rebuild.go +++ b/lib/tui/components/cue/browser/rebuild.go @@ -19,25 +19,21 @@ import ( ) func (C *Browser) RebuildValue() { - C.setThinking(true) - defer C.setThinking(false) - - if len(C.sources) > 0 { - // start with an empty value - val := singletons.EmptyValue() - - // fill all the sources into one - for _, S := range C.sources { - v, _ := S.GetValue() + C.SetThinking(true) + defer C.SetThinking(false) + + val := singletons.EmptyValue() + // fill all the sources into one + for _, S := range C.sources { + v, _ := S.GetValue() + if v.Exists() { val = val.FillPath(cue.ParsePath(S.Path), v) } - - // cache in the browser...? - C.value = val } + C.value = val } -func (C *Browser) setThinking(thinking bool) { +func (C *Browser) SetThinking(thinking bool) { c := tcell.ColorWhite if thinking { c = tcell.ColorViolet @@ -52,8 +48,8 @@ func (C *Browser) setThinking(thinking bool) { func (C *Browser) Rebuild() { var err error - C.setThinking(true) - defer C.setThinking(false) + C.SetThinking(true) + defer C.SetThinking(false) path := "" @@ -74,6 +70,14 @@ func (C *Browser) Rebuild() { for _, s := range C.sources { fmt.Fprintf(C.codeW, "%# v\n\n", pretty.Formatter(*s)) } + } else if C.nextMode == "text" { + C.code.Clear() + C.SetPrimitive(C.code) + + for _, s := range C.sources { + txt, _ := s.GetText() + fmt.Fprintln(C.codeW, txt) + } } else if C.nextMode == "tree" { root := tview.NewTreeNode(path) root.SetColor(tcell.ColorSilver) @@ -144,7 +148,6 @@ func (C *Browser) Rebuild() { if err != nil { writeErr(err) } - } if err == nil { diff --git a/lib/tui/components/cue/chain/chain.go b/lib/tui/components/cue/chain/chain.go deleted file mode 100644 index 5f5138feb..000000000 --- a/lib/tui/components/cue/chain/chain.go +++ /dev/null @@ -1,2 +0,0 @@ -package chain - diff --git a/lib/tui/components/cue/flower/codec.go b/lib/tui/components/cue/flower/codec.go deleted file mode 100644 index d17af3d51..000000000 --- a/lib/tui/components/cue/flower/codec.go +++ /dev/null @@ -1,18 +0,0 @@ -package flower - -import ( - "github.com/hofstadter-io/hof/lib/tui/components/widget" -) - - -func (F *Flower) Encode() (map[string]any, error) { - m := make(map[string]any) - - return m, nil -} - -func (F *Flower) Decode(map[string]any) (widget.Widget, error) { - f := New() - - return f, nil -} diff --git a/lib/tui/components/cue/flower/events.go b/lib/tui/components/cue/flower/events.go deleted file mode 100644 index d1a6c11de..000000000 --- a/lib/tui/components/cue/flower/events.go +++ /dev/null @@ -1,49 +0,0 @@ -package flower - -import ( - "github.com/gdamore/tcell/v2" - - "github.com/hofstadter-io/hof/lib/tui" - "github.com/hofstadter-io/hof/lib/tui/tview" -) - -func (F *Flower) setupKeybinds() { - // events (hotkeys) - F.SetInputCapture(func(evt *tcell.EventKey) *tcell.EventKey { - switch evt.Key() { - case tcell.KeyRune: - if (evt.Modifiers() & tcell.ModAlt) == tcell.ModAlt { - switch evt.Rune() { - case 'f': - flexDir := F.GetDirection() - if flexDir == tview.FlexRow { - F.SetDirection(tview.FlexColumn) - } else { - F.SetDirection(tview.FlexRow) - } - tui.Draw() - - case 's': - F.Rebuild() - - case 'S': - F.Rebuild() - - case 'R': - F.Rebuild() - - default: - return evt - } - - return nil - } - - return evt - - default: - return evt - } - }) -} - diff --git a/lib/tui/components/cue/flower/flower.go b/lib/tui/components/cue/flower/flower.go deleted file mode 100644 index 5b1c7a25c..000000000 --- a/lib/tui/components/cue/flower/flower.go +++ /dev/null @@ -1,136 +0,0 @@ -package flower - -import ( - "bytes" - - "cuelang.org/go/cue" - - "github.com/hofstadter-io/hof/cmd/hof/flags" - - flowcontext "github.com/hofstadter-io/hof/flow/context" - "github.com/hofstadter-io/hof/flow/middleware" - "github.com/hofstadter-io/hof/flow/tasks" - "github.com/hofstadter-io/hof/flow/flow" - - "github.com/hofstadter-io/hof/lib/tui/components/cue/browser" - "github.com/hofstadter-io/hof/lib/tui/components/cue/helpers" - "github.com/hofstadter-io/hof/lib/tui/tview" -) - -type valPack struct { - config *helpers.SourceConfig - value cue.Value - viewer *browser.Browser // scope -} - - -type Flower struct { - *tview.Flex - - scope *valPack - - edit *tview.TextArea // text - - // value from which flow is created - value cue.Value - - // TODO, flow DAG viewer - - // TODO, stdout/stderr viewer - - // FOR NOW, something to explore the dag during/after running - final *valPack - - // something to run it - flowctx *flowcontext.Context - stdin, stdout, stderr bytes.Buffer - - - // def want to try terraform/dag package - -} - -func (F *Flower) TypeName() string { - return "cue/flower" -} - -func New() (*Flower) { - - F := &Flower{ - Flex: tview.NewFlex(), - scope: &valPack{}, - final: &valPack{}, - } - - // our wrapper around the CUE widgets - F.Flex = tview.NewFlex().SetDirection(tview.FlexColumn) - - // scope viewer - F.scope.config = &helpers.SourceConfig{} - F.scope.viewer = browser.New(F.scope.config, "cue") - F.scope.viewer.SetName("scope") - F.scope.viewer.SetBorder(true) - - // curr editor - F.edit = tview.NewTextArea() - F.edit. - SetTitle(" expression(s) "). - SetBorder(true) - - F.edit.SetText("", false) - - // TODO, options form - - // TODO, flow DAG viewer - - // for now, results viewer - F.final.config = &helpers.SourceConfig{} - F.final.viewer = browser.New(F.final.config, "cue") - F.final.viewer.SetName("result") - F.final.viewer.SetBorder(true) - F.final.viewer.SetUsingScope(false) - - // layout - F.Flex. - AddItem(F.scope.viewer, 0, 1, true). - AddItem(F.edit, 0, 1, true). - AddItem(F.final.viewer, 0, 1, true) - - return F -} - - -func (F *Flower) runFlow() error { - - postFlow := F.value - - - - - ctx := flowcontext.New() - ctx.RootValue = postFlow - ctx.Stdin = &F.stdin - ctx.Stdout = &F.stdout - ctx.Stderr = &F.stderr - - F.flowctx = ctx - - // how to inject tags into original value - // fill / return value - ropts := flags.RootPflagpole{} - fopts := flags.FlowPflagpole{} - middleware.UseDefaults(ctx, ropts, fopts) - tasks.RegisterDefaults(ctx) - - p, err := flow.OldFlow(ctx, postFlow) - if err != nil { - return err - } - - err = p.Start() - if err != nil { - return err - } - - return nil -} diff --git a/lib/tui/components/cue/flower/rebuild.go b/lib/tui/components/cue/flower/rebuild.go deleted file mode 100644 index bdd3456ac..000000000 --- a/lib/tui/components/cue/flower/rebuild.go +++ /dev/null @@ -1,205 +0,0 @@ -package flower - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "cuelang.org/go/cue" - "github.com/gdamore/tcell/v2" - - "github.com/hofstadter-io/hof/lib/tui" - "github.com/hofstadter-io/hof/lib/tui/components/cue/helpers" - "github.com/hofstadter-io/hof/lib/yagu" -) - -func (F *Flower) setThinking(thinking bool, which string) { - c := tcell.ColorWhite - if thinking { - c = tcell.ColorViolet - } - - switch which { - case "scope": - F.scope.viewer.SetBorderColor(c) - - case "final": - F.final.viewer.SetBorderColor(c) - - default: - F.scope.viewer.SetBorderColor(c) - F.edit.SetBorderColor(c) - F.final.viewer.SetBorderColor(c) - } - go tui.Draw() -} - -func (F *Flower) Rebuild() error { - tui.Log("info", fmt.Sprintf("Flow.rebuildScope %v", F.scope.config)) - var ( - v cue.Value - err error - ) - - // get latest user text - src := F.edit.GetText() - - var sv cue.Value - - { - F.setThinking(true, "scope") - defer F.setThinking(false, "scope") - - // get latest scope - sv, err = F.scope.config.GetValue() - if err != nil { - tui.Log("error", err) - } - - F.scope.viewer.Rebuild() - } - - { - F.setThinking(true, "final") - defer F.setThinking(false, "final") - // rebuild the value - ctx := sv.Context() - v = ctx.CompileString(src, cue.InferBuiltins(true), cue.Scope(sv)) - cfg := &helpers.SourceConfig{Value: v} - - // only update view value, that way, if we erase everything, we still see the value - F.final.config = cfg - F.final.viewer.SetSourceConfig(cfg) - F.final.viewer.Rebuild() - } - - tui.Draw() - return nil -} - -func (F *Flower) HandleAction(action string, args []string, context map[string]any) (bool, error) { - tui.Log("warn", fmt.Sprintf("Flower.HandleAction: %v %v %v", action, args, context)) - var err error - handled := true - - // item actions - switch action { - case "export": - if len(args) != 1 { - err = fmt.Errorf("export requires a filename") - } else { - filename := args[0] - err := F.ExportFinalToFile(filename) - // if ok... - if err == nil { - msg := fmt.Sprintf("value exported to %s", filename) - tui.Tell("error", msg) - tui.Log("trace", msg) - } - } - - case "update": - err = F.updateFromArgsAndContext(args, context) - - case "set.value": - F.setThinking(true, "final") - defer F.setThinking(false, "final") - context["target"] = "value" - err = F.updateFromArgsAndContext(args, context) - case "set.scope": - F.setThinking(true, "scope") - defer F.setThinking(false, "scope") - context["target"] = "scope" - err = F.updateFromArgsAndContext(args, context) - - default: - handled = false - // err = fmt.Errorf("unknown command %q", action) - } - - return handled, err -} - -func (F *Flower) updateFromArgsAndContext(args[] string, context map[string]any) error { - // tui.Log("warn", fmt.Sprintf("Flower.updateHandler.1: %v %v", args, context)) - // get source, defaults to empty, new runtime? - source := "" - if _source, ok := context["source"]; ok { - source = _source.(string) - } - - target := "value" - if _target, ok := context["target"]; ok { - target = _target.(string) - } - - // setup our source config - srcCfg := &helpers.SourceConfig{ - Args: args, - } - - // special case, source will be empty when the args are all cue entrypoints - // we want to... - // (1) catch special empty case for new play - // (2) we want different defaults for empty when there are args, based on the target - // for (1), we need temporary to know we are in new play mode - if len(args) == 0 || (len(args) == 1 && args[0] == "new") { - source = "" // very temporary setting - target = "value" - } - - tui.Log("warn", fmt.Sprintf("Flower.updateHandler.2: %v %v %v", source, target, srcCfg)) - - switch target { - case "value": - // local source default, assume it was a filename - if source == "" { - source = "file" - } else if source == "" { - source = "" - } - srcCfg.Source = helpers.EvalSource(source) - - // tui.Log("warn", fmt.Sprintf("Flower.updateHandler.3.V: %v", srcCfg)) - // tui.Log("extra", fmt.Sprintf("Eval.playItem.value: %v", srcCfg )) - // C.UseScope(false) - // need to get the text once at startup - txt, err := srcCfg.GetText() - if err != nil { - tui.Log("error", err) - return err - } - // tui.Log("extra", fmt.Sprintf("Eval.playItem.value.text: %v", txt )) - F.edit.SetText(txt, false) - - case "scope": - if source == "" { - source = "runtime" - } - srcCfg.Source = helpers.EvalSource(source) - - // tui.Log("warn", fmt.Sprintf("Flower.updateHandler.3.S: %v", srcCfg)) - F.scope.config = srcCfg - F.scope.viewer.SetSourceConfig(srcCfg) - } - - return F.Rebuild() -} - -func (F *Flower) ExportFinalToFile(filename string) (error) { - ext := filepath.Ext(filename) - ext = strings.TrimPrefix(ext, ".") - src, err := F.final.viewer.GetValueText(ext) - if err != nil { - return err - } - - dir := filepath.Dir(filename) - err = yagu.Mkdir(dir) - if err != nil { - return err - } - - return os.WriteFile(filename, []byte(src), 0644) -} diff --git a/lib/tui/components/cue/helpers/source-config.go b/lib/tui/components/cue/helpers/source-config.go index 6d1cf7ecd..04f948485 100644 --- a/lib/tui/components/cue/helpers/source-config.go +++ b/lib/tui/components/cue/helpers/source-config.go @@ -13,13 +13,13 @@ import ( type EvalSource string const ( - EvalNone = "" - EvalRuntime = "runtime" - EvalText = "text" - EvalFile = "file" - EvalBash = "bash" - EvalHttp = "http" - EvalConn = "conn" + EvalNone EvalSource = "" + EvalRuntime EvalSource = "runtime" + EvalText EvalSource = "text" + EvalFile EvalSource = "file" + EvalBash EvalSource = "bash" + EvalHttp EvalSource = "http" + EvalConn EvalSource = "conn" ) type SourceConfig struct { diff --git a/lib/tui/components/cue/playground/codec.go b/lib/tui/components/cue/playground/codec.go index 1793ce666..f726d00ef 100644 --- a/lib/tui/components/cue/playground/codec.go +++ b/lib/tui/components/cue/playground/codec.go @@ -15,6 +15,7 @@ func (C *Playground) Encode() (map[string]any, error) { "typename": C.TypeName(), "useScope": C.useScope, "seeScope": C.seeScope, + "mode": C.mode, "text": C.edit.GetText(), "direction": C.GetDirection(), } @@ -102,6 +103,12 @@ func (C *Playground) Decode(input map[string]any) (widget.Widget, error) { } w.seeScope = s.(bool) + s, ok = input["mode"] + if !ok { + return nil, fmt.Errorf("'mode' missing when calling playground.Decode: %#v", input) + } + w.mode = PlayMode(s.(string)) + w.SetDirection(input["direction"].(int)) if useScope { diff --git a/lib/tui/components/cue/playground/events.go b/lib/tui/components/cue/playground/events.go index 920565d97..b580ddaa5 100644 --- a/lib/tui/components/cue/playground/events.go +++ b/lib/tui/components/cue/playground/events.go @@ -34,6 +34,13 @@ func (C *Playground) setupKeybinds() { } C.Rebuild() + case 'E': + C.mode = ModeEval + C.Rebuild() + case 'W': + C.mode = ModeFlow + C.Rebuild() + case 'R': C.scope.Rebuild() diff --git a/lib/tui/components/cue/playground/flow.go b/lib/tui/components/cue/playground/flow.go new file mode 100644 index 000000000..e9843ecf3 --- /dev/null +++ b/lib/tui/components/cue/playground/flow.go @@ -0,0 +1,38 @@ +package playground + +import ( + "bytes" + + "cuelang.org/go/cue" + + "github.com/hofstadter-io/hof/cmd/hof/flags" + flowcontext "github.com/hofstadter-io/hof/flow/context" + "github.com/hofstadter-io/hof/flow/middleware" + "github.com/hofstadter-io/hof/flow/tasks" + "github.com/hofstadter-io/hof/flow/flow" +) + + +func (C *Playground) runFlow(val cue.Value) (cue.Value, error) { + var stdin, stdout, stderr bytes.Buffer + + ctx := flowcontext.New() + ctx.RootValue = val + ctx.Stdin = &stdin + ctx.Stdout = &stdout + ctx.Stderr = &stderr + + // how to inject tags into original value + // fill / return value + middleware.UseDefaults(ctx, flags.RootPflagpole{}, flags.FlowPflagpole{}) + tasks.RegisterDefaults(ctx) + + f, err := flow.OldFlow(ctx, val) + if err != nil { + return val, err + } + + err = f.Start() + + return f.Final, err +} diff --git a/lib/tui/components/cue/playground/handle-action.go b/lib/tui/components/cue/playground/handle-action.go index 299ce3983..30c9c80be 100644 --- a/lib/tui/components/cue/playground/handle-action.go +++ b/lib/tui/components/cue/playground/handle-action.go @@ -85,6 +85,13 @@ func (C *Playground) HandleAction(action string, args []string, context map[stri C.scope.Rebuild() C.Rebuild() + case "flow.clear": + C.mode = ModeEval + C.Rebuild() + case "flow.run": + C.mode = ModeFlow + C.Rebuild() + default: handled = false // err = fmt.Errorf("unknown command %q", action) diff --git a/lib/tui/components/cue/playground/playground.go b/lib/tui/components/cue/playground/playground.go index 5d431ce9a..da62822ef 100644 --- a/lib/tui/components/cue/playground/playground.go +++ b/lib/tui/components/cue/playground/playground.go @@ -4,19 +4,18 @@ import ( "fmt" "time" - "cuelang.org/go/cue" - "github.com/hofstadter-io/hof/lib/tui/components/cue/browser" "github.com/hofstadter-io/hof/lib/tui/components/cue/helpers" "github.com/hofstadter-io/hof/lib/tui/tview" "github.com/hofstadter-io/hof/lib/watch" ) -type valPack struct { - config *helpers.SourceConfig - value cue.Value +type PlayMode string -} +const ( + ModeEval PlayMode = "eval" + ModeFlow PlayMode = "flow" +) type Playground struct { // *tview.Frame eventually? @@ -31,6 +30,8 @@ type Playground struct { edit *tview.TextArea // text editCfg *helpers.SourceConfig + mode PlayMode + // the final value final *browser.Browser // scope @@ -48,6 +49,7 @@ func New(initialText string) (*Playground) { C := &Playground{ Flex: tview.NewFlex(), debounceTime: time.Millisecond * 500, + mode: ModeEval, } // our wrapper around the CUE widgets @@ -104,6 +106,9 @@ func (C *Playground) UseScope(use bool) { func (C *Playground) ToggleShowScope() { C.seeScope = !C.seeScope +} + +func (C *Playground) RebuildEditTitle() { // when not showing scope and has scope // display in editory text s := "" @@ -112,7 +117,7 @@ func (C *Playground) ToggleShowScope() { s += fmt.Sprintf("[violet](srcs:%d)[-] ", l) } } - C.edit.SetTitle(fmt.Sprintf(" %sexpression(s) ", s)) + C.edit.SetTitle(fmt.Sprintf(" [gold]%s[-] %sexpr(s) ", C.mode, s)) } func (C *Playground) SetFlexDirection(dir int) { diff --git a/lib/tui/components/cue/playground/rebuild.go b/lib/tui/components/cue/playground/rebuild.go index a0c5cf942..2b29c1b4f 100644 --- a/lib/tui/components/cue/playground/rebuild.go +++ b/lib/tui/components/cue/playground/rebuild.go @@ -56,35 +56,62 @@ func (C *Playground) Rebuild() error { // user code that will be evaluated src := C.edit.GetText() - C.setThinking(true, "final") - defer C.setThinking(false, "final") - - // compile a value - sv := C.scope.GetValue() - if C.useScope && sv.Exists() { - ctx := sv.Context() - v = ctx.CompileString(src, cue.InferBuiltins(true), cue.Scope(sv)) - } else { - // just compile the text - ctx := singletons.CueContext() - v = ctx.CompileString(src, cue.InferBuiltins(true)) - } - - // make a new config with the latest value for the output - cfg := &helpers.SourceConfig{Value: v} - if err != nil { - tui.Log("error", err) - cfg = &helpers.SourceConfig{Text: err.Error()} - } - // only update view value, that way, if we erase everything, we still see the value - C.final.ClearSourceConfigs() - C.final.AddSourceConfig(cfg) - C.final.SetUsingScope(C.useScope) - C.final.RebuildValue() - C.final.Rebuild() - + go func() { + + defer C.setThinking(false, "final") + + // compile a value + sv := C.scope.GetValue() + if C.useScope && sv.Exists() { + ctx := sv.Context() + v = ctx.CompileString(src, cue.InferBuiltins(true), cue.Scope(sv)) + } else { + // just compile the text + ctx := singletons.CueContext() + v = ctx.CompileString(src, cue.InferBuiltins(true)) + } + + var cfg *helpers.SourceConfig + // make a new config with the latest value for the output + cfg = &helpers.SourceConfig{Value: v} + // only update view value, that way, if we erase everything, we still see the value + + + if C.mode == ModeFlow { + // first has to pass basic CUE checks so that errors look the same + err = v.Validate() + if err == nil { + tui.Log("trace", "got here") + // then we try to run the flow + // we need a special way to deal with errors here + v, err = C.runFlow(v) + if err != nil { + tui.Log("error", err) + cfg = &helpers.SourceConfig{Text: err.Error()} + C.final.SetMode("text") + } else { + cfg.Value = v + } + } + + } + + if err == nil && C.final.GetMode() == "text" { + C.final.SetMode("cue") + } + + C.final.ClearSourceConfigs() + C.final.AddSourceConfig(cfg) + C.final.RebuildValue() + C.final.Rebuild() + + C.RebuildEditTitle() + + tui.Draw() + }() + tui.Draw() return nil diff --git a/lib/tui/modules/eval/argparse.go b/lib/tui/modules/eval/argparse.go index 684aabfad..40c0ec9fe 100644 --- a/lib/tui/modules/eval/argparse.go +++ b/lib/tui/modules/eval/argparse.go @@ -104,7 +104,8 @@ func enrichContext(context map[string]any) (map[string]any) { // in-item commands (these are for scope, but could be more) case "add", - "set": + "set", + "clear": context["action"] = tok maybeActionTarget() diff --git a/lib/tui/modules/eval/help.go b/lib/tui/modules/eval/help.go index 3ed6f93bf..162676e3e 100644 --- a/lib/tui/modules/eval/help.go +++ b/lib/tui/modules/eval/help.go @@ -12,9 +12,9 @@ const EvalHelpText = ` Create dashboards of these views, playgrounds, and chains and then save, load, or share them with others. - Currently there is support for [violet]eval[-] and [violet]flow[-](basic). + Currently there is support for [blue]hof/eval[-] and [blue]hof/flow[-]. New releases will expand core feature support in this tui, - notably [violet]datamodel[-], [violet]gen[-], and [violet]vet[-] are upcoming. + notably [blue]datamodel[-], [blue]gen[-], and [blue]vet[-] are upcoming. [dodgerblue::bu]Contents:[darkgray::-] @@ -86,16 +86,20 @@ const EvalHelpText = ` [gold::b]Playground[-::-] is a multi-widget Item for working with CUE. You can edit CUE and see the results in real-time, with optional scope. + There are also two modes: [blue]hof/eval[-] (default) and [blue]hof/flow[-]. + The widgets in the playground are: - [darkgray]1. [gold]viewer[-] for the [lightseagreen]scope[-] (if available) - [darkgray]2. [gold]editor[-] for the [lightseagreen]main[-] value - [darkgray]3. [gold]viewer[-] for the [lightseagreen]final[-] value + [darkgray]1. [gold]viewer[-] for the [lightseagreen]scope[-] + [darkgray]2. [gold]editor[-] for the [lightseagreen]main[-] + [darkgray]3. [gold]viewer[-] for the [lightseagreen]final[-] [blue::bu]Hotkeys[-::-] [lime]Alt-f[-] [lightseagreen]Rotate[-] this item (lowercase of Panel rotate hotkey) [lime]Alt-R[-] [lightseagreen]Reload[-] data source and refresh [lime]Alt-s[-] [lightseagreen]Toggle[-] toggle the scope viewer [lime]Alt-S[-] [lightseagreen]Toggle[-] toggle the scope usage + [lime]Alt-E[-] [lightseagreen]Toggle[-] set to eval mode ([blue]hof/eval[-],default) + [lime]Alt-W[-] [lightseagreen]Toggle[-] set to flow mode ([blue]hof/flow[-]) [blue::bu]Commands[-::-] [violet]play [lightseagreen] [-]create a new, empty playground @@ -105,7 +109,7 @@ const EvalHelpText = ` [violet]push [lightseagreen] [-]push the current edit text to cue/play, link returned [violet]play scope [lightseagreen]test.cue [-]create new play with scope from file [violet]play scope [lightseagreen]./ [-]create new play with scope from dir - [violet]play scope [lightseagreen] [-]create new play with scope from eval args & flags + [violet]play scope [lightseagreen]bash or https://... [-]create new play with any of the data sources [violet]play scope [lightseagreen] [-]create new play with scope from eval args & flags There are many more commands for working with widgets, primarily [gold]play[-]. diff --git a/lib/tui/tview/textview.go b/lib/tui/tview/textview.go index 5b3b6e0c4..e345305d8 100644 --- a/lib/tui/tview/textview.go +++ b/lib/tui/tview/textview.go @@ -1341,29 +1341,29 @@ func (t *TextView) MouseHandler() func(action MouseAction, event *tcell.EventMou case MouseLeftDown: setFocus(t) consumed = true - case MouseLeftClick: - if t.regionTags { - // Find a region to highlight. - rectX, rectY, _, _ := t.GetInnerRect() - x -= rectX - y -= rectY - var highlightedID string - if yt := y+t.lineOffset; yt < len(t.lineIndex) { - line := t.lineIndex[yt] - for regionID, fromTo := range line.regions { - if x >= fromTo[0] && x < fromTo[1] { - highlightedID = regionID - break - } - } - } - if highlightedID != "" { - t.Highlight(highlightedID) - } else if !t.toggleHighlights { - t.Highlight() - } - } - consumed = true + //case MouseLeftClick: + // if t.regionTags { + // // Find a region to highlight. + // rectX, rectY, _, _ := t.GetInnerRect() + // x -= rectX + // y -= rectY + // var highlightedID string + // if yt := y+t.lineOffset; yt < len(t.lineIndex) { + // line := t.lineIndex[yt] + // for regionID, fromTo := range line.regions { + // if x >= fromTo[0] && x < fromTo[1] { + // highlightedID = regionID + // break + // } + // } + // } + // if highlightedID != "" { + // t.Highlight(highlightedID) + // } else if !t.toggleHighlights { + // t.Highlight() + // } + // } + // consumed = true case MouseScrollUp: t.trackEnd = false t.lineOffset -= 6