From c9083ea6ab0981eed8c960bddc7771589851688f Mon Sep 17 00:00:00 2001 From: "Masci, Richard (rx7322)" Date: Fri, 11 Jun 2021 16:47:45 -0400 Subject: [PATCH 1/3] Created package out of application. New application included. --- .idea/.gitignore | 8 + .idea/jsonui.iml | 9 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + CHANGELOG.md | 4 + README.md | 31 ++- jsonui-cmd/Makefile | 33 +++ jsonui-cmd/main.go | 40 +++ jsonuipkg.go | 390 ++++++++++++++++++++++++++++++ jsonui.go => orig/jsonui.go | 0 tree_test.go => orig/tree_test.go | 0 tree.go | 2 +- tree_search_test.go | 2 +- 13 files changed, 530 insertions(+), 3 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/jsonui.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 jsonui-cmd/Makefile create mode 100644 jsonui-cmd/main.go create mode 100644 jsonuipkg.go rename jsonui.go => orig/jsonui.go (100%) rename tree_test.go => orig/tree_test.go (100%) diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/jsonui.iml b/.idea/jsonui.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/jsonui.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b089683 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa64f0..f815124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.2.0 2021.06.11 + - Moved cmd to jsonui-cmd + - Made package out of jsonui, enabling others with json files to use + Easy: just run jsonui.Interactive([]byte with json.) ## 1.0.1 2018.09.15 - fixes `null` rendering - adds binary builds diff --git a/README.md b/README.md index 9d28355..2888139 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,36 @@ ![](img/jsonui.gif) +## Import into your application +jsonui can now be imported into your application -- +```bigquery +jsonBytes,err := ioutil.ReadFile("jsonfile.json") +errorhandle(err) +jsonui.Interactive(jsonBytes) +``` + +When it runs, scroll down to the section, press x to execute. The output will look like this: +``` +] $ cat test.json | jsonui +JSON Path: address.gateways +``` +And then the user will be able to extract json using another utility called 'jj' +```bigquery +]$ cat test.json | jj address.gateways +["Sopron", "Vienna", "Budapest"] +``` ## Install +This Version: +`go get -u github.com/rmasci/jsonui` + +Original: `go get -u github.com/gulyasm/jsonui` ## Binary Releases +This Version: +[Binary releases are availabe](https://github.com/rmasci/jsonui/releases) + +Original: [Binary releases are availabe](https://github.com/gulyasm/jsonui/releases) ## Usage @@ -42,11 +68,14 @@ Expand all nodes #### `C` Collapse all nodes - +### `x` +Execute. Will give you the JSON Path to use with jj command. +[https://github.com/tidwall/jj](https://github.com/tidwall/jj) #### `q/Ctrl+C` Quit jsonui + ## Acknowledgments Special thanks for [asciimoo](https://github.com/asciimoo) and the [wuzz](https://github.com/asciimoo/wuzz) project for all the help and suggestions. diff --git a/jsonui-cmd/Makefile b/jsonui-cmd/Makefile new file mode 100644 index 0000000..6bed02c --- /dev/null +++ b/jsonui-cmd/Makefile @@ -0,0 +1,33 @@ +compilerFlag=-gcflags=-trimpath=$(shell pwd) -asmflags=-trimpath=$(shell pwd) +goFiles=main.go +gomod=off +all: mac linux windows freebsd netbsd openbsd pi + +windows:$(goFiles) + GO111MODULE=$(gomod) GOOS=windows GOARCH=386 go build $(compilerFlag) -o ../../jsonui-release/win32-jsonui.exe $(goFiles) + GO111MODULE=$(gomod) GOOS=windows GOARCH=amd64 go build $(compilerFlag) -o ../../jsonui-release/win64-jsonui.exe $(goFiles) + +linux: $(goFiles) + GO111MODULE=$(gomod) GOOS=linux GOARCH=386 go build $(compilerFlag) -o ../../jsonui-release/lin32-jsonui $(goFiles) + GO111MODULE=$(gomod) GOOS=linux GOARCH=amd64 go build $(compilerFlag) -o ../../jsonui-release/lin64-jsonui $(goFiles) + +pi: $(gofiles) + GO111MODULE=$(gomod) GOOS=linux GOARCH=arm GOARM=6 go build $(compilerFlag) -o ../../jsonui-release/jsonui-pi $(goFilesU) + +mac: + GO111MODULE=$(gomod) GOOS=darwin GOARCH=amd64 go build $(compilerFlag) -o ../../jsonui-release/mac-jsonui $(goFiles) + +freebsd: $(goFiles) + GO111MODULE=$(gomod) GOOS=freebsd GOARCH=386 go build $(compilerFlag) -o ../../jsonui-release/freebsd32-jsonui $(goFiles) + GO111MODULE=$(gomod) GOOS=freebsd GOARCH=amd64 go build $(compilerFlag) -o ../../jsonui-release/freebsd64-jsonui $(goFiles) + +netbsd: $(goFiles) + GO111MODULE=$(gomod) GOOS=freebsd GOARCH=386 go build $(compilerFlag) -o ../../jsonui-release/netbsd32-jsonui $(goFiles) + GO111MODULE=$(gomod) GOOS=freebsd GOARCH=amd64 go build $(compilerFlag) -o ../../jsonui-release/netbsd64-jsonui $(goFiles) + +openbsd: $(goFiles) + GO111MODULE=$(gomod) GOOS=openbsd GOARCH=386 go build $(compilerFlag) -o ../../jsonui-release/openbsd32-jsonui $(goFiles) + GO111MODULE=$(gomod) GOOS=openbsd GOARCH=amd64 go build $(compilerFlag) -o ../../jsonui-release/openbsd64-jsonui $(goFiles) + +solaris: $(goFiles) + GO111MODULE=$(gomod) GOOS=solaris GOARCH=amd64 go build $(compilerFlag) -o ../../jsonui-release/sol-jsonui $(goFiles) \ No newline at end of file diff --git a/jsonui-cmd/main.go b/jsonui-cmd/main.go new file mode 100644 index 0000000..121ff1b --- /dev/null +++ b/jsonui-cmd/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/rmasci/jsonui" + "github.com/spf13/pflag" +) + +func main() { + var inFile string + var jsonByte []byte + var err error + pflag.StringVarP(&inFile, "file", "f", "", "JSON File to parse") + pflag.Parse() + if inFile != "" { + jsonByte, err = ioutil.ReadFile(inFile) + if err != nil { + fmt.Println("Could not read file", err) + os.Exit(1) + } + } else { + stat, _ := os.Stdin.Stat() + if (stat.Mode() & os.ModeCharDevice) == 0 { + jsonByte, err = ioutil.ReadAll(os.Stdin) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + } + if len(jsonByte) <= 1 { + fmt.Println("No Data") + os.Exit(1) + } + jp := jsonui.Interactive(jsonByte) + fmt.Println("JSON Path:", jp) +} diff --git a/jsonuipkg.go b/jsonuipkg.go new file mode 100644 index 0000000..ccadab0 --- /dev/null +++ b/jsonuipkg.go @@ -0,0 +1,390 @@ +package jsonui + +// Most of the code written here is from a program called jsonui https://github.com/gulyasm/jsonui -- I did not write this +// but modified it to work in xjconvert. -- rx7322. + +import ( + "fmt" + "io/ioutil" + "log" + "math" + "strings" + + "github.com/jroimartin/gocui" +) + +//const VERSION = "1.0.1" + +const ( + treeView = "Hit 'h' for help, 'x' execute." + textView = "text" + pathView = "path" + helpView = "help" +) + +type position struct { + prc float32 + margin int +} + +func (p position) getCoordinate(max int) int { + // value = prc * MAX + abs + return int(p.prc*float32(max)) - p.margin +} + +type viewPosition struct { + x0, y0, x1, y1 position +} + +func logFile(s string) error { + d1 := []byte(s + "\n") + return ioutil.WriteFile("log.txt", d1, 0644) +} + +func (vp viewPosition) getCoordinates(maxX, maxY int) (int, int, int, int) { + var x0 = vp.x0.getCoordinate(maxX) + var y0 = vp.y0.getCoordinate(maxY) + var x1 = vp.x1.getCoordinate(maxX) + var y1 = vp.y1.getCoordinate(maxY) + return x0, y0, x1, y1 +} + +var helpWindowToggle = false + +var viewPositions = map[string]viewPosition{ + treeView: { + position{0.0, 0}, + position{0.0, 0}, + position{0.3, 2}, + position{0.9, 2}, + }, + textView: { + position{0.3, 0}, + position{0.0, 0}, + position{1.0, 2}, + position{0.9, 2}, + }, + pathView: { + position{0.0, 0}, + position{0.89, 0}, + position{1.0, 2}, + position{1.0, 2}, + }, +} + +var tree treeNode + +func Interactive(jsonBod []byte) (jp string) { + jp = startInteract(jsonBod) + + //fmt.Printf("jsonPath=%v\n", jp) + //fmt.Printf("JSONPath: %v\n", jp) + return jp +} +func startInteract(jsonBod []byte) (jp string) { + var err error + tree, err = fromBytes(jsonBod) + if err != nil { + log.Panicln(err) + } + g, err := gocui.NewGui(gocui.OutputNormal) + if err != nil { + log.Panicln(err) + } + defer g.Close() + + g.SetManagerFunc(layout) + + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding("", 'x', gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding(treeView, 'k', gocui.ModNone, cursorMovement(-1)); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, 'j', gocui.ModNone, cursorMovement(1)); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, gocui.KeyArrowUp, gocui.ModNone, cursorMovement(-1)); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, gocui.KeyArrowDown, gocui.ModNone, cursorMovement(1)); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, 'K', gocui.ModNone, cursorMovement(-15)); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, 'J', gocui.ModNone, cursorMovement(15)); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, gocui.KeyPgup, gocui.ModNone, cursorMovement(-15)); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, gocui.KeyPgdn, gocui.ModNone, cursorMovement(15)); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, 'e', gocui.ModNone, toggleExpand); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, 'E', gocui.ModNone, expandAll); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding(treeView, 'C', gocui.ModNone, collapseAll); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding("", 'h', gocui.ModNone, toggleHelp); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding("", '?', gocui.ModNone, toggleHelp); err != nil { + log.Panicln(err) + } + g.SelFgColor = gocui.ColorBlack + g.SelBgColor = gocui.ColorGreen + + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + log.Panicln(err) + } + p := findTreePosition(g) + for i, j := range p { + j = strings.TrimLeft(j, "[") + j = strings.TrimRight(j, "]") + if i == 0 { + jp = j + } else { + jp = jp + "." + j + } + } + log.Printf("Returning jp: %v\n", jp) + return jp +} + +const helpMessage = ` +Interactive - Help +---------------------------------------------- +j/ArrowDown ═ Move a line down +k/ArrowUp ═ Move a line up +J/PageDown ═ Move 15 line down +K/PageUp ═ Move 15 line up +e ═ Toggle expend/collapse node +E ═ Expand all nodes +C ═ Collapse all nodes +x/ctrl+c ═ Execute +h/? ═ Toggle help message +` + +func layout(g *gocui.Gui) error { + var views = []string{treeView, textView, pathView} + maxX, maxY := g.Size() + for _, view := range views { + x0, y0, x1, y1 := viewPositions[view].getCoordinates(maxX, maxY) + if v, err := g.SetView(view, x0, y0, x1, y1); err != nil { + v.SelFgColor = gocui.ColorBlack + v.SelBgColor = gocui.ColorGreen + + v.Title = " " + view + " " + if err != gocui.ErrUnknownView { + return err + + } + if v.Name() == treeView { + v.Highlight = true + drawTree(g, v, tree) + // v.Autoscroll = true + } + if v.Name() == textView { + drawJSON(g, v) + } + + } + } + if helpWindowToggle { + height := strings.Count(helpMessage, "\n") + 1 + width := -1 + for _, line := range strings.Split(helpMessage, "\n") { + width = int(math.Max(float64(width), float64(len(line)+2))) + } + if v, err := g.SetView(helpView, maxX/2-width/2, maxY/2-height/2, maxX/2+width/2, maxY/2+height/2); err != nil { + if err != gocui.ErrUnknownView { + return err + + } + fmt.Fprintln(v, helpMessage) + + } + } else { + g.DeleteView(helpView) + } + _, err := g.SetCurrentView(treeView) + if err != nil { + log.Fatal("failed to set current view: ", err) + } + return nil + +} +func getPath(g *gocui.Gui, v *gocui.View) string { + p := findTreePosition(g) + for i, s := range p { + transformed := s + if !strings.HasPrefix(s, "[") && !strings.HasSuffix(s, "]") { + transformed = fmt.Sprintf("[%q]", s) + } + p[i] = transformed + } + return strings.Join(p, "") +} + +func drawPath(g *gocui.Gui, v *gocui.View) error { + pv, err := g.View(pathView) + if err != nil { + log.Fatal("failed to get pathView", err) + } + p := getPath(g, v) + pv.Clear() + fmt.Fprintf(pv, p) + return nil +} +func drawJSON(g *gocui.Gui, v *gocui.View) error { + dv, err := g.View(textView) + if err != nil { + log.Fatal("failed to get textView", err) + } + p := findTreePosition(g) + treeTodraw := tree.find(p) + if treeTodraw != nil { + dv.Clear() + fmt.Fprintf(dv, treeTodraw.String(2, 0)) + } + return nil +} + +func lineBelow(v *gocui.View, d int) bool { + _, y := v.Cursor() + line, err := v.Line(y + d) + return err == nil && line != "" +} + +func countIndex(s string) int { + count := 0 + for _, c := range s { + if c == ' ' { + count++ + } + } + return count +} + +func getLine(s string, y int) string { + lines := strings.Split(s, "\n") + return lines[y] +} + +var cleanPatterns = []string{ + treeSignUpEnding, + treeSignDash, + treeSignUpMiddle, + treeSignVertical, + " (+)", +} + +func findTreePosition(g *gocui.Gui) treePosition { + v, err := g.View(treeView) + if err != nil { + log.Fatal("failed to get treeview", err) + } + path := treePosition{} + ci := -1 + _, yOffset := v.Origin() + _, yCurrent := v.Cursor() + y := yOffset + yCurrent + s := v.Buffer() + for cy := y; cy >= 0; cy-- { + line := getLine(s, cy) + for _, pattern := range cleanPatterns { + line = strings.Replace(line, pattern, "", -1) + } + + if count := countIndex(line); count < ci || ci == -1 { + path = append(path, strings.TrimSpace(line)) + ci = count + } + } + for i := len(path)/2 - 1; i >= 0; i-- { + opp := len(path) - 1 - i + path[i], path[opp] = path[opp], path[i] + } + + return path[1:] +} + +// This is a workaround for not having a Buffer +// function in gocui +func bufferLen(v *gocui.View) int { + s := v.Buffer() + return len(strings.Split(s, "\n")) - 1 +} + +func drawTree(g *gocui.Gui, v *gocui.View, tree treeNode) error { + tv, err := g.View(treeView) + if err != nil { + log.Fatal("failed to get treeView", err) + } + tv.Clear() + tree.draw(tv, 2, 0) + maxY := bufferLen(tv) + cx, cy := tv.Cursor() + lastLine := maxY - 2 + if cy > lastLine { + tv.SetCursor(cx, lastLine) + tv.SetOrigin(0, 0) + } + + return nil +} + +func expandAll(g *gocui.Gui, v *gocui.View) error { + tree.expandAll() + return drawTree(g, v, tree) +} + +func collapseAll(g *gocui.Gui, v *gocui.View) error { + tree.collapseAll() + return drawTree(g, v, tree) +} + +func toggleExpand(g *gocui.Gui, v *gocui.View) error { + p := findTreePosition(g) + subTree := tree.find(p) + subTree.toggleExpanded() + return drawTree(g, v, tree) +} + +func cursorMovement(d int) func(g *gocui.Gui, v *gocui.View) error { + return func(g *gocui.Gui, v *gocui.View) error { + dir := 1 + if d < 0 { + dir = -1 + } + distance := int(math.Abs(float64(d))) + for ; distance > 0; distance-- { + if lineBelow(v, distance*dir) { + v.MoveCursor(0, distance*dir, false) + drawJSON(g, v) + drawPath(g, v) + return nil + } + } + return nil + } +} +func toggleHelp(g *gocui.Gui, v *gocui.View) error { + helpWindowToggle = !helpWindowToggle + return nil +} + +func quit(g *gocui.Gui, v *gocui.View) error { + + return gocui.ErrQuit +} diff --git a/jsonui.go b/orig/jsonui.go similarity index 100% rename from jsonui.go rename to orig/jsonui.go diff --git a/tree_test.go b/orig/tree_test.go similarity index 100% rename from tree_test.go rename to orig/tree_test.go diff --git a/tree.go b/tree.go index 2515a11..d60151c 100644 --- a/tree.go +++ b/tree.go @@ -1,4 +1,4 @@ -package main +package jsonui import ( "encoding/json" diff --git a/tree_search_test.go b/tree_search_test.go index 6c53d12..24d24e4 100644 --- a/tree_search_test.go +++ b/tree_search_test.go @@ -1,4 +1,4 @@ -package main +package jsonui import "testing" From 1e971ba6e1bf2c0239cc234290e9a61616d21803 Mon Sep 17 00:00:00 2001 From: "Masci, Richard (rx7322)" Date: Fri, 11 Jun 2021 16:57:56 -0400 Subject: [PATCH 2/3] Added changes to README --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2888139..e735755 100644 --- a/README.md +++ b/README.md @@ -7,25 +7,31 @@ ## Import into your application jsonui can now be imported into your application -- -```bigquery +``` +... jsonBytes,err := ioutil.ReadFile("jsonfile.json") errorhandle(err) -jsonui.Interactive(jsonBytes) +jsonpath:=jsonui.Interactive(jsonBytes) +fmt.Println(jsonpath) +... ``` - When it runs, scroll down to the section, press x to execute. The output will look like this: ``` ] $ cat test.json | jsonui JSON Path: address.gateways ``` And then the user will be able to extract json using another utility called 'jj' -```bigquery +``` ]$ cat test.json | jj address.gateways ["Sopron", "Vienna", "Budapest"] ``` + +You could also use the jsonpath variable above for other packages like gjson or sjson to get / set values. ## Install This Version: -`go get -u github.com/rmasci/jsonui` +`git clone https://github.com/rmasci/jsonui.git` +cd jsonui/jsonui-cmd +make Original: `go get -u github.com/gulyasm/jsonui` From a8569b64df5cdd7138fdc41742bed573e63c35bc Mon Sep 17 00:00:00 2001 From: RM Date: Wed, 23 Jun 2021 11:09:54 -0400 Subject: [PATCH 3/3] Update jsonuipkg.go Added a comment for godoc --- jsonuipkg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsonuipkg.go b/jsonuipkg.go index ccadab0..0bc507e 100644 --- a/jsonuipkg.go +++ b/jsonuipkg.go @@ -73,7 +73,7 @@ var viewPositions = map[string]viewPosition{ } var tree treeNode - +// Very simple to use, just call jsonui.Interactive([]jsonbytes) and you enter in to interactive mode. The return is the location the user selected. From here the user can then use gjson to pull the value, display it to the user, or use it for other purposes. func Interactive(jsonBod []byte) (jp string) { jp = startInteract(jsonBod)