pax_global_header00006660000000000000000000000064134136147360014522gustar00rootroot0000000000000052 comment=ab019bb2dc1c2cb1e134059107db723edac8bdbe jid-0.7.3/000077500000000000000000000000001341361473600122775ustar00rootroot00000000000000jid-0.7.3/.circleci/000077500000000000000000000000001341361473600141325ustar00rootroot00000000000000jid-0.7.3/.circleci/config.yml000066400000000000000000000013101341361473600161150ustar00rootroot00000000000000version: 2 jobs: test: docker: - image: circleci/golang:latest steps: - checkout - run: go test -v ./ - run: go test -v ./cmd/jid release: docker: - image: circleci/golang:latest steps: - checkout - run: name: build jid using goreleaser command: curl -sL https://git.io/goreleaser | bash workflows: version: 2 test_and_release: jobs: - test: filters: tags: only: /v[0-9]+(\.[0-9]+)*(-.*)*/ - release: requires: - test filters: branches: ignore: /.*/ tags: only: /v[0-9]+(\.[0-9]+)*(-.*)*/ jid-0.7.3/.gitignore000066400000000000000000000005021341361473600142640ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof *.swp *.test test.* # jid package jid *.out *.log dist jid-0.7.3/.goreleaser.yml000066400000000000000000000014651341361473600152360ustar00rootroot00000000000000# This is an example goreleaser.yaml file with some sane defaults. # Make sure to check the documentation at http://goreleaser.com before: hooks: # you may remove this if you don't use vgo - go mod download # you may remove this if you don't need go generate - go generate ./... builds: - env: - CGO_ENABLED=0 main: ./cmd/jid/jid.go ldflags: - -s -w goos: - windows - openbsd - netbsd - linux - freebsd - darwin goarch: - arm64 - amd64 - 386 archive: name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" format: zip files: - none* checksum: name_template: 'checksums.txt' snapshot: name_template: "{{ .Tag }}-next" changelog: sort: asc filters: exclude: - '^docs:' - '^test:' # for Validation #release: # disable: true jid-0.7.3/ChangeLog000066400000000000000000000012221341361473600140460ustar00rootroot00000000000000ChangeLog ======= ### 0.7.2 - Jan 23 2017 BugFix: * fix version number ### 0.7.1 - Jan 5 2017 Features: * scroll to bottom and top ### 0.7.0 - Jan 4 2017 Features: * output with color * monochrome mode option BugFix: * multibyte Query & Json key/value Modified README ### 0.6.3 - 15 Dec 2016 Features: * Add `-help` and `-h` command for show a help * Add Ctrl-U command and a query behavior Change Behaviors: * Force insert `.` ### 0.6.2 - 9 Dec 2016 Features: * Add --version flag for homebrew test ### 0.6.1 - 5 Dec 2016 Features: * Get first argument of `jid` for initial query Change Behaviors: * Auto input . at first character of query jid-0.7.3/LICENSE000066400000000000000000000020611341361473600133030ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 simeji Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jid-0.7.3/README.md000066400000000000000000000064471341361473600135710ustar00rootroot00000000000000# jid [![Circle CI](https://circleci.com/gh/simeji/jid/tree/master.svg?style=shield)](https://circleci.com/gh/simeji/jid/tree/master) Json Incremental Digger It's a very simple tool. You can drill down JSON interactively by using filtering queries like [jq](https://stedolan.github.io/jq/). **Suggestion** and **Auto completion** of this tool will provide you a very comfortable JSON drill down. ## Demo ![demo-jid-main](https://github.com/simeji/jid/wiki/images/demo-jid-main-640-colorize.gif) ## Installation * [With homebrew (for Mac)](#with-homebrew-for-mac) * [With pkg (for FreeBSD)](#with-pkg-for-freebsd) * [Simply use "jid" command](#simply-use-jid-command) * [Build](#build) ### With homebrew (for Mac) ``` brew install jid ``` ### With pkg (for FreeBSD) ``` pkg install jid ``` ### Simply use "jid" command If you simply want to use `jid` command, please download binary from below. https://github.com/simeji/jid/releases ## Build ``` go get -u github.com/simeji/jid/cmd/jid ``` ## Usage ### Quick start * [simple json example](#simple-json-example) * [simple json example2](#simple-json-example2) * [with initial query](#with-initial-query) * [with curl](#with-curl) #### simple json example Please execute the below command. ``` echo '{"aa":"2AA2","bb":{"aaa":[123,"cccc",[1,2]],"c":321}}'| jid ``` then, jid will be running. You can dig JSON data incrementally. When you enter `.bb.aaa[2]`, you will see the following. ``` [Filter]> .bb.aaa[2] [ 1, 2 ] ``` Then, you press Enter key and output `[1,2]` and exit. #### simple json example2 This json is used by [demo section](https://github.com/simeji/jid#demo). ``` echo '{"info":{"date":"2016-10-23","version":1.0},"users":[{"name":"simeji","uri":"https://github.com/simeji","id":1},{"name":"simeji2","uri":"https://example.com/simeji","id":2},{"name":"simeji3","uri":"https://example.com/simeji3","id":3}],"userCount":3}}'|jid ``` #### With a initial query First argument of `jid` is initial query. (Use JSON same as [Demo](#demo)) ![demo-jid-with-query](https://github.com/simeji/jid/wiki/images/demo-jid-with-query-640.gif) #### with curl Sample for using [RDAP](https://datatracker.ietf.org/wg/weirds/documents/) data. ``` curl -s http://rdg.afilias.info/rdap/domain/example.info | jid ``` #### Load JSON from a file ``` jid < file.json ``` ## Keymaps |key|description| |:-----------|:----------| |`TAB` / `CTRL` + `I` |Show available items and choice them| |`CTRL` + `W` |Delete from the cursor to the start of the word| |`CTRL` + `U` |Delete whole query| |`CTRL` + `F` / Right Arrow (:arrow_right:)|Move cursor a character to the right| |`CTRL` + `B` / Left Arrow (:arrow_left:)|Move cursor a character to the left| |`CTRL` + `A`|To the first character of the 'Filter'| |`CTRL` + `E`|To the end of the 'Filter'| |`CTRL` + `J`|Scroll json buffer 1 line downwards| |`CTRL` + `K`|Scroll json buffer 1 line upwards| |`CTRL` + `G`|Scroll json buffer to bottom| |`CTRL` + `T`|Scroll json buffer to top| |`CTRL` + `L`|Change view mode whole json or keys (only object)| |`ESC`|Hide a candidate box| ### Option |option|description| |:-----------|:----------| |First argument ($1) | Initial query| |-h | print a help| |-help | print a help| |-version | print the version and exit| |-q | Output query mode (for jq)| |-M | monochrome output mode| jid-0.7.3/cmd/000077500000000000000000000000001341361473600130425ustar00rootroot00000000000000jid-0.7.3/cmd/jid/000077500000000000000000000000001341361473600136105ustar00rootroot00000000000000jid-0.7.3/cmd/jid/jid.go000066400000000000000000000037401341361473600147110ustar00rootroot00000000000000package main import ( "flag" "fmt" "os" "github.com/simeji/jid" ) const VERSION = "0.7.3" func main() { content := os.Stdin var qm bool var help bool var version bool var mono bool var pretty bool qs := "." flag.BoolVar(&qm, "q", false, "Output query mode") flag.BoolVar(&help, "h", false, "print a help") flag.BoolVar(&help, "help", false, "print a help") flag.BoolVar(&version, "version", false, "print the version and exit") flag.BoolVar(&mono, "M", false, "monochrome output mode") flag.BoolVar(&pretty, "p", false, "pretty print json result") flag.Parse() if help { flag.Usage() fmt.Println(getHelpString()) os.Exit(0) } if version { fmt.Println(fmt.Sprintf("jid version v%s", VERSION)) os.Exit(0) } args := flag.Args() if len(args) > 0 { qs = args[0] } ea := &jid.EngineAttribute{ DefaultQuery: qs, Monochrome: mono, PrettyResult: pretty, } e, err := jid.NewEngine(content, ea) if err != nil { fmt.Println(err) os.Exit(1) } os.Exit(run(e, qm)) } func run(e jid.EngineInterface, qm bool) int { result := e.Run() if result.GetError() != nil { return 2 } if qm { fmt.Printf("%s", result.GetQueryString()) } else { fmt.Printf("%s", result.GetContent()) } return 0 } func getHelpString() string { return ` ============ Load JSON from a file ============== $ jid < file.json ============ With a JSON filter mode ============= TAB / CTRL-I Show available items and choice them CTRL-W Delete from the cursor to the start of the word CTRL-U Delete whole query CTRL-F / Right Arrow Move cursor a character to the right CTRL-B / Left Arrow Move cursor a character to the left CTRL-A To the first character of the 'Filter' CTRL-E To the end of the 'Filter' CTRL-J Scroll json buffer 1 line downwards CTRL-K Scroll json buffer 1 line upwards CTRL-G Scroll json buffer to bottom CTRL-T Scroll json buffer to top CTRL-L Change view mode whole json or keys (only object) ESC Hide a candidate box ` } jid-0.7.3/cmd/jid/jid_test.go000066400000000000000000000022341341361473600157450ustar00rootroot00000000000000package main import ( "fmt" "os" "testing" "github.com/simeji/jid" "github.com/stretchr/testify/assert" ) var called int = 0 func TestMain(m *testing.M) { called = 0 code := m.Run() defer os.Exit(code) } func TestJidRun(t *testing.T) { var assert = assert.New(t) e := &EngineMock{err: nil} result := run(e, false) assert.Zero(result) assert.Equal(2, called) result = run(e, true) assert.Equal(1, called) result = run(e, false) assert.Zero(result) } func TestJidRunWithError(t *testing.T) { called = 0 var assert = assert.New(t) e := &EngineMock{err: fmt.Errorf("")} result := run(e, false) assert.Equal(2, result) assert.Equal(0, called) } type EngineMock struct{ err error } func (e *EngineMock) Run() jid.EngineResultInterface { return &EngineResultMock{err: e.err} } func (e *EngineMock) GetQuery() jid.QueryInterface { return jid.NewQuery([]rune("")) } type EngineResultMock struct{ err error } func (e *EngineResultMock) GetQueryString() string { called = 1 return ".querystring" } func (e *EngineResultMock) GetContent() string { called = 2 return `{"test":"result"}` } func (e *EngineResultMock) GetError() error { return e.err } jid-0.7.3/engine.go000066400000000000000000000144731341361473600141040ustar00rootroot00000000000000package jid import ( "io" "strings" "github.com/nsf/termbox-go" ) const ( DefaultY int = 1 FilterPrompt string = "[Filter]> " ) type EngineInterface interface { Run() EngineResultInterface GetQuery() QueryInterface } type EngineResultInterface interface { GetQueryString() string GetContent() string GetError() error } type Engine struct { manager *JsonManager query QueryInterface queryCursorIdx int term *Terminal complete []string keymode bool candidates []string candidatemode bool candidateidx int contentOffset int queryConfirm bool prettyResult bool } type EngineAttribute struct { DefaultQuery string Monochrome bool PrettyResult bool } func NewEngine(s io.Reader, ea *EngineAttribute) (EngineInterface, error) { j, err := NewJsonManager(s) if err != nil { return nil, err } e := &Engine{ manager: j, term: NewTerminal(FilterPrompt, DefaultY, ea.Monochrome), query: NewQuery([]rune(ea.DefaultQuery)), complete: []string{"", ""}, keymode: false, candidates: []string{}, candidatemode: false, candidateidx: 0, contentOffset: 0, queryConfirm: false, prettyResult: ea.PrettyResult, } e.queryCursorIdx = e.query.Length() return e, nil } type EngineResult struct { content string qs string err error } func (er *EngineResult) GetQueryString() string { return er.qs } func (er *EngineResult) GetContent() string { return er.content } func (er *EngineResult) GetError() error { return er.err } func (e *Engine) GetQuery() QueryInterface { return e.query } func (e *Engine) Run() EngineResultInterface { err := termbox.Init() if err != nil { panic(err) } defer termbox.Close() var contents []string for { if e.query.StringGet() == "" { e.query.StringSet(".") e.queryCursorIdx = e.query.Length() } bl := len(contents) contents = e.getContents() e.setCandidateData() e.queryConfirm = false if bl != len(contents) { e.contentOffset = 0 } ta := &TerminalDrawAttributes{ Query: e.query.StringGet(), Contents: contents, CandidateIndex: e.candidateidx, ContentsOffsetY: e.contentOffset, Complete: e.complete[0], Candidates: e.candidates, CursorOffset: e.query.IndexOffset(e.queryCursorIdx), } err = e.term.Draw(ta) if err != nil { panic(err) } switch ev := termbox.PollEvent(); ev.Type { case termbox.EventKey: switch ev.Key { case 0: e.inputChar(ev.Ch) case termbox.KeyBackspace, termbox.KeyBackspace2: e.deleteChar() case termbox.KeyTab: e.tabAction() case termbox.KeyArrowLeft, termbox.KeyCtrlB: e.moveCursorBackward() case termbox.KeyArrowRight, termbox.KeyCtrlF: e.moveCursorForward() case termbox.KeyHome, termbox.KeyCtrlA: e.moveCursorToTop() case termbox.KeyEnd, termbox.KeyCtrlE: e.moveCursorToEnd() case termbox.KeyCtrlK: e.scrollToAbove() case termbox.KeyCtrlJ: e.scrollToBelow() case termbox.KeyCtrlG: e.scrollToBottom(len(contents)) case termbox.KeyCtrlT: e.scrollToTop() case termbox.KeyCtrlL: e.toggleKeymode() case termbox.KeyCtrlU: e.deleteLineQuery() case termbox.KeyCtrlW: e.deleteWordBackward() case termbox.KeyEsc: e.escapeCandidateMode() case termbox.KeyEnter: if !e.candidatemode { var cc string var err error if e.prettyResult { cc, _, _, err = e.manager.GetPretty(e.query, true) }else{ cc, _, _, err = e.manager.Get(e.query, true) } return &EngineResult{ content: cc, qs: e.query.StringGet(), err: err, } } e.confirmCandidate() case termbox.KeyCtrlC: return &EngineResult{} default: } case termbox.EventError: panic(ev.Err) break default: } } } func (e *Engine) getContents() []string { var c string var contents []string c, e.complete, e.candidates, _ = e.manager.GetPretty(e.query, e.queryConfirm) if e.keymode { contents = e.candidates } else { contents = strings.Split(c, "\n") } return contents } func (e *Engine) setCandidateData() { if l := len(e.candidates); e.complete[0] == "" && l > 1 { if e.candidateidx >= l { e.candidateidx = 0 } } else { e.candidatemode = false } if !e.candidatemode { e.candidateidx = 0 e.candidates = []string{} } } func (e *Engine) confirmCandidate() { _, _ = e.query.PopKeyword() _ = e.query.StringAdd(".") _ = e.query.StringAdd(e.candidates[e.candidateidx]) e.queryCursorIdx = e.query.Length() e.queryConfirm = true } func (e *Engine) deleteChar() { if i := e.queryCursorIdx - 1; i > 0 { _ = e.query.Delete(i) e.queryCursorIdx-- } } func (e *Engine) deleteLineQuery() { _ = e.query.StringSet("") e.queryCursorIdx = 0 } func (e *Engine) scrollToBelow() { e.contentOffset++ } func (e *Engine) scrollToAbove() { if o := e.contentOffset - 1; o >= 0 { e.contentOffset = o } } func (e *Engine) scrollToBottom(rownum int) { e.contentOffset = rownum - 1 } func (e *Engine) scrollToTop() { e.contentOffset = 0 } func (e *Engine) toggleKeymode() { e.keymode = !e.keymode } func (e *Engine) deleteWordBackward() { if k, _ := e.query.StringPopKeyword(); k != "" && !strings.Contains(k, "[") { _ = e.query.StringAdd(".") } e.queryCursorIdx = e.query.Length() } func (e *Engine) tabAction() { if !e.candidatemode { e.candidatemode = true if e.complete[0] != e.complete[1] && e.complete[0] != "" { if k, _ := e.query.StringPopKeyword(); !strings.Contains(k, "[") { _ = e.query.StringAdd(".") } _ = e.query.StringAdd(e.complete[1]) } else { _ = e.query.StringAdd(e.complete[0]) } } else { e.candidateidx = e.candidateidx + 1 } e.queryCursorIdx = e.query.Length() } func (e *Engine) escapeCandidateMode() { e.candidatemode = false } func (e *Engine) inputChar(ch rune) { _ = e.query.Insert([]rune{ch}, e.queryCursorIdx) e.queryCursorIdx++ } func (e *Engine) moveCursorBackward() { if i := e.queryCursorIdx - 1; i >= 0 { e.queryCursorIdx-- } } func (e *Engine) moveCursorForward() { if e.query.Length() > e.queryCursorIdx { e.queryCursorIdx++ } } func (e *Engine) moveCursorWordBackwark() { } func (e *Engine) moveCursorWordForward() { } func (e *Engine) moveCursorToTop() { e.queryCursorIdx = 0 } func (e *Engine) moveCursorToEnd() { e.queryCursorIdx = e.query.Length() } jid-0.7.3/engine_test.go000066400000000000000000000201541341361473600151340ustar00rootroot00000000000000package jid import ( "bytes" "os" "testing" "github.com/stretchr/testify/assert" ) func TestNewEngine(t *testing.T) { var assert = assert.New(t) f, _ := os.Create("/dev/null") e, err := NewEngine(f, &EngineAttribute{ DefaultQuery: "", Monochrome: false, }) assert.Nil(e) assert.NotNil(err) ee := getEngine(`{"name":"go"}`, "") assert.NotNil(ee) assert.Equal("", ee.query.StringGet()) assert.Equal(0, ee.queryCursorIdx) } func TestNewEngineWithQuery(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, ".nam") assert.Equal(".nam", e.query.StringGet()) assert.Equal(4, e.queryCursorIdx) e = getEngine(`{"name":"go"}`, "nam") assert.Equal("", e.query.StringGet()) assert.Equal(0, e.queryCursorIdx) e = getEngine(`{"name":"go"}`, ".nam..") assert.Equal("", e.query.StringGet()) assert.Equal(0, e.queryCursorIdx) } func TestDeleteChar(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, "") e.query.StringSet(".name") e.queryCursorIdx = e.query.Length() e.deleteChar() assert.Equal(".nam", e.query.StringGet()) assert.Equal(4, e.queryCursorIdx) } func TestDeleteWordBackward(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, "") e.query.StringSet(".name") e.deleteWordBackward() assert.Equal(".", e.query.StringGet()) assert.Equal(1, e.queryCursorIdx) e.query.StringSet(".name[1]") e.deleteWordBackward() assert.Equal(".name", e.query.StringGet()) assert.Equal(5, e.queryCursorIdx) e.query.StringSet(".name[") e.deleteWordBackward() assert.Equal(".name", e.query.StringGet()) assert.Equal(5, e.queryCursorIdx) } func TestDeleteLineQuery(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, "") e.query.StringSet(".name") e.deleteLineQuery() assert.Equal("", e.query.StringGet()) assert.Equal(0, e.queryCursorIdx) } func TestScrollToAbove(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"named":"go","NameTest":[1,2,3]}`, "") assert.Equal(0, e.contentOffset) e.scrollToAbove() assert.Equal(0, e.contentOffset) e.contentOffset = 5 e.scrollToAbove() assert.Equal(4, e.contentOffset) e.scrollToAbove() assert.Equal(3, e.contentOffset) } func TestScrollToBelow(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"named":"go","NameTest":[1,2,3]}`, "") e.scrollToBelow() assert.Equal(1, e.contentOffset) e.scrollToBelow() e.scrollToBelow() assert.Equal(3, e.contentOffset) } func TestScrollToBottomAndTop(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"named":"go","NameTest":[1,2,3]}`, "") e.scrollToBottom(5) assert.Equal(4, e.contentOffset) e.scrollToTop() assert.Equal(0, e.contentOffset) } func TestGetContents(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, "") c := e.getContents() assert.Equal([]string{`{`, ` "name": "go"`, "}"}, c) assert.Equal([]string{}, e.candidates) assert.Equal([]string{"", ""}, e.complete) e = getEngine(`{"name":"go", "naming":"simeji", "foo":"bar"}`, "") e.query.StringSet(".n") c = e.getContents() assert.Equal([]string{`{`, ` "foo": "bar",`, ` "name": "go",`, ` "naming": "simeji"`, "}"}, c) assert.Equal([]string{"name", "naming"}, e.candidates) assert.Equal([]string{"am", "nam"}, e.complete) e.keymode = true c = e.getContents() assert.Equal([]string{"name", "naming"}, c) assert.Equal([]string{"name", "naming"}, e.candidates) assert.Equal([]string{"am", "nam"}, e.complete) } func TestSetCandidateData(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, "") // case 1 e.candidates = []string{"test", "testing"} e.complete = []string{"est", "test"} e.candidatemode = true e.candidateidx = 1 e.setCandidateData() assert.False(e.candidatemode) assert.Zero(e.candidateidx) assert.Equal([]string{}, e.candidates) // case 2 e.candidates = []string{"test"} e.complete = []string{"", "test"} e.candidatemode = true e.candidateidx = 1 e.setCandidateData() assert.False(e.candidatemode) assert.Zero(e.candidateidx) assert.Equal([]string{}, e.candidates) // case 3 e.candidates = []string{"test", "testing"} e.complete = []string{"", "test"} e.candidatemode = true e.candidateidx = 2 e.setCandidateData() assert.True(e.candidatemode) assert.Zero(e.candidateidx) assert.Equal([]string{"test", "testing"}, e.candidates) // case 4 e.candidates = []string{"test", "testing"} e.complete = []string{"", "test"} e.candidatemode = true e.candidateidx = 1 e.setCandidateData() assert.True(e.candidatemode) assert.Equal(1, e.candidateidx) assert.Equal([]string{"test", "testing"}, e.candidates) // case 4 e.candidates = []string{"test", "testing"} e.complete = []string{"", "test"} e.candidatemode = false e.candidateidx = 1 e.setCandidateData() assert.False(e.candidatemode) assert.Equal(0, e.candidateidx) assert.Equal([]string{}, e.candidates) } func TestConfirmCandidate(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go","NameTest":[1,2,3]}`, "") e.query.StringSet(".") e.queryConfirm = false e.candidates = []string{"test", "testing", "foo"} e.candidateidx = 0 e.confirmCandidate() assert.Equal(".test", e.query.StringGet()) assert.True(e.queryConfirm) assert.Equal(5, e.queryCursorIdx) e.candidateidx = 2 e.confirmCandidate() assert.Equal(".foo", e.query.StringGet()) assert.True(e.queryConfirm) assert.Equal(4, e.queryCursorIdx) e = getEngine(`{"name":"go"}`, "") e.query.StringSet(".name.hoge") e.candidates = []string{"aaa", "bbb", "ccc"} e.candidateidx = 1 e.confirmCandidate() assert.True(e.queryConfirm) assert.Equal(9, e.queryCursorIdx) assert.Equal(".name.bbb", e.query.StringGet()) } func TestCtrllAction(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go","NameTest":[1,2,3]}`, "") assert.False(e.keymode) e.toggleKeymode() assert.True(e.keymode) e.toggleKeymode() assert.False(e.keymode) } func TestTabAction(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go","NameTest":[1,2,3]}`, "") e.query.StringSet(".namet") e.complete = []string{"est", "NameTest"} e.candidatemode = false e.tabAction() assert.Equal(".NameTest", e.query.StringGet()) _, e.complete, _, _ = e.manager.GetPretty(e.query, true) e.candidatemode = false e.tabAction() assert.Equal(".NameTest[", e.query.StringGet()) _, e.complete, _, _ = e.manager.GetPretty(e.query, true) e.candidatemode = false e.tabAction() assert.Equal(".NameTest[", e.query.StringGet()) } func TestEscAction(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go","NameTest":[1,2,3]}`, "") assert.False(e.candidatemode) e.escapeCandidateMode() assert.False(e.candidatemode) e.candidatemode = true e.escapeCandidateMode() assert.False(e.candidatemode) } func TestInputChar(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, "") e.query.StringSet(".name") e.queryCursorIdx = e.query.Length() assert.Equal(5, e.queryCursorIdx) e.inputChar('n') assert.Equal(".namen", e.query.StringGet()) assert.Equal(6, e.queryCursorIdx) e.inputChar('.') assert.Equal(".namen.", e.query.StringGet()) assert.Equal(7, e.queryCursorIdx) } func TestMoveCursorForwardAndBackward(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"simeji"}`, "") e.query.StringSet(".ne") e.moveCursorForward() assert.Equal(1, e.queryCursorIdx) e.moveCursorForward() assert.Equal(2, e.queryCursorIdx) e.moveCursorForward() assert.Equal(3, e.queryCursorIdx) e.moveCursorForward() assert.Equal(3, e.queryCursorIdx) e.moveCursorBackward() assert.Equal(2, e.queryCursorIdx) e.moveCursorBackward() assert.Equal(1, e.queryCursorIdx) e.moveCursorBackward() assert.Equal(0, e.queryCursorIdx) e.moveCursorBackward() assert.Equal(0, e.queryCursorIdx) } func TestMoveCursorToTopAndEnd(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"simeji"}`, "") e.query.StringSet(".ne") e.moveCursorToTop() assert.Zero(e.queryCursorIdx) e.moveCursorToEnd() assert.Equal(3, e.queryCursorIdx) } func getEngine(j string, qs string) *Engine { r := bytes.NewBufferString(j) e, _ := NewEngine(r, &EngineAttribute{ DefaultQuery: qs, Monochrome: false, }) ee := e.(*Engine) return ee } jid-0.7.3/go.mod000066400000000000000000000006401341361473600134050ustar00rootroot00000000000000module github.com/simeji/jid require ( github.com/bitly/go-simplejson v0.5.0 github.com/fatih/color v1.7.0 // indirect github.com/mattn/go-colorable v0.0.9 // indirect github.com/mattn/go-isatty v0.0.4 // indirect github.com/mattn/go-runewidth v0.0.4 github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed github.com/nwidger/jsoncolor v0.0.0-20170215171346-75a6de4340e5 github.com/pkg/errors v0.8.0 ) jid-0.7.3/go.sum000066400000000000000000000027141341361473600134360ustar00rootroot00000000000000github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed h1:bAVGG6B+R5qpSylrrA+BAMrzYkdAoiTaKPVxRB+4cyM= github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/nwidger/jsoncolor v0.0.0-20170215171346-75a6de4340e5 h1:d+C3xJdxZT7wNlxqEwbXn3R355CwAhYBL9raVNfSnK0= github.com/nwidger/jsoncolor v0.0.0-20170215171346-75a6de4340e5/go.mod h1:GYFm0zZgTNeoK1QxuIofRDasy2ibmaJZhZLzwsMXUF4= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= jid-0.7.3/json_manager.go000066400000000000000000000063561341361473600153030ustar00rootroot00000000000000package jid import ( "github.com/bitly/go-simplejson" "github.com/pkg/errors" "io" "io/ioutil" "regexp" "strconv" //"strings" ) type JsonManager struct { current *simplejson.Json origin *simplejson.Json suggestion *Suggestion } func NewJsonManager(reader io.Reader) (*JsonManager, error) { buf, err := ioutil.ReadAll(reader) if err != nil { return nil, errors.Wrap(err, "invalid data") } j, err2 := simplejson.NewJson(buf) if err2 != nil { return nil, errors.Wrap(err2, "invalid json format") } json := &JsonManager{ origin: j, current: j, suggestion: NewSuggestion(), } return json, nil } func (jm *JsonManager) Get(q QueryInterface, confirm bool) (string, []string, []string, error) { json, suggestion, candidates, _ := jm.GetFilteredData(q, confirm) data, enc_err := json.Encode() if enc_err != nil { return "", []string{"", ""}, []string{"", ""}, errors.Wrap(enc_err, "failure json encode") } return string(data), suggestion, candidates, nil } func (jm *JsonManager) GetPretty(q QueryInterface, confirm bool) (string, []string, []string, error) { json, suggestion, candidates, _ := jm.GetFilteredData(q, confirm) s, err := json.EncodePretty() if err != nil { return "", []string{"", ""}, []string{"", ""}, errors.Wrap(err, "failure json encode") } return string(s), suggestion, candidates, nil } func (jm *JsonManager) GetFilteredData(q QueryInterface, confirm bool) (*simplejson.Json, []string, []string, error) { json := jm.origin lastKeyword := q.StringGetLastKeyword() keywords := q.StringGetKeywords() idx := 0 if l := len(keywords); l == 0 { return json, []string{"", ""}, []string{}, nil } else if l > 0 { idx = l - 1 } for _, keyword := range keywords[0:idx] { json, _ = getItem(json, keyword) } reg := regexp.MustCompile(`\[[0-9]*$`) suggest := jm.suggestion.Get(json, lastKeyword) candidateKeys := jm.suggestion.GetCandidateKeys(json, lastKeyword) // hash if len(reg.FindString(lastKeyword)) < 1 { candidateNum := len(candidateKeys) if j, exist := getItem(json, lastKeyword); exist && (confirm || candidateNum == 1) { json = j candidateKeys = []string{} if _, err := json.Array(); err == nil { suggest = jm.suggestion.Get(json, "") } else { suggest = []string{"", ""} } } else if candidateNum < 1 { json = j suggest = jm.suggestion.Get(json, "") } } return json, suggest, candidateKeys, nil } func (jm *JsonManager) GetCandidateKeys(q QueryInterface) []string { return jm.suggestion.GetCandidateKeys(jm.current, q.StringGetLastKeyword()) } func getItem(json *simplejson.Json, s string) (*simplejson.Json, bool) { var result *simplejson.Json var exist bool re := regexp.MustCompile(`\[([0-9]+)\]`) matches := re.FindStringSubmatch(s) if s == "" { return json, false } // Query include [ if len(matches) > 0 { index, _ := strconv.Atoi(matches[1]) if a, err := json.Array(); err != nil { exist = false } else if len(a) < index { exist = false } result = json.GetIndex(index) } else { result, exist = json.CheckGet(s) if result == nil { result = &simplejson.Json{} } } return result, exist } func isEmptyJson(j *simplejson.Json) bool { switch j.Interface().(type) { case nil: return true default: return false } } jid-0.7.3/json_manager_test.go000066400000000000000000000263751341361473600163450ustar00rootroot00000000000000package jid import ( "bytes" "github.com/bitly/go-simplejson" "github.com/stretchr/testify/assert" "io/ioutil" "testing" ) func TestNewJson(t *testing.T) { var assert = assert.New(t) r := bytes.NewBufferString("{\"name\":\"go\"}") jm, e := NewJsonManager(r) rr := bytes.NewBufferString("{\"name\":\"go\"}") buf, _ := ioutil.ReadAll(rr) sj, _ := simplejson.NewJson(buf) assert.Equal(jm, &JsonManager{ current: sj, origin: sj, suggestion: NewSuggestion(), }) assert.Nil(e) assert.Equal("go", jm.current.Get("name").MustString()) } func TestNewJsonWithError(t *testing.T) { var assert = assert.New(t) r := bytes.NewBufferString("{\"name\":\"go\"") jm, e := NewJsonManager(r) assert.Nil(jm) assert.Regexp("invalid json format", e.Error()) } func TestGet(t *testing.T) { var assert = assert.New(t) r := bytes.NewBufferString("{\"name\":\"go\"}") jm, _ := NewJsonManager(r) q := NewQueryWithString(".name") result, suggest, candidateKeys, err := jm.Get(q, false) assert.Nil(err) assert.Equal(`"go"`, result) assert.Equal([]string{``, ``}, suggest) assert.Equal([]string{}, candidateKeys) // data data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}` r = bytes.NewBufferString(data) jm, _ = NewJsonManager(r) // case 2 q = NewQueryWithString(".abcde") result, suggest, candidateKeys, err = jm.Get(q, false) assert.Nil(err) //assert.Equal(`"2AA2"`, result) assert.Equal(`{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}`, result) assert.Equal([]string{``, "abcde"}, suggest) // case 3 q = NewQueryWithString(".abcde_fgh") result, suggest, candidateKeys, err = jm.Get(q, false) assert.Nil(err) assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result) assert.Equal([]string{``, ``}, suggest) // case 4 q = NewQueryWithString(".abcde_fgh.aaa[2]") result, suggest, candidateKeys, err = jm.Get(q, false) assert.Equal(`[1,2]`, result) // case 5 q = NewQueryWithString(".abcde_fgh.aaa[3]") result, suggest, candidateKeys, err = jm.Get(q, false) assert.Nil(err) assert.Equal(`null`, result) // case 6 q = NewQueryWithString(".abcde_fgh.aa") result, suggest, candidateKeys, err = jm.Get(q, false) assert.Nil(err) assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result) assert.Equal([]string{`a`, `aaa`}, suggest) // case 7 q = NewQueryWithString(".abcde_fgh.ac") result, suggest, candidateKeys, err = jm.Get(q, false) assert.Nil(err) assert.Equal(`null`, result) assert.Equal([]string{``, ``}, suggest) // data data = `{"abc":"2AA2","def":{"aaa":"bbb"}}` r = bytes.NewBufferString(data) jm, _ = NewJsonManager(r) // case 2 q = NewQueryWithString(".def") result, suggest, candidateKeys, err = jm.Get(q, false) assert.Nil(err) assert.Equal(`{"aaa":"bbb"}`, result) assert.Equal([]string{``, ``}, suggest) } func TestGetPretty(t *testing.T) { var assert = assert.New(t) r := bytes.NewBufferString("{\"name\":\"go\"}") jm, _ := NewJsonManager(r) q := NewQueryWithString(".name") result, _, _, err := jm.GetPretty(q, true) assert.Nil(err) assert.Equal(`"go"`, result) } func TestGetItem(t *testing.T) { var assert = assert.New(t) rr := bytes.NewBufferString(`{"name":"go"}`) buf, _ := ioutil.ReadAll(rr) sj, _ := simplejson.NewJson(buf) d, _ := getItem(sj, "") result, _ := d.Encode() assert.Equal(`{"name":"go"}`, string(result)) d, _ = getItem(sj, "name") result, _ = d.Encode() assert.Equal(`"go"`, string(result)) // case 2 rr = bytes.NewBufferString(`{"name":"go","age":20}`) buf, _ = ioutil.ReadAll(rr) sj, _ = simplejson.NewJson(buf) d, _ = getItem(sj, "age") result, _ = d.Encode() assert.Equal("20", string(result)) // case 3 rr = bytes.NewBufferString(`{"data":{"name":"go","age":20}}`) buf, _ = ioutil.ReadAll(rr) sj, _ = simplejson.NewJson(buf) d, _ = getItem(sj, "data") d2, _ := getItem(d, "name") d3, _ := getItem(d, "age") result2, _ := d2.Encode() result3, _ := d3.Encode() assert.Equal(`"go"`, string(result2)) assert.Equal(`20`, string(result3)) // case 4 rr = bytes.NewBufferString(`{"data":[{"name":"test","age":30},{"name":"go","age":20}]}`) buf, _ = ioutil.ReadAll(rr) sj, _ = simplejson.NewJson(buf) d, _ = getItem(sj, "data") d2, _ = getItem(d, "[1]") d3, _ = getItem(d2, "name") result, _ = d3.Encode() assert.Equal(`"go"`, string(result)) // case 5 rr = bytes.NewBufferString(`[{"name":"go","age":20}]`) buf, _ = ioutil.ReadAll(rr) sj, _ = simplejson.NewJson(buf) d, _ = getItem(sj, "") result, _ = d.Encode() assert.Equal(`[{"age":20,"name":"go"}]`, string(result)) // case 6 d, _ = getItem(sj, "[0]") result, _ = d.Encode() assert.Equal(`{"age":20,"name":"go"}`, string(result)) } func TestGetFilteredData(t *testing.T) { var assert = assert.New(t) // data data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"},"cc":{"a":[3,4]}}` r := bytes.NewBufferString(data) jm, _ := NewJsonManager(r) // case 1 q := NewQueryWithString(".abcde") result, s, c, err := jm.GetFilteredData(q, false) assert.Nil(err) d, _ := result.Encode() assert.Equal(`{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"},"cc":{"a":[3,4]}}`, string(d)) //assert.Equal(`"2AA2"`, string(d)) assert.Equal([]string{``, `abcde`}, s) assert.Equal([]string{"abcde", "abcde_fgh"}, c) // case 2 q = NewQueryWithString(".abcde_fgh") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) assert.Equal([]string{``, ``}, s) assert.Equal([]string{}, c) // case 3 q = NewQueryWithString(".abcde_fgh.aaa[2]") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`[1,2]`, string(d)) assert.Equal([]string{`[`, `[`}, s) // case 4 q = NewQueryWithString(".abcde_fgh.aaa[3]") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`null`, string(d)) assert.Equal([]string{``, ``}, s) // case 5 q = NewQueryWithString(".abcde_fgh.aaa") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`[123,"cccc",[1,2]]`, string(d)) assert.Equal([]string{`[`, `[`}, s) // case 6 q = NewQueryWithString(".abcde_fgh.aa") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) assert.Equal([]string{`a`, `aaa`}, s) // case 7 q = NewQueryWithString(".abcde_fgh.aaa[") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`[123,"cccc",[1,2]]`, string(d)) assert.Equal([]string{``, `[`}, s) // case 8 q = NewQueryWithString(".") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"},"cc":{"a":[3,4]}}`, string(d)) assert.Equal([]string{``, ``}, s) // case 9 q = NewQueryWithString(".cc.") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"a":[3,4]}`, string(d)) assert.Equal([]string{`a`, `a`}, s) assert.Equal([]string{"a"}, c) // case 2-1 data = `{"arraytest":[{"aaa":123,"aab":234},[1,2]]}` r = bytes.NewBufferString(data) jm, _ = NewJsonManager(r) q = NewQueryWithString(".arraytest[0]") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"aaa":123,"aab":234}`, string(d)) assert.Equal([]string{``, ``}, s) assert.Equal([]string{}, c) // case 3-1 data = `{"aa":"abcde","bb":{"foo":"bar"}}` r = bytes.NewBufferString(data) jm, _ = NewJsonManager(r) q = NewQueryWithString(".bb") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"foo":"bar"}`, string(d)) assert.Equal([]string{``, ``}, s) assert.Equal([]string{}, c) // case 4-1 data = `[{"name": "simeji"},{"name": "simeji2"}]` r = bytes.NewBufferString(data) jm, _ = NewJsonManager(r) q = NewQueryWithString("") result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`[{"name":"simeji"},{"name":"simeji2"}]`, string(d)) assert.Equal([]string{``, ``}, s) assert.Equal([]string{}, c) // case 5-1 data = `{"PrivateName":"simei", "PrivateAlias": "simeji2"}` r = bytes.NewBufferString(data) jm, _ = NewJsonManager(r) q = NewQueryWithString(".Private") result, s, c, err = jm.GetFilteredData(q, false) d, _ = result.Encode() assert.Equal([]string{``, `Private`}, s) assert.Equal([]string{"PrivateAlias", "PrivateName"}, c) } func TestGetFilteredDataWithMatchQuery(t *testing.T) { var assert = assert.New(t) data := `{"name":[1,2,3], "naming":{"account":"simeji"}, "test":"simeji", "testing":"ok"}` r := bytes.NewBufferString(data) jm, _ := NewJsonManager(r) q := NewQueryWithString(`.name`) result, s, c, err := jm.GetFilteredData(q, false) assert.Nil(err) d, _ := result.Encode() assert.Equal(`[1,2,3]`, string(d)) assert.Equal([]string{"[", "["}, s) assert.Equal([]string{}, c) q = NewQueryWithString(`.naming`) result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"account":"simeji"}`, string(d)) assert.Equal([]string{"", ""}, s) assert.Equal([]string{}, c) q = NewQueryWithString(`.naming.`) result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"account":"simeji"}`, string(d)) assert.Equal([]string{"account", "account"}, s) assert.Equal([]string{"account"}, c) q = NewQueryWithString(`.test`) result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"name":[1,2,3],"naming":{"account":"simeji"},"test":"simeji","testing":"ok"}`, string(d)) assert.Equal([]string{"", "test"}, s) assert.Equal([]string{"test", "testing"}, c) } func TestGetCandidateKeys(t *testing.T) { var assert = assert.New(t) data := `{"name":[1,2,3], "naming":{"account":"simeji"}, "test":"simeji", "testing":"ok"}` r := bytes.NewBufferString(data) jm, _ := NewJsonManager(r) q := NewQueryWithString(`.n`) keys := jm.GetCandidateKeys(q) assert.Equal([]string{"name", "naming"}, keys) q = NewQueryWithString(`.`) keys = jm.GetCandidateKeys(q) assert.Equal([]string{"name", "naming", "test", "testing"}, keys) q = NewQueryWithString(`.test`) keys = jm.GetCandidateKeys(q) assert.Equal([]string{"test", "testing"}, keys) q = NewQueryWithString(`.testi`) keys = jm.GetCandidateKeys(q) assert.Equal([]string{"testing"}, keys) q = NewQueryWithString(`.testia`) keys = jm.GetCandidateKeys(q) assert.Equal([]string{}, keys) } func TestGetCurrentKeys(t *testing.T) { var assert = assert.New(t) r := bytes.NewBufferString(`{"name":"go","age":20,"weight":60}`) buf, _ := ioutil.ReadAll(r) sj, _ := simplejson.NewJson(buf) keys := getCurrentKeys(sj) assert.Equal([]string{"age", "name", "weight"}, keys) r = bytes.NewBufferString(`[2,3,"aa"]`) buf, _ = ioutil.ReadAll(r) sj, _ = simplejson.NewJson(buf) keys = getCurrentKeys(sj) assert.Equal([]string{}, keys) } func TestIsEmptyJson(t *testing.T) { var assert = assert.New(t) r := bytes.NewBufferString(`{"name":"go"}`) buf, _ := ioutil.ReadAll(r) sj, _ := simplejson.NewJson(buf) assert.Equal(false, isEmptyJson(sj)) assert.Equal(true, isEmptyJson(&simplejson.Json{})) } jid-0.7.3/query.go000066400000000000000000000113331341361473600137740ustar00rootroot00000000000000package jid import ( "regexp" "strings" "github.com/mattn/go-runewidth" ) type QueryInterface interface { Get() []rune Set(query []rune) []rune Insert(query []rune, idx int) []rune Add(query []rune) []rune Delete(i int) []rune Clear() []rune Length() int IndexOffset(int) int GetChar(int) rune GetKeywords() [][]rune GetLastKeyword() []rune PopKeyword() ([]rune, []rune) StringGet() string StringSet(query string) string StringInsert(query string, idx int) string StringAdd(query string) string StringGetKeywords() []string StringGetLastKeyword() string StringPopKeyword() (string, []rune) } type Query struct { query *[]rune complete *[]rune } func NewQuery(query []rune) *Query { q := &Query{ query: &[]rune{}, complete: &[]rune{}, } _ = q.Set(query) return q } func NewQueryWithString(query string) *Query { return NewQuery([]rune(query)) } func (q *Query) Get() []rune { return *q.query } func (q *Query) GetChar(idx int) rune { var r rune = 0 qq := q.Get() if l := len(qq); l > idx && idx >= 0 { r = qq[idx] } return r } func (q *Query) Length() int { return len(q.Get()) } func (q *Query) IndexOffset(i int) int { o := 0 if l := q.Length(); i >= l { o = runewidth.StringWidth(q.StringGet()) } else if i >= 0 && i < l { o = runewidth.StringWidth(string(q.Get()[:i])) } return o } func (q *Query) Set(query []rune) []rune { if validate(query) { q.query = &query } return q.Get() } func (q *Query) Insert(query []rune, idx int) []rune { qq := q.Get() if idx == 0 { qq = append(query, qq...) } else if idx > 0 && len(qq) >= idx { _q := make([]rune, idx+len(query)-1) copy(_q, qq[:idx]) qq = append(append(_q, query...), qq[idx:]...) } return q.Set(qq) } func (q *Query) StringInsert(query string, idx int) string { return string(q.Insert([]rune(query), idx)) } func (q *Query) Add(query []rune) []rune { return q.Set(append(q.Get(), query...)) } func (q *Query) Delete(i int) []rune { var d []rune qq := q.Get() lastIdx := len(qq) if i < 0 { if lastIdx+i >= 0 { d = qq[lastIdx+i:] qq = qq[0 : lastIdx+i] } else { d = qq qq = qq[0:0] } } else if i == 0 { d = []rune{} qq = qq[1:] } else if i > 0 && i < lastIdx { d = []rune{qq[i]} qq = append(qq[:i], qq[i+1:]...) } _ = q.Set(qq) return d } func (q *Query) Clear() []rune { return q.Set([]rune("")) } func (q *Query) GetKeywords() [][]rune { query := string(*q.query) if query == "" { return [][]rune{} } splitQuery := strings.Split(query, ".") lastIdx := len(splitQuery) - 1 keywords := [][]rune{} for i, keyword := range splitQuery { if keyword != "" || i == lastIdx { re := regexp.MustCompile(`\[[0-9]*\]?`) matchIndexes := re.FindAllStringIndex(keyword, -1) if len(matchIndexes) < 1 { keywords = append(keywords, []rune(keyword)) } else { if matchIndexes[0][0] > 0 { keywords = append(keywords, []rune(keyword[0:matchIndexes[0][0]])) } for _, matchIndex := range matchIndexes { k := keyword[matchIndex[0]:matchIndex[1]] keywords = append(keywords, []rune(k)) } } } } return keywords } func (q *Query) GetLastKeyword() []rune { keywords := q.GetKeywords() if l := len(keywords); l > 0 { return keywords[l-1] } return []rune("") } func (q *Query) StringGetLastKeyword() string { return string(q.GetLastKeyword()) } func (q *Query) PopKeyword() ([]rune, []rune) { var keyword []rune var lastSepIdx int var lastBracketIdx int qq := q.Get() for i, e := range qq { if e == '.' { lastSepIdx = i } else if e == '[' { lastBracketIdx = i } } if lastBracketIdx > lastSepIdx { lastSepIdx = lastBracketIdx } keywords := q.GetKeywords() if l := len(keywords); l > 0 { keyword = keywords[l-1] } query := q.Set(qq[0:lastSepIdx]) return keyword, query } func (q *Query) StringGet() string { return string(q.Get()) } func (q *Query) StringSet(query string) string { return string(q.Set([]rune(query))) } func (q *Query) StringAdd(query string) string { return string(q.Add([]rune(query))) } func (q *Query) StringGetKeywords() []string { var keywords []string for _, keyword := range q.GetKeywords() { keywords = append(keywords, string(keyword)) } return keywords } func (q *Query) StringPopKeyword() (string, []rune) { keyword, query := q.PopKeyword() return string(keyword), query } func validate(r []rune) bool { s := string(r) if s == "" { return true } if regexp.MustCompile(`^[^.]`).MatchString(s) { return false } if regexp.MustCompile(`\.{2,}`).MatchString(s) { return false } if regexp.MustCompile(`\[[0-9]*\][^\.\[]`).MatchString(s) { return false } if regexp.MustCompile(`\[{2,}|\]{2,}`).MatchString(s) { return false } if regexp.MustCompile(`.\.\[`).MatchString(s) { return false } return true } jid-0.7.3/query_test.go000066400000000000000000000233241341361473600150360ustar00rootroot00000000000000package jid import ( "github.com/stretchr/testify/assert" "testing" ) func TestValidate(t *testing.T) { var assert = assert.New(t) assert.True(validate([]rune(".test.name"))) assert.True(validate([]rune(".test.name."))) assert.True(validate([]rune(".test[0].name."))) assert.True(validate([]rune(".[0].name."))) assert.True(validate([]rune(".name[9][1]"))) assert.True(validate([]rune(".[0][1].name."))) assert.False(validate([]rune("[0].name."))) assert.False(validate([]rune(".test[0]].name."))) assert.False(validate([]rune(".test..name"))) assert.False(validate([]rune(".test.name.."))) assert.False(validate([]rune(".test[[0]].name."))) assert.False(validate([]rune(".test[0]name."))) assert.False(validate([]rune(".test.[0].name."))) } func TestNewQuery(t *testing.T) { var assert = assert.New(t) v := []rune(".name") q := NewQuery(v) assert.Equal(*q.query, []rune(".name")) assert.Equal(*q.complete, []rune("")) } func TestNewQueryWithInvalidQuery(t *testing.T) { var assert = assert.New(t) v := []rune("name") q := NewQuery(v) assert.Equal(*q.query, []rune("")) assert.Equal(*q.complete, []rune("")) } func TestNewQueryWithString(t *testing.T) { var assert = assert.New(t) q := NewQueryWithString(".name") assert.Equal(*q.query, []rune(".name")) assert.Equal(*q.complete, []rune("")) } func TestNewQueryWithStringWithInvalidQuery(t *testing.T) { var assert = assert.New(t) q := NewQueryWithString("name") assert.Equal(*q.query, []rune("")) assert.Equal(*q.complete, []rune("")) } func TestQueryGet(t *testing.T) { var assert = assert.New(t) v := []rune(".test") q := NewQuery(v) assert.Equal(q.Get(), []rune(".test")) } func TestQueryLength(t *testing.T) { var assert = assert.New(t) v := []rune(".test") q := NewQuery(v) assert.Equal(5, q.Length()) v = []rune(".string.日本語.japan") q = NewQuery(v) assert.Equal(17, q.Length()) } func TestQueryIndexOffsetN(t *testing.T) { var assert = assert.New(t) v := []rune(".test") q := NewQuery(v) assert.Equal(4, q.IndexOffset(4)) assert.Equal(0, q.IndexOffset(0)) assert.Equal(0, q.IndexOffset(-1)) assert.Equal(5, q.IndexOffset(6)) //off-------012345679-101213|j_15,n_19 v = []rune(".string.日本語.japan") //idx-------012345678-9-10|j_12,n_16 q = NewQuery(v) assert.Equal(19, q.IndexOffset(16)) assert.Equal(10, q.IndexOffset(9)) } func TestQueryGetChar(t *testing.T) { var assert = assert.New(t) v := []rune(".test") q := NewQuery(v) assert.Equal('e', q.GetChar(2)) assert.Equal('t', q.GetChar(4)) assert.Equal('.', q.GetChar(0)) assert.Equal('.', q.GetChar(0)) assert.Equal(rune(0), q.GetChar(-1)) assert.Equal(rune(0), q.GetChar(6)) v = []rune(".string.日本語.japan") q = NewQuery(v) assert.Equal('n', q.GetChar(5)) assert.Equal('本', q.GetChar(9)) assert.Equal('.', q.GetChar(11)) assert.Equal(rune(0), q.GetChar(17)) } func TestQuerySet(t *testing.T) { var assert = assert.New(t) v := []rune(".hello") q := NewQuery(v) assert.Equal([]rune(".world"), q.Set([]rune(".world"))) assert.Equal("", string(q.Set([]rune("")))) } func TestQuerySetWithInvalidQuery(t *testing.T) { var assert = assert.New(t) v := []rune(".hello") q := NewQuery(v) assert.Equal(q.Set([]rune("world")), []rune(".hello")) } func TestQueryAdd(t *testing.T) { var assert = assert.New(t) v := []rune(".hello") q := NewQuery(v) assert.Equal(q.Add([]rune("world")), []rune(".helloworld")) } func TestQueryInsert(t *testing.T) { var assert = assert.New(t) v := []rune(".hello.world") q := NewQuery(v) assert.Equal([]rune(".hello.world"), q.Insert([]rune("w"), 0)) assert.Equal([]rune(".whello.world"), q.Insert([]rune("w"), 1)) assert.Equal([]rune(".wwhello.world"), q.Insert([]rune("w"), 1)) assert.Equal([]rune(".wwhello.world"), q.Insert([]rune("."), 1)) assert.Equal([]rune(".wwh.ello.world"), q.Insert([]rune("."), 4)) assert.Equal([]rune(".wwh.ello.worldg"), q.Insert([]rune("g"), 15)) assert.Equal([]rune(".wwh.ello.worldg"), q.Insert([]rune("a"), 20)) } func TestQueryStringInsert(t *testing.T) { var assert = assert.New(t) q := NewQueryWithString(".hello.world") assert.Equal(".hello.world", q.StringInsert("w", 0)) assert.Equal(".whello.world", q.StringInsert("w", 1)) assert.Equal(".wwhello.world", q.StringInsert("w", 1)) assert.Equal(".wwhello.world", q.StringInsert(".", 1)) assert.Equal(".wwh.ello.world", q.StringInsert(".", 4)) assert.Equal(".wwh.ello.worlda", q.StringInsert("a", 15)) assert.Equal(".wwh.ello.worlda", q.StringInsert("a", 20)) } func TestQueryClear(t *testing.T) { var assert = assert.New(t) v := []rune(".test") q := NewQuery(v) assert.Equal(q.Clear(), []rune("")) } func TestQueryDelete(t *testing.T) { var assert = assert.New(t) v := []rune(".helloworld") q := NewQuery(v) assert.Equal([]rune("d"), q.Delete(-1)) assert.Equal([]rune(".helloworl"), q.Get()) assert.Equal([]rune("l"), q.Delete(-1)) assert.Equal([]rune(".hellowor"), q.Get()) assert.Equal([]rune("or"), q.Delete(-2)) assert.Equal([]rune(".hellow"), q.Get()) assert.Equal([]rune(".hellow"), q.Delete(-8)) assert.Equal([]rune(""), q.Get()) q = NewQuery([]rune(".hello.world")) assert.Equal([]rune(""), q.Delete(0)) assert.Equal([]rune(".hello.world"), q.Get()) assert.Equal([]rune("h"), q.Delete(1)) assert.Equal([]rune(".ello.world"), q.Get()) assert.Equal([]rune("e"), q.Delete(1)) assert.Equal([]rune(".llo.world"), q.Get()) assert.Equal([]rune(""), q.Delete(0)) assert.Equal([]rune(".llo.world"), q.Get()) assert.Equal([]rune("o"), q.Delete(3)) assert.Equal([]rune(".ll.world"), q.Get()) assert.Equal([]rune("."), q.Delete(3)) assert.Equal([]rune(".llworld"), q.Get()) assert.Equal([]rune("w"), q.Delete(3)) assert.Equal([]rune(".llorld"), q.Get()) } func TestGetKeywords(t *testing.T) { var assert = assert.New(t) v := []rune(".test.name") q := NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("test"), []rune("name"), }) v = []rune("") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{}) v = []rune(".test.name.") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("test"), []rune("name"), []rune(""), }) v = []rune(".hello") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("hello"), }) v = []rune(".hello.") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("hello"), []rune(""), }) v = []rune(".hello[") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("hello"), []rune("["), }) v = []rune(".hello[12") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("hello"), []rune("[12"), }) v = []rune(".hello[0]") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("hello"), []rune("[0]"), }) v = []rune(".hello[13][0]") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("hello"), []rune("[13]"), []rune("[0]"), }) v = []rune(".[3][23].hello[13][0]") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("[3]"), []rune("[23]"), []rune("hello"), []rune("[13]"), []rune("[0]"), }) } func TestGetLastKeyword(t *testing.T) { var assert = assert.New(t) v := []rune(".test.name") q := NewQuery(v) assert.Equal(q.GetLastKeyword(), []rune("name")) v = []rune(".test.") q = NewQuery(v) assert.Equal(q.GetLastKeyword(), []rune("")) v = []rune(".test") q = NewQuery(v) assert.Equal(q.GetLastKeyword(), []rune("test")) v = []rune("") q = NewQuery(v) assert.Equal(q.GetLastKeyword(), []rune("")) } func TestStringGetLastKeyword(t *testing.T) { var assert = assert.New(t) v := []rune(".test.name") q := NewQuery(v) assert.Equal(q.StringGetLastKeyword(), "name") v = []rune(".test.") q = NewQuery(v) assert.Equal(q.StringGetLastKeyword(), "") v = []rune(".test") q = NewQuery(v) assert.Equal(q.StringGetLastKeyword(), "test") v = []rune("") q = NewQuery(v) assert.Equal(q.StringGetLastKeyword(), "") } func TestPopKeyword(t *testing.T) { var assert = assert.New(t) v := []rune(".test.name") q := NewQuery(v) k, query := q.PopKeyword() assert.Equal(k, []rune("name")) assert.Equal(query, []rune(".test")) assert.Equal(q.Get(), []rune(".test")) v = []rune(".test.name.") q = NewQuery(v) k, query = q.PopKeyword() assert.Equal(k, []rune("")) assert.Equal(query, []rune(".test.name")) assert.Equal(q.Get(), []rune(".test.name")) } func TestQueryStringGet(t *testing.T) { var assert = assert.New(t) v := []rune(".test") q := NewQuery(v) assert.Equal(q.StringGet(), ".test") } func TestQueryStringSet(t *testing.T) { var assert = assert.New(t) v := []rune(".hello") q := NewQuery(v) assert.Equal(q.StringSet(".world"), ".world") } func TestQueryStringAdd(t *testing.T) { var assert = assert.New(t) v := []rune(".hello") q := NewQuery(v) assert.Equal(q.StringAdd("world"), ".helloworld") } func TestStringGetKeywords(t *testing.T) { var assert = assert.New(t) v := []rune(".test.name") q := NewQuery(v) assert.Equal(q.StringGetKeywords(), []string{ "test", "name", }) v = []rune(".test.name") q = NewQuery(v) assert.Equal(q.StringGetKeywords(), []string{ "test", "name", }) v = []rune("") q = NewQuery(v) kws := q.StringGetKeywords() assert.Equal([]string(nil), kws) assert.Equal(0, len(kws)) } func TestStringPopKeyword(t *testing.T) { var assert = assert.New(t) v := []rune(".test.name") q := NewQuery(v) k, query := q.StringPopKeyword() assert.Equal(k, "name") assert.Equal(query, []rune(".test")) assert.Equal(q.Get(), []rune(".test")) v = []rune(".test.name.") q = NewQuery(v) k, query = q.StringPopKeyword() assert.Equal(k, "") assert.Equal(query, []rune(".test.name")) assert.Equal(q.Get(), []rune(".test.name")) v = []rune(".test.name[23]") q = NewQuery(v) k, query = q.StringPopKeyword() assert.Equal(k, "[23]") assert.Equal(query, []rune(".test.name")) assert.Equal(q.Get(), []rune(".test.name")) } jid-0.7.3/suggestion.go000066400000000000000000000051641341361473600150230ustar00rootroot00000000000000package jid import ( "github.com/bitly/go-simplejson" "regexp" "sort" "strings" ) type SuggestionInterface interface { Get(json *simplejson.Json, keyword string) []string GetCandidateKeys(json *simplejson.Json, keyword string) []string } type SuggestionDataType int const ( UNKNOWN SuggestionDataType = iota ARRAY MAP NUMBER STRING BOOL ) type Suggestion struct { } func NewSuggestion() *Suggestion { return &Suggestion{} } func (s *Suggestion) Get(json *simplejson.Json, keyword string) []string { var completion string var suggestion string if a, err := json.Array(); err == nil { if len(a) > 1 { kw := regexp.MustCompile(`\[([0-9]+)?\]?`).FindString(keyword) if kw == "" { return []string{"[", "["} } else if kw == "[" { return []string{"", "["} } return []string{strings.Replace(kw+"]", kw, "", -1), kw + "]"} } return []string{strings.Replace(`[0]`, keyword, "", -1), `[0]`} } candidateKeys := s.GetCandidateKeys(json, keyword) if keyword == "" { if l := len(candidateKeys); l > 1 { return []string{"", ""} } else if l == 1 { return []string{candidateKeys[0], candidateKeys[0]} } } for _, key := range candidateKeys { // first if suggestion == "" && key != "" { suggestion = key } else { axis := suggestion if len(suggestion) > len(key) { axis = key } max := 0 for i, _ := range axis { if suggestion[i] != key[i] { break } max = i } if max == 0 { suggestion = "" break } suggestion = suggestion[0 : max+1] } } if reg, err := regexp.Compile("(?i)^" + keyword); err == nil { completion = reg.ReplaceAllString(suggestion, "") } return []string{completion, suggestion} } func (s *Suggestion) GetCandidateKeys(json *simplejson.Json, keyword string) []string { candidates := []string{} if _, err := json.Array(); err == nil { return []string{} } if keyword == "" { return getCurrentKeys(json) } reg, err := regexp.Compile("(?i)^" + keyword) if err != nil { return []string{} } for _, key := range getCurrentKeys(json) { if reg.MatchString(key) { candidates = append(candidates, key) } } return candidates } func getCurrentKeys(json *simplejson.Json) []string { keys := []string{} m, err := json.Map() if err != nil { return keys } for k := range m { keys = append(keys, k) } sort.Strings(keys) return keys } func (s *Suggestion) GetCurrentType(json *simplejson.Json) SuggestionDataType { if _, err := json.Array(); err == nil { return ARRAY } else if _, err = json.Map(); err == nil { return MAP } else if _, err = json.String(); err == nil { return STRING } return UNKNOWN } jid-0.7.3/suggestion_test.go000066400000000000000000000061221341361473600160550ustar00rootroot00000000000000package jid import ( "bytes" "github.com/bitly/go-simplejson" "github.com/stretchr/testify/assert" "io/ioutil" "testing" ) func TestNewSuggestion(t *testing.T) { var assert = assert.New(t) assert.Equal(NewSuggestion(), &Suggestion{}) } func TestSuggestionGet(t *testing.T) { var assert = assert.New(t) j := createJson(`{"name":"simeji-github"}`) s := NewSuggestion() j = createJson(`{"name":"simeji-github", "naming":"simeji", "nickname":"simejisimeji"}`) assert.Equal([]string{"m", "nam"}, s.Get(j, "na")) j = createJson(`{"abcde":"simeji-github", "abcdef":"simeji", "ab":"simejisimeji"}`) assert.Equal([]string{"", ""}, s.Get(j, "")) assert.Equal([]string{"b", "ab"}, s.Get(j, "a")) assert.Equal([]string{"de", "abcde"}, s.Get(j, "abc")) assert.Equal([]string{"", "abcde"}, s.Get(j, "abcde")) j = createJson(`["zero"]`) assert.Equal([]string{"[0]", "[0]"}, s.Get(j, "")) assert.Equal([]string{"0]", "[0]"}, s.Get(j, "[")) assert.Equal([]string{"]", "[0]"}, s.Get(j, "[0")) j = createJson(`["zero", "one"]`) assert.Equal([]string{"[", "["}, s.Get(j, "")) assert.Equal([]string{"", "["}, s.Get(j, "[")) assert.Equal([]string{"]", "[0]"}, s.Get(j, "[0")) j = createJson(`{"Abcabc":"simeji-github", "Abcdef":"simeji"}`) assert.Equal([]string{"bc", "Abc"}, s.Get(j, "a")) assert.Equal([]string{"c", "Abc"}, s.Get(j, "ab")) j = createJson(`{"RootDeviceNames":"simeji-github", "RootDeviceType":"simeji"}`) assert.Equal([]string{"ootDevice", "RootDevice"}, s.Get(j, "r")) assert.Equal([]string{"ootDevice", "RootDevice"}, s.Get(j, "R")) } func TestSuggestionGetCurrentType(t *testing.T) { var assert = assert.New(t) s := NewSuggestion() j := createJson(`[1,2,3]`) assert.Equal(ARRAY, s.GetCurrentType(j)) j = createJson(`{"name":[1,2,3], "naming":{"account":"simeji"}, "test":"simeji", "testing":"ok"}`) assert.Equal(MAP, s.GetCurrentType(j)) j = createJson(`"name"`) assert.Equal(STRING, s.GetCurrentType(j)) j = createJson("1") assert.Equal(UNKNOWN, s.GetCurrentType(j)) } func TestSuggestionGetCandidateKeys(t *testing.T) { var assert = assert.New(t) j := createJson(`{"naming":"simeji", "nickname":"simejisimeji", "city":"tokyo", "name":"simeji-github" }`) s := NewSuggestion() assert.Equal([]string{"city", "name", "naming", "nickname"}, s.GetCandidateKeys(j, "")) assert.Equal([]string{"name", "naming", "nickname"}, s.GetCandidateKeys(j, "n")) assert.Equal([]string{"name", "naming"}, s.GetCandidateKeys(j, "na")) assert.Equal([]string{}, s.GetCandidateKeys(j, "nana")) j = createJson(`{"abcde":"simeji-github", "abcdef":"simeji", "ab":"simejisimeji"}`) assert.Equal([]string{"abcde", "abcdef"}, s.GetCandidateKeys(j, "abcde")) j = createJson(`{"name":"simeji-github"}`) assert.Equal([]string{"name"}, s.GetCandidateKeys(j, "")) j = createJson(`{"n":"simeji-github"}`) assert.Equal([]string{"n"}, s.GetCandidateKeys(j, "")) j = createJson(`[1,2,"aa"]`) s = NewSuggestion() assert.Equal([]string{}, s.GetCandidateKeys(j, "[")) } func createJson(s string) *simplejson.Json { r := bytes.NewBufferString(s) buf, _ := ioutil.ReadAll(r) j, _ := simplejson.NewJson(buf) return j } jid-0.7.3/terminal.go000066400000000000000000000132331341361473600144430ustar00rootroot00000000000000package jid import ( "fmt" "io/ioutil" "regexp" "strings" "github.com/mattn/go-runewidth" "github.com/nsf/termbox-go" "github.com/nwidger/jsoncolor" ) type Terminal struct { defaultY int prompt string formatter *jsoncolor.Formatter monochrome bool outputArea *[][]termbox.Cell } type TerminalDrawAttributes struct { Query string Contents []string CandidateIndex int ContentsOffsetY int Complete string Candidates []string CursorOffset int } func NewTerminal(prompt string, defaultY int, monochrome bool) *Terminal { t := &Terminal{ prompt: prompt, defaultY: defaultY, monochrome: monochrome, outputArea: &[][]termbox.Cell{}, formatter: nil, } if !monochrome { t.formatter = t.initColorizeFormatter() } return t } func (t *Terminal) Draw(attr *TerminalDrawAttributes) error { query := attr.Query complete := attr.Complete rows := attr.Contents candidates := attr.Candidates candidateidx := attr.CandidateIndex contentOffsetY := attr.ContentsOffsetY termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) y := t.defaultY t.drawFilterLine(query, complete) if len(candidates) > 0 { y = t.drawCandidates(0, t.defaultY, candidateidx, candidates) } cellsArr, err := t.rowsToCells(rows) if err != nil { return err } for idx, cells := range cellsArr { if i := idx - contentOffsetY; i >= 0 { t.drawCells(0, i+y, cells) } } termbox.SetCursor(len(t.prompt)+attr.CursorOffset, 0) termbox.Flush() return nil } func (t *Terminal) drawFilterLine(qs string, complete string) error { fs := t.prompt + qs cs := complete str := fs + cs color := termbox.ColorDefault backgroundColor := termbox.ColorDefault var cells []termbox.Cell match := []int{len(fs), len(fs + cs)} var c termbox.Attribute for i, s := range str { c = color if i >= match[0] && i < match[1] { c = termbox.ColorGreen } cells = append(cells, termbox.Cell{ Ch: s, Fg: c, Bg: backgroundColor, }) } t.drawCells(0, 0, cells) return nil } type termboxSprintfFuncer struct { fg termbox.Attribute bg termbox.Attribute outputArea *[][]termbox.Cell } func (tsf *termboxSprintfFuncer) SprintfFunc() func(format string, a ...interface{}) string { return func(format string, a ...interface{}) string { cells := tsf.outputArea idx := len(*cells) - 1 str := fmt.Sprintf(format, a...) for _, s := range str { if s == '\n' { *cells = append(*cells, []termbox.Cell{}) idx++ continue } (*cells)[idx] = append((*cells)[idx], termbox.Cell{ Ch: s, Fg: tsf.fg, Bg: tsf.bg, }) } return "dummy" } } func (t *Terminal) initColorizeFormatter() *jsoncolor.Formatter { formatter := jsoncolor.NewFormatter() regular := &termboxSprintfFuncer{ fg: termbox.ColorDefault, bg: termbox.ColorDefault, outputArea: t.outputArea, } bold := &termboxSprintfFuncer{ fg: termbox.AttrBold, bg: termbox.ColorDefault, outputArea: t.outputArea, } blueBold := &termboxSprintfFuncer{ fg: termbox.ColorBlue | termbox.AttrBold, bg: termbox.ColorDefault, outputArea: t.outputArea, } green := &termboxSprintfFuncer{ fg: termbox.ColorGreen, bg: termbox.ColorDefault, outputArea: t.outputArea, } blackBold := &termboxSprintfFuncer{ fg: termbox.ColorBlack | termbox.AttrBold, bg: termbox.ColorDefault, outputArea: t.outputArea, } formatter.SpaceColor = regular formatter.CommaColor = bold formatter.ColonColor = bold formatter.ObjectColor = bold formatter.ArrayColor = bold formatter.FieldQuoteColor = blueBold formatter.FieldColor = blueBold formatter.StringQuoteColor = green formatter.StringColor = green formatter.TrueColor = regular formatter.FalseColor = regular formatter.NumberColor = regular formatter.NullColor = blackBold return formatter } func (t *Terminal) rowsToCells(rows []string) ([][]termbox.Cell, error) { *t.outputArea = [][]termbox.Cell{[]termbox.Cell{}} var err error if t.formatter != nil { err = t.formatter.Format(ioutil.Discard, []byte(strings.Join(rows, "\n"))) } cells := *t.outputArea if err != nil || t.monochrome { cells = [][]termbox.Cell{} for _, row := range rows { var cls []termbox.Cell for _, char := range row { cls = append(cls, termbox.Cell{ Ch: char, Fg: termbox.ColorDefault, Bg: termbox.ColorDefault, }) } cells = append(cells, cls) } } return cells, nil } func (t *Terminal) drawCells(x int, y int, cells []termbox.Cell) { i := 0 for _, c := range cells { termbox.SetCell(x+i, y, c.Ch, c.Fg, c.Bg) w := runewidth.RuneWidth(c.Ch) if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(c.Ch) { w = 1 } i += w } } func (t *Terminal) drawCandidates(x int, y int, index int, candidates []string) int { color := termbox.ColorBlack backgroundColor := termbox.ColorWhite w, _ := termbox.Size() ss := candidates[index] re := regexp.MustCompile("[[:space:]]" + ss + "[[:space:]]") var rows []string var str string for _, word := range candidates { combine := " " if l := len(str); l+len(word)+1 >= w { rows = append(rows, str+" ") str = "" } str += combine + word } rows = append(rows, str+" ") for i, row := range rows { match := re.FindStringIndex(row) var c termbox.Attribute ii := 0 for k, s := range row { c = color backgroundColor = termbox.ColorMagenta if match != nil && k >= match[0]+1 && k < match[1]-1 { backgroundColor = termbox.ColorWhite } termbox.SetCell(x+ii, y+i, s, c, backgroundColor) w := runewidth.RuneWidth(s) if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(s) { w = 1 } ii += w } } return y + len(rows) }