Repository: simeji/jid
Branch: master
Commit: 2968365762bd
Files: 17
Total size: 71.7 KB
Directory structure:
gitextract_eacgd7ye/
├── .circleci/
│ └── config.yml
├── .gitignore
├── .goreleaser.yml
├── ChangeLog
├── LICENSE
├── README.md
├── engine.go
├── engine_test.go
├── go.mod
├── go.sum
├── json_manager.go
├── json_manager_test.go
├── query.go
├── query_test.go
├── suggestion.go
├── suggestion_test.go
└── terminal.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
version: 2.1
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.1
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]+)*(-.*)*/
================================================
FILE: .gitignore
================================================
# 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
.idea
================================================
FILE: .goreleaser.yml
================================================
# 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
================================================
FILE: ChangeLog
================================================
ChangeLog
=======
### 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
================================================
FILE: LICENSE
================================================
The 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.
================================================
FILE: README.md
================================================
# jid
[](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

## Installation
* [With HomeBrew (for macOS)](#with-homebrew-for-macos)
* [With MacPorts (for macOS)](#with-macports-for-macos)
* [With pkg (for FreeBSD)](#with-pkg-for-freebsd)
* [With scoop (for Windows)](#with-scoop-for-windows)
* [Other package management system](#other-package-management-systems)
* [Simply use "jid" command](#simply-use-jid-command)
* [Build](#build)
### With HomeBrew (for macOS)
```
brew install jid
```
### With MacPorts (for macOS)
```
sudo port install jid
```
### With pkg (for FreeBSD)
```
pkg install jid
```
### With scoop (for Windows)
```
scoop install jid
```
### Other package management systems
Jid can install by package management systems of below OS.
[](https://repology.org/metapackage/jid/versions)
### 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 install github.com/simeji/jid/cmd/jid@latest
```
## 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))

#### 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` + `N`|Scroll json buffer 'Page Down'|
|`CTRL` + `P`|Scroll json buffer 'Page Up'|
|`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|
================================================
FILE: engine.go
================================================
package jid
import (
"io"
"strings"
termbox "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.KeyCtrlN:
_, h := termbox.Size()
e.scrollPageDown(len(contents), h)
case termbox.KeyCtrlP:
_, h := termbox.Size()
e.scrollPageUp(h)
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) scrollPageDown(rownum int, height int) {
co := rownum - 1
if o := rownum - e.contentOffset; o > height {
co = e.contentOffset + (height - DefaultY)
}
e.contentOffset = co
}
func (e *Engine) scrollPageUp(height int) {
co := 0
if o := e.contentOffset - (height - DefaultY); o > 0 {
co = o
}
e.contentOffset = co
}
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()
}
================================================
FILE: engine_test.go
================================================
package 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 TestScrollPageUpDown(t *testing.T) {
var assert = assert.New(t)
e := getEngine(`{"named":"go","NameTest":[1,2,3]}`, "")
cl := len(e.getContents())
// Then DefaultY = 1
e.scrollPageDown(cl, 3)
assert.Equal(2, e.contentOffset)
e.scrollPageDown(cl, 3)
assert.Equal(4, e.contentOffset)
e.scrollPageUp(3)
assert.Equal(2, e.contentOffset)
// term height changed
e.scrollPageDown(cl, 5)
assert.Equal(6, e.contentOffset)
e.scrollPageDown(cl, 5)
assert.Equal(7, e.contentOffset)
e.scrollPageDown(cl, 5)
assert.Equal(7, e.contentOffset)
e.scrollPageUp(5)
assert.Equal(3, e.contentOffset)
e.scrollPageUp(5)
assert.Equal(0, e.contentOffset)
e.scrollPageUp(5)
assert.Equal(0, e.contentOffset)
// term height > content size + default Y (a filter query line)
e.scrollPageDown(cl, 10)
assert.Equal(7, 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
}
================================================
FILE: go.mod
================================================
module github.com/simeji/jid
go 1.20
require (
github.com/bitly/go-simplejson v0.5.0
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
github.com/stretchr/testify v1.8.1
)
require (
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.10.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: go.sum
================================================
github.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/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: json_manager.go
================================================
package jid
import (
"github.com/bitly/go-simplejson"
"github.com/pkg/errors"
"io"
"regexp"
"strconv"
//"strings"
)
type JsonManager struct {
current *simplejson.Json
origin *simplejson.Json
suggestion *Suggestion
}
func NewJsonManager(reader io.Reader) (*JsonManager, error) {
buf, err := io.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
}
}
================================================
FILE: json_manager_test.go
================================================
package jid
import (
"bytes"
"io"
"testing"
simplejson "github.com/bitly/go-simplejson"
"github.com/stretchr/testify/assert"
)
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, _ := io.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, _ := io.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, _ = io.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, _ = io.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, _ = io.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, _ = io.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))
// case 7 key contains '.'
rr = bytes.NewBufferString(`{"na.me":"go","age":20}`)
buf, _ = io.ReadAll(rr)
sj, _ = simplejson.NewJson(buf)
d, _ = getItem(sj, "na.me")
result, _ = d.Encode()
assert.Equal(`"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 TestGetFilteredDataWithContainDots(t *testing.T) {
var assert = assert.New(t)
// data
data := `{"abc.de":"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(`.\"abc.de\"`)
result, s, c, err := jm.GetFilteredData(q, false)
assert.Nil(err)
d, _ := result.Encode()
assert.Equal(`"2AA2"`, string(d))
assert.Equal([]string{``, ``}, s)
assert.Equal([]string{}, c)
// case 2
q = NewQueryWithString(`."abc.de"`)
result, s, c, err = jm.GetFilteredData(q, false)
assert.Nil(err)
d, _ = result.Encode()
assert.Equal(`null`, string(d))
assert.Equal([]string{``, ``}, s)
assert.Equal([]string{}, c)
// case 3
q = NewQueryWithString(`.abc.de`)
result, s, c, err = jm.GetFilteredData(q, false)
assert.Nil(err)
d, _ = result.Encode()
assert.Equal(`null`, string(d))
assert.Equal([]string{"", ""}, s)
assert.Equal([]string{}, 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, _ := io.ReadAll(r)
sj, _ := simplejson.NewJson(buf)
keys := getCurrentKeys(sj)
assert.Equal([]string{"age", "name", "weight"}, keys)
r = bytes.NewBufferString(`[2,3,"aa"]`)
buf, _ = io.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, _ := io.ReadAll(r)
sj, _ := simplejson.NewJson(buf)
assert.Equal(false, isEmptyJson(sj))
assert.Equal(true, isEmptyJson(&simplejson.Json{}))
}
================================================
FILE: query.go
================================================
package jid
import (
"regexp"
"strings"
runewidth "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 {
qq := *q.query
if qq == nil || string(qq) == "" {
return [][]rune{}
}
splitQuery := []string{}
rr := []rune{}
enclosed := true
ql := len(*q.query)
for i := 0; i < ql; i++ {
r := qq[i]
if ii := i + 1; r == '\\' && ql > ii && qq[ii] == '"' {
enclosed = !enclosed
i++ // skip '"(double quortation)'
continue
}
if enclosed && r == '.' {
splitQuery = append(splitQuery, string(rr))
rr = []rune{}
} else {
rr = append(rr, r)
}
}
if rr != nil {
v := []string{string(rr)}
if !enclosed {
v = strings.Split(string(rr), ".")
}
splitQuery = append(splitQuery, v...)
}
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) {
keyword := q.GetLastKeyword()
nq := string(keyword)
qq := q.StringGet()
for _, r := range keyword {
if r == '.' {
nq = `\"` + string(keyword) + `\"`
break
}
}
re := regexp.MustCompile(`(\.)?(\\")?` + regexp.QuoteMeta(nq) + "$")
qq = re.ReplaceAllString(qq, "")
query := q.Set([]rune(qq))
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
}
================================================
FILE: query_test.go
================================================
package jid
import (
"testing"
"github.com/stretchr/testify/assert"
)
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([][]rune{
[]rune("test"),
[]rune("name"),
}, q.GetKeywords())
v = []rune("")
q = NewQuery(v)
assert.Equal([][]rune{}, q.GetKeywords())
v = []rune(".test.name.")
q = NewQuery(v)
assert.Equal([][]rune{
[]rune("test"),
[]rune("name"),
[]rune(""),
}, q.GetKeywords())
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 TestGetKeywordsWithDots(t *testing.T) {
var assert = assert.New(t)
v := []rune(`.test.\"na.me\"`)
q := NewQuery(v)
assert.Equal([][]rune{
[]rune("test"),
[]rune("na.me"),
}, q.GetKeywords())
v = []rune(`.test.\"na.me\`)
q = NewQuery(v)
assert.Equal([][]rune{
[]rune("test"),
[]rune("na"),
[]rune(`me\`),
}, q.GetKeywords())
}
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([]rune("name"), k)
assert.Equal([]rune(".test"), query)
assert.Equal([]rune(".test"), q.Get())
v = []rune(".a[0")
q = NewQuery(v)
k, query = q.PopKeyword()
assert.Equal([]rune("[0"), k)
assert.Equal([]rune(".a"), query)
assert.Equal([]rune(".a"), q.Get())
k, query = q.PopKeyword()
assert.Equal([]rune("a"), k)
assert.Equal([]rune(""), query)
assert.Equal([]rune(""), q.Get())
v = []rune(".")
q = NewQuery(v)
k, query = q.PopKeyword()
assert.Equal([]rune(""), k)
assert.Equal([]rune(""), query)
assert.Equal([]rune(""), q.Get())
v = []rune(".test.name.")
q = NewQuery(v)
k, query = q.PopKeyword()
assert.Equal([]rune(""), k)
assert.Equal([]rune(".test.name"), query)
assert.Equal([]rune(".test.name"), q.Get())
v = []rune(`.name.\"te.st\"`)
q = NewQuery(v)
k, query = q.PopKeyword()
assert.Equal([]rune("te.st"), k)
assert.Equal([]rune(".name"), query)
assert.Equal([]rune(".name"), q.Get())
v = []rune(`.name.\"te.st\".hoge`)
q = NewQuery(v)
k, query = q.PopKeyword()
assert.Equal([]rune("hoge"), k)
assert.Equal([]rune(`.name.\"te.st\"`), query)
assert.Equal([]rune(`.name.\"te.st\"`), q.Get())
v = []rune(`.name.\"te`)
q = NewQuery(v)
k, query = q.PopKeyword()
assert.Equal([]rune(`te`), k)
assert.Equal([]rune(`.name`), query)
assert.Equal([]rune(`.name`), q.Get())
}
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"))
}
================================================
FILE: suggestion.go
================================================
package jid
import (
"regexp"
"sort"
"strings"
simplejson "github.com/bitly/go-simplejson"
)
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 {
kk := []string{}
m, err := json.Map()
if err != nil {
return kk
}
for k := range m {
kk = append(kk, k)
}
sort.Strings(kk)
keys := []string{}
for _, k := range kk {
if strings.Contains(k, ".") {
var sb strings.Builder
sb.Grow(len(k) + 4)
sb.WriteString(`\"`)
sb.WriteString(k)
sb.WriteString(`\"`)
k = sb.String()
}
keys = append(keys, k)
}
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
}
================================================
FILE: suggestion_test.go
================================================
package jid
import (
"bytes"
"io"
"testing"
simplejson "github.com/bitly/go-simplejson"
"github.com/stretchr/testify/assert"
)
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 TestSuggestionGetCandidateKeysWithDots(t *testing.T) {
var assert = assert.New(t)
j := createJson(`{"nam.ing":"simeji", "nickname":"simejisimeji", "city":"tokyo", "name":"simeji-github" }`)
s := NewSuggestion()
assert.Equal([]string{"city", `\"nam.ing\"`, "name", "nickname"}, s.GetCandidateKeys(j, ""))
assert.Equal([]string{`\"nam.ing\"`, "name", "nickname"}, s.GetCandidateKeys(j, "n"))
}
func createJson(s string) *simplejson.Json {
r := bytes.NewBufferString(s)
buf, _ := io.ReadAll(r)
j, _ := simplejson.NewJson(buf)
return j
}
================================================
FILE: terminal.go
================================================
package jid
import (
"fmt"
"io"
"regexp"
"strings"
runewidth "github.com/mattn/go-runewidth"
termbox "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
_, h := termbox.Size()
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 {
i := idx - contentOffsetY
if i >= 0 {
t.drawCells(0, i+y, cells)
}
if i > h {
break
}
}
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(io.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:]]" + regexp.QuoteMeta(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)
}
gitextract_eacgd7ye/ ├── .circleci/ │ └── config.yml ├── .gitignore ├── .goreleaser.yml ├── ChangeLog ├── LICENSE ├── README.md ├── engine.go ├── engine_test.go ├── go.mod ├── go.sum ├── json_manager.go ├── json_manager_test.go ├── query.go ├── query_test.go ├── suggestion.go ├── suggestion_test.go └── terminal.go
SYMBOL INDEX (154 symbols across 9 files)
FILE: engine.go
constant DefaultY (line 11) | DefaultY int = 1
constant FilterPrompt (line 12) | FilterPrompt string = "[Filter]> "
type EngineInterface (line 15) | type EngineInterface interface
type EngineResultInterface (line 20) | type EngineResultInterface interface
type Engine (line 26) | type Engine struct
method GetQuery (line 86) | func (e *Engine) GetQuery() QueryInterface {
method Run (line 90) | func (e *Engine) Run() EngineResultInterface {
method getContents (line 197) | func (e *Engine) getContents() []string {
method setCandidateData (line 209) | func (e *Engine) setCandidateData() {
method confirmCandidate (line 223) | func (e *Engine) confirmCandidate() {
method deleteChar (line 231) | func (e *Engine) deleteChar() {
method deleteLineQuery (line 239) | func (e *Engine) deleteLineQuery() {
method scrollToBelow (line 244) | func (e *Engine) scrollToBelow() {
method scrollToAbove (line 248) | func (e *Engine) scrollToAbove() {
method scrollToBottom (line 254) | func (e *Engine) scrollToBottom(rownum int) {
method scrollToTop (line 258) | func (e *Engine) scrollToTop() {
method scrollPageDown (line 262) | func (e *Engine) scrollPageDown(rownum int, height int) {
method scrollPageUp (line 270) | func (e *Engine) scrollPageUp(height int) {
method toggleKeymode (line 278) | func (e *Engine) toggleKeymode() {
method deleteWordBackward (line 281) | func (e *Engine) deleteWordBackward() {
method tabAction (line 287) | func (e *Engine) tabAction() {
method escapeCandidateMode (line 303) | func (e *Engine) escapeCandidateMode() {
method inputChar (line 306) | func (e *Engine) inputChar(ch rune) {
method moveCursorBackward (line 311) | func (e *Engine) moveCursorBackward() {
method moveCursorForward (line 317) | func (e *Engine) moveCursorForward() {
method moveCursorWordBackwark (line 323) | func (e *Engine) moveCursorWordBackwark() {
method moveCursorWordForward (line 325) | func (e *Engine) moveCursorWordForward() {
method moveCursorToTop (line 327) | func (e *Engine) moveCursorToTop() {
method moveCursorToEnd (line 330) | func (e *Engine) moveCursorToEnd() {
type EngineAttribute (line 41) | type EngineAttribute struct
function NewEngine (line 47) | func NewEngine(s io.Reader, ea *EngineAttribute) (EngineInterface, error) {
type EngineResult (line 69) | type EngineResult struct
method GetQueryString (line 75) | func (er *EngineResult) GetQueryString() string {
method GetContent (line 79) | func (er *EngineResult) GetContent() string {
method GetError (line 82) | func (er *EngineResult) GetError() error {
FILE: engine_test.go
function TestNewEngine (line 11) | func TestNewEngine(t *testing.T) {
function TestNewEngineWithQuery (line 28) | func TestNewEngineWithQuery(t *testing.T) {
function TestDeleteChar (line 43) | func TestDeleteChar(t *testing.T) {
function TestDeleteWordBackward (line 54) | func TestDeleteWordBackward(t *testing.T) {
function TestDeleteLineQuery (line 74) | func TestDeleteLineQuery(t *testing.T) {
function TestScrollToAbove (line 84) | func TestScrollToAbove(t *testing.T) {
function TestScrollToBelow (line 97) | func TestScrollToBelow(t *testing.T) {
function TestScrollToBottomAndTop (line 106) | func TestScrollToBottomAndTop(t *testing.T) {
function TestScrollPageUpDown (line 117) | func TestScrollPageUpDown(t *testing.T) {
function TestGetContents (line 154) | func TestGetContents(t *testing.T) {
function TestSetCandidateData (line 177) | func TestSetCandidateData(t *testing.T) {
function TestConfirmCandidate (line 238) | func TestConfirmCandidate(t *testing.T) {
function TestCtrllAction (line 269) | func TestCtrllAction(t *testing.T) {
function TestTabAction (line 279) | func TestTabAction(t *testing.T) {
function TestEscAction (line 300) | func TestEscAction(t *testing.T) {
function TestInputChar (line 311) | func TestInputChar(t *testing.T) {
function TestMoveCursorForwardAndBackward (line 327) | func TestMoveCursorForwardAndBackward(t *testing.T) {
function TestMoveCursorToTopAndEnd (line 351) | func TestMoveCursorToTopAndEnd(t *testing.T) {
function getEngine (line 363) | func getEngine(j string, qs string) *Engine {
FILE: json_manager.go
type JsonManager (line 12) | type JsonManager struct
method Get (line 40) | func (jm *JsonManager) Get(q QueryInterface, confirm bool) (string, []...
method GetPretty (line 52) | func (jm *JsonManager) GetPretty(q QueryInterface, confirm bool) (stri...
method GetFilteredData (line 61) | func (jm *JsonManager) GetFilteredData(q QueryInterface, confirm bool)...
method GetCandidateKeys (line 99) | func (jm *JsonManager) GetCandidateKeys(q QueryInterface) []string {
function NewJsonManager (line 18) | func NewJsonManager(reader io.Reader) (*JsonManager, error) {
function getItem (line 103) | func getItem(json *simplejson.Json, s string) (*simplejson.Json, bool) {
function isEmptyJson (line 132) | func isEmptyJson(j *simplejson.Json) bool {
FILE: json_manager_test.go
function TestNewJson (line 12) | func TestNewJson(t *testing.T) {
function TestNewJsonWithError (line 32) | func TestNewJsonWithError(t *testing.T) {
function TestGet (line 42) | func TestGet(t *testing.T) {
function TestGetPretty (line 113) | func TestGetPretty(t *testing.T) {
function TestGetItem (line 125) | func TestGetItem(t *testing.T) {
function TestGetFilteredData (line 200) | func TestGetFilteredData(t *testing.T) {
function TestGetFilteredDataWithMatchQuery (line 336) | func TestGetFilteredDataWithMatchQuery(t *testing.T) {
function TestGetFilteredDataWithContainDots (line 376) | func TestGetFilteredDataWithContainDots(t *testing.T) {
function TestGetCandidateKeys (line 412) | func TestGetCandidateKeys(t *testing.T) {
function TestGetCurrentKeys (line 440) | func TestGetCurrentKeys(t *testing.T) {
function TestIsEmptyJson (line 457) | func TestIsEmptyJson(t *testing.T) {
FILE: query.go
type QueryInterface (line 10) | type QueryInterface interface
type Query (line 32) | type Query struct
method Get (line 49) | func (q *Query) Get() []rune {
method GetChar (line 53) | func (q *Query) GetChar(idx int) rune {
method Length (line 62) | func (q *Query) Length() int {
method IndexOffset (line 66) | func (q *Query) IndexOffset(i int) int {
method Set (line 76) | func (q *Query) Set(query []rune) []rune {
method Insert (line 83) | func (q *Query) Insert(query []rune, idx int) []rune {
method StringInsert (line 95) | func (q *Query) StringInsert(query string, idx int) string {
method Add (line 99) | func (q *Query) Add(query []rune) []rune {
method Delete (line 103) | func (q *Query) Delete(i int) []rune {
method Clear (line 126) | func (q *Query) Clear() []rune {
method GetKeywords (line 130) | func (q *Query) GetKeywords() [][]rune {
method GetLastKeyword (line 185) | func (q *Query) GetLastKeyword() []rune {
method StringGetLastKeyword (line 193) | func (q *Query) StringGetLastKeyword() string {
method PopKeyword (line 197) | func (q *Query) PopKeyword() ([]rune, []rune) {
method StringGet (line 216) | func (q *Query) StringGet() string {
method StringSet (line 220) | func (q *Query) StringSet(query string) string {
method StringAdd (line 224) | func (q *Query) StringAdd(query string) string {
method StringGetKeywords (line 228) | func (q *Query) StringGetKeywords() []string {
method StringPopKeyword (line 236) | func (q *Query) StringPopKeyword() (string, []rune) {
function NewQuery (line 37) | func NewQuery(query []rune) *Query {
function NewQueryWithString (line 45) | func NewQueryWithString(query string) *Query {
function validate (line 241) | func validate(r []rune) bool {
FILE: query_test.go
function TestValidate (line 9) | func TestValidate(t *testing.T) {
function TestNewQuery (line 28) | func TestNewQuery(t *testing.T) {
function TestNewQueryWithInvalidQuery (line 38) | func TestNewQueryWithInvalidQuery(t *testing.T) {
function TestNewQueryWithString (line 48) | func TestNewQueryWithString(t *testing.T) {
function TestNewQueryWithStringWithInvalidQuery (line 57) | func TestNewQueryWithStringWithInvalidQuery(t *testing.T) {
function TestQueryGet (line 66) | func TestQueryGet(t *testing.T) {
function TestQueryLength (line 75) | func TestQueryLength(t *testing.T) {
function TestQueryIndexOffsetN (line 89) | func TestQueryIndexOffsetN(t *testing.T) {
function TestQueryGetChar (line 109) | func TestQueryGetChar(t *testing.T) {
function TestQuerySet (line 131) | func TestQuerySet(t *testing.T) {
function TestQuerySetWithInvalidQuery (line 141) | func TestQuerySetWithInvalidQuery(t *testing.T) {
function TestQueryAdd (line 150) | func TestQueryAdd(t *testing.T) {
function TestQueryInsert (line 158) | func TestQueryInsert(t *testing.T) {
function TestQueryStringInsert (line 171) | func TestQueryStringInsert(t *testing.T) {
function TestQueryClear (line 184) | func TestQueryClear(t *testing.T) {
function TestQueryDelete (line 193) | func TestQueryDelete(t *testing.T) {
function TestGetKeywords (line 225) | func TestGetKeywords(t *testing.T) {
function TestGetKeywordsWithDots (line 300) | func TestGetKeywordsWithDots(t *testing.T) {
function TestGetLastKeyword (line 320) | func TestGetLastKeyword(t *testing.T) {
function TestStringGetLastKeyword (line 340) | func TestStringGetLastKeyword(t *testing.T) {
function TestPopKeyword (line 360) | func TestPopKeyword(t *testing.T) {
function TestQueryStringGet (line 418) | func TestQueryStringGet(t *testing.T) {
function TestQueryStringSet (line 427) | func TestQueryStringSet(t *testing.T) {
function TestQueryStringAdd (line 436) | func TestQueryStringAdd(t *testing.T) {
function TestStringGetKeywords (line 445) | func TestStringGetKeywords(t *testing.T) {
function TestStringPopKeyword (line 469) | func TestStringPopKeyword(t *testing.T) {
FILE: suggestion.go
type SuggestionInterface (line 11) | type SuggestionInterface interface
type SuggestionDataType (line 16) | type SuggestionDataType
constant UNKNOWN (line 19) | UNKNOWN SuggestionDataType = iota
constant ARRAY (line 20) | ARRAY
constant MAP (line 21) | MAP
constant NUMBER (line 22) | NUMBER
constant STRING (line 23) | STRING
constant BOOL (line 24) | BOOL
type Suggestion (line 27) | type Suggestion struct
method Get (line 34) | func (s *Suggestion) Get(json *simplejson.Json, keyword string) []stri...
method GetCandidateKeys (line 90) | func (s *Suggestion) GetCandidateKeys(json *simplejson.Json, keyword s...
method GetCurrentType (line 141) | func (s *Suggestion) GetCurrentType(json *simplejson.Json) SuggestionD...
function NewSuggestion (line 30) | func NewSuggestion() *Suggestion {
function getCurrentKeys (line 113) | func getCurrentKeys(json *simplejson.Json) []string {
FILE: suggestion_test.go
function TestNewSuggestion (line 12) | func TestNewSuggestion(t *testing.T) {
function TestSuggestionGet (line 17) | func TestSuggestionGet(t *testing.T) {
function TestSuggestionGetCurrentType (line 50) | func TestSuggestionGetCurrentType(t *testing.T) {
function TestSuggestionGetCandidateKeys (line 64) | func TestSuggestionGetCandidateKeys(t *testing.T) {
function TestSuggestionGetCandidateKeysWithDots (line 87) | func TestSuggestionGetCandidateKeysWithDots(t *testing.T) {
function createJson (line 96) | func createJson(s string) *simplejson.Json {
FILE: terminal.go
type Terminal (line 14) | type Terminal struct
method Draw (line 46) | func (t *Terminal) Draw(attr *TerminalDrawAttributes) error {
method drawFilterLine (line 87) | func (t *Terminal) drawFilterLine(qs string, complete string) error {
method initColorizeFormatter (line 141) | func (t *Terminal) initColorizeFormatter() *jsoncolor.Formatter {
method rowsToCells (line 191) | func (t *Terminal) rowsToCells(rows []string) ([][]termbox.Cell, error) {
method drawCells (line 220) | func (t *Terminal) drawCells(x int, y int, cells []termbox.Cell) {
method drawCandidates (line 234) | func (t *Terminal) drawCandidates(x int, y int, index int, candidates ...
type TerminalDrawAttributes (line 22) | type TerminalDrawAttributes struct
function NewTerminal (line 32) | func NewTerminal(prompt string, defaultY int, monochrome bool) *Terminal {
type termboxSprintfFuncer (line 114) | type termboxSprintfFuncer struct
method SprintfFunc (line 120) | func (tsf *termboxSprintfFuncer) SprintfFunc() func(format string, a ....
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (82K chars).
[
{
"path": ".circleci/config.yml",
"chars": 716,
"preview": "version: 2.1\njobs:\n test:\n docker:\n - image: circleci/golang:latest\n steps:\n - checkout\n - run: go"
},
{
"path": ".gitignore",
"chars": 329,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
},
{
"path": ".goreleaser.yml",
"chars": 821,
"preview": "# This is an example goreleaser.yaml file with some sane defaults.\n# Make sure to check the documentation at http://gore"
},
{
"path": "ChangeLog",
"chars": 658,
"preview": "ChangeLog\n=======\n\n### 0.7.2 - Jan 23 2017\n\nBugFix:\n* fix version number\n\n### 0.7.1 - Jan 5 2017\n\nFeatures:\n* scroll to "
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 simeji\n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "README.md",
"chars": 3996,
"preview": "# jid\n\n[](https://circleci.com/gh/simeji/ji"
},
{
"path": "engine.go",
"chars": 6961,
"preview": "package jid\n\nimport (\n\t\"io\"\n\t\"strings\"\n\n\ttermbox \"github.com/nsf/termbox-go\"\n)\n\nconst (\n\tDefaultY int = 1\n\tFilter"
},
{
"path": "engine_test.go",
"chars": 9144,
"preview": "package jid\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewEngine(t *testing."
},
{
"path": "go.mod",
"chars": 763,
"preview": "module github.com/simeji/jid\n\ngo 1.20\n\nrequire (\n\tgithub.com/bitly/go-simplejson v0.5.0\n\tgithub.com/mattn/go-runewidth v"
},
{
"path": "go.sum",
"chars": 4073,
"preview": "github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=\ngithub.com/bitly/go-simplejson v0."
},
{
"path": "json_manager.go",
"chars": 3293,
"preview": "package jid\n\nimport (\n\t\"github.com/bitly/go-simplejson\"\n\t\"github.com/pkg/errors\"\n\t\"io\"\n\t\"regexp\"\n\t\"strconv\"\n\t//\"strings\""
},
{
"path": "json_manager_test.go",
"chars": 12692,
"preview": "package jid\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\tsimplejson \"github.com/bitly/go-simplejson\"\n\t\"github.com/stretchr/test"
},
{
"path": "query.go",
"chars": 5265,
"preview": "package jid\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\trunewidth \"github.com/mattn/go-runewidth\"\n)\n\ntype QueryInterface interface "
},
{
"path": "query_test.go",
"chars": 11307,
"preview": "package jid\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestValidate(t *testing.T) {\n\tvar assert"
},
{
"path": "suggestion.go",
"chars": 2941,
"preview": "package jid\n\nimport (\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\n\tsimplejson \"github.com/bitly/go-simplejson\"\n)\n\ntype SuggestionInter"
},
{
"path": "suggestion_test.go",
"chars": 3558,
"preview": "package jid\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\tsimplejson \"github.com/bitly/go-simplejson\"\n\t\"github.com/stretchr/test"
},
{
"path": "terminal.go",
"chars": 5863,
"preview": "package jid\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"regexp\"\n\t\"strings\"\n\n\trunewidth \"github.com/mattn/go-runewidth\"\n\ttermbox \"github.com"
}
]
About this extraction
This page contains the full source code of the simeji/jid GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (71.7 KB), approximately 23.5k tokens, and a symbol index with 154 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.