From de290bc7a9104bc3bedf2bdeb6fb8b2e7595279e Mon Sep 17 00:00:00 2001 From: simeji Date: Mon, 15 Aug 2016 03:08:49 +0900 Subject: [PATCH 1/5] refactoring: add Query module and add Test case --- engine.go | 87 +++++++++++++----------- engine_test.go | 34 ++++++++++ query.go | 103 ++++++++++++++++++++++++++++ query_test.go | 181 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 366 insertions(+), 39 deletions(-) create mode 100644 engine_test.go create mode 100644 query.go create mode 100644 query_test.go diff --git a/engine.go b/engine.go index 41f1e60..779e87e 100644 --- a/engine.go +++ b/engine.go @@ -1,13 +1,12 @@ package jig import ( - //"errors" "fmt" "github.com/bitly/go-simplejson" "github.com/nsf/termbox-go" + "io" "io/ioutil" "log" - "os" "regexp" "sort" "strconv" @@ -21,7 +20,6 @@ const ( ) var ( - f *[]rune complete *[]rune ) @@ -29,18 +27,23 @@ type Engine struct { json *simplejson.Json orgJson *simplejson.Json currentKeys []string - query bool + jq bool pretty bool + query *Query } -func NewEngine(s *os.File, q bool, p bool) *Engine { - j := parse(s) +func NewEngine(s io.Reader, q bool, p bool) *Engine { + j, err := parse(s) + if err != true { + return &Engine{} + } e := &Engine{ json: j, orgJson: j, currentKeys: []string{}, - query: q, + jq: q, pretty: p, + query: NewQuery([]rune("")), } return e } @@ -50,8 +53,8 @@ func (e Engine) Run() int { if !e.render(e.json) { return 2 } - if e.query { - fmt.Printf("%s", string(*f)) + if e.jq { + fmt.Printf("%s", e.query.StringGet()) } else if e.pretty { s, err := e.json.EncodePretty() if err != nil { @@ -68,20 +71,23 @@ func (e Engine) Run() int { return 0 } -func parse(content *os.File) *simplejson.Json { +func parse(content io.Reader) (*simplejson.Json, bool) { + res := true buf, err := ioutil.ReadAll(content) if err != nil { - log.Fatal(err) + log.Printf("Bad contents '", err, "'") + res = false } - js, err := simplejson.NewJson(buf) + j, err := simplejson.NewJson(buf) if err != nil { - log.Fatal(err) + log.Printf("Bad Json Format '", err, "'") + res = false } - return js + return j, res } // fix:me @@ -93,20 +99,22 @@ func (e *Engine) render(json *simplejson.Json) bool { } defer termbox.Close() - f = &[]rune{} complete = &[]rune{} contents := e.prettyContents() keymode := false for { - e.json = e.filterJson(string(*f)) + var flgFilter bool + e.json, flgFilter = e.filterJson(e.orgJson, e.query.StringGet()) e.currentKeys = e.getCurrentKeys(e.json) - e.suggest() + if !flgFilter { + e.suggest() + } if keymode { ckeys := []string{} - kws := strings.Split(string(*f), ".") - if lkw := kws[len(kws)-1]; lkw != "" { + kws := e.query.StringGetKeywords() + if lkw := kws[len(kws)-1]; lkw != "" && !flgFilter { for k, _ := range e.getFilteredCurrentKeys(e.json, lkw) { ckeys = append(ckeys, e.currentKeys[k]) } @@ -118,7 +126,7 @@ func (e *Engine) render(json *simplejson.Json) bool { } else { contents = e.prettyContents() } - draw(contents) + e.draw(contents) switch ev := termbox.PollEvent(); ev.Type { case termbox.EventKey: switch ev.Key { @@ -127,10 +135,10 @@ func (e *Engine) render(json *simplejson.Json) bool { case termbox.KeyCtrlK: keymode = !keymode case termbox.KeySpace: - *f = append(*f, rune(' ')) + _ = e.query.StringAdd(" ") case termbox.KeyCtrlW: //delete whole word to period - s := string(*f) + s := e.query.StringGet() kws := strings.Split(s, ".") lki := len(kws) - 1 var lk string @@ -139,12 +147,9 @@ func (e *Engine) render(json *simplejson.Json) bool { if lk != "" { s = s + "." } - *f = []rune(s[0:len(s)]) + _ = e.query.StringSet(s[0:len(s)]) case termbox.KeyBackspace, termbox.KeyBackspace2: - if i := len(*f) - 1; i >= 0 { - slice := *f - *f = slice[0:i] - } + _ = e.query.Delete(1) case termbox.KeyTab: if len(*complete) > 0 { e.autoComplete() @@ -154,7 +159,7 @@ func (e *Engine) render(json *simplejson.Json) bool { case termbox.KeyEnter: return true case 0: - *f = append(*f, rune(ev.Ch)) + _ = e.query.StringAdd(string(ev.Ch)) default: } case termbox.EventError: @@ -166,12 +171,12 @@ func (e *Engine) render(json *simplejson.Json) bool { } func (e *Engine) autoComplete() { - *f = append(*f, *complete...) + _ = e.query.Add(*complete) *complete = []rune("") } func (e *Engine) suggest() bool { - s := string(*f) + s := e.query.StringGet() if arr, _ := e.json.Array(); arr != nil { if l := len(s); l < 1 { *complete = []rune("") @@ -216,7 +221,7 @@ func (e *Engine) suggest() bool { *complete = []rune(kw) s = strings.Join(tkws, ".") + "." + v } - *f = []rune(s) + _ = e.query.StringSet(s) return true } else { var sw []rune @@ -247,7 +252,7 @@ func (e *Engine) suggest() bool { *complete = []rune(kw) s = strings.Join(tkws, ".") + "." + lkw } - *f = []rune(s) + _ = e.query.StringSet(s) return true } *complete = []rune("") @@ -276,16 +281,15 @@ func (e *Engine) prettyContents() []string { return strings.Split(string(s), "\n") } -func (e *Engine) filterJson(q string) *simplejson.Json { - json := e.orgJson +func (e *Engine) filterJson(json *simplejson.Json, q string) (*simplejson.Json, bool) { if len(q) < 1 { - return json + return json, false } keywords := strings.Split(q, ".") // check start "." if keywords[0] != "" { - return &simplejson.Json{} + return &simplejson.Json{}, false } keywords = keywords[1:] @@ -295,6 +299,8 @@ func (e *Engine) filterJson(q string) *simplejson.Json { lastIdx := len(keywords) - 1 + flgMatchLastKw := false + //eachFlg := false for ki, keyword := range keywords { if len(keyword) == 0 { @@ -324,6 +330,7 @@ func (e *Engine) filterJson(q string) *simplejson.Json { json = tj } else if !isEmptyJson(tj) { json = tj + flgMatchLastKw = true } lmi := len(matchIndexes) - 1 for idx, m := range matchIndexes { @@ -332,6 +339,7 @@ func (e *Engine) filterJson(q string) *simplejson.Json { //eachFlg = true } else if tj := json.GetIndex(i); !isEmptyJson(tj) { json = tj + flgMatchLastKw = true } } } else { @@ -343,10 +351,11 @@ func (e *Engine) filterJson(q string) *simplejson.Json { json = tj } else if !isEmptyJson(tj) { json = tj + flgMatchLastKw = true } } } - return json + return json, flgMatchLastKw } func (e *Engine) getCurrentKeys(json *simplejson.Json) []string { @@ -373,11 +382,11 @@ func isEmptyJson(j *simplejson.Json) bool { } } -func draw(rows []string) { +func (e *Engine) draw(rows []string) { termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) - fs := FilterPrompt + string(*f) + fs := FilterPrompt + e.query.StringGet() cs := string(*complete) drawln(0, 0, fs+cs, []([]int){[]int{len(fs), len(fs) + len(cs)}}) termbox.SetCursor(len(fs), 0) diff --git a/engine_test.go b/engine_test.go new file mode 100644 index 0000000..bfea131 --- /dev/null +++ b/engine_test.go @@ -0,0 +1,34 @@ +package jig + +import ( + "bytes" + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestNewEngine(t *testing.T) { + var assert = assert.New(t) + + f, _ := os.Create("/dev/null") + e := NewEngine(f, false, false) + assert.Equal(e, &Engine{}, "they should be equal") + + r := bytes.NewBufferString("{\"name\":\"go\"}") + e = NewEngine(r, false, false) + assert.NotEqual(e, &Engine{}, "they should be not equal") + assert.Equal(e.json.Get("name").MustString(), "go", "they should be equal") +} + +func TestParse(t *testing.T) { + var assert = assert.New(t) + + r := bytes.NewBufferString("{\"name\":\"go\"}") + + _, e := parse(r) + assert.True(e) + + r2 := bytes.NewBufferString("{\"name\":\"go\"") + _, e2 := parse(r2) + assert.False(e2) +} diff --git a/query.go b/query.go new file mode 100644 index 0000000..3e6f570 --- /dev/null +++ b/query.go @@ -0,0 +1,103 @@ +package jig + +import ( + "strings" +) + +type Query struct { + query *[]rune + complete *[]rune +} + +func NewQuery(query []rune) *Query { + return &Query{ + query: &query, + complete: &[]rune{}, + } +} + +func (q *Query) Get() []rune { + return *q.query +} + +func (q *Query) Set(query []rune) []rune { + q.query = &query + return q.Get() +} + +func (q *Query) Add(query []rune) []rune { + return q.Set(append(q.Get(), query...)) +} + +func (q *Query) Delete(i int) []rune { + qq := q.Get() + newLastIdx := len(qq) - i + if newLastIdx < 0 { + newLastIdx = 0 + } + return q.Set(qq[0:newLastIdx]) +} + +func (q *Query) Clear() []rune { + return q.Set([]rune("")) +} + +func (q *Query) GetKeywords() [][]rune { + query := string(*q.query) + + splitQuery := strings.Split(query, ".") + lastIdx := len(splitQuery) - 1 + + keywords := [][]rune{} + for i, keyword := range splitQuery { + if keyword != "" || i == lastIdx { + keywords = append(keywords, []rune(keyword)) + } + } + return keywords +} + +func (q *Query) GetLastKeyword() []rune { + keywords := q.GetKeywords() + return keywords[len(keywords)-1] +} + +func (q *Query) PopKeyword() ([]rune, []rune) { + lastSepIdx := 0 + for i, e := range *q.query { + if e == '.' { + lastSepIdx = i + } + } + qq := q.Get() + keyword := qq[lastSepIdx+1:] + query := q.Set(qq[0 : lastSepIdx+1]) + 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 { + query := string(*q.query) + + splitQuery := strings.Split(query, ".") + lastIdx := len(splitQuery) - 1 + + keywords := []string{} + for i, keyword := range splitQuery { + if keyword != "" || i == lastIdx { + keywords = append(keywords, keyword) + } + } + return keywords +} diff --git a/query_test.go b/query_test.go new file mode 100644 index 0000000..f3e5915 --- /dev/null +++ b/query_test.go @@ -0,0 +1,181 @@ +package jig + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +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 TestQueryGet(t *testing.T) { + var assert = assert.New(t) + + v := []rune("test") + q := NewQuery(v) + + assert.Equal(q.Get(), []rune("test")) +} + +func TestQuerySet(t *testing.T) { + var assert = assert.New(t) + + v := []rune("hello") + q := NewQuery(v) + + assert.Equal(q.Set([]rune("world")), []rune("world")) +} + +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 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(q.Delete(1), []rune("helloworl")) + assert.Equal(q.Delete(1), []rune("hellowor")) + assert.Equal(q.Delete(2), []rune("hellow")) + assert.Equal(q.Delete(8), []rune("")) +} + +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("test.name") + q = NewQuery(v) + assert.Equal(q.GetKeywords(), [][]rune{ + []rune("test"), + []rune("name"), + }) + + v = []rune("test...name") + q = NewQuery(v) + assert.Equal(q.GetKeywords(), [][]rune{ + []rune("test"), + []rune("name"), + }) + + 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(""), + }) +} + +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")) +} + +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", + }) +} From d5ee3e3be941e0886db448c9fd544f3790b9f1ac Mon Sep 17 00:00:00 2001 From: simeji Date: Sat, 20 Aug 2016 12:34:22 +0900 Subject: [PATCH 2/5] refactoring: Add json manager module improve query validation --- engine.go | 15 +-- exception.go | 19 ++++ json_manager.go | 152 +++++++++++++++++++++++++ json_manager_test.go | 263 +++++++++++++++++++++++++++++++++++++++++++ query.go | 106 ++++++++++++++--- query_test.go | 167 ++++++++++++++++++++++----- 6 files changed, 661 insertions(+), 61 deletions(-) create mode 100644 exception.go create mode 100644 json_manager.go create mode 100644 json_manager_test.go diff --git a/engine.go b/engine.go index 779e87e..c629bd9 100644 --- a/engine.go +++ b/engine.go @@ -6,7 +6,6 @@ import ( "github.com/nsf/termbox-go" "io" "io/ioutil" - "log" "regexp" "sort" "strconv" @@ -26,6 +25,7 @@ var ( type Engine struct { json *simplejson.Json orgJson *simplejson.Json + manager *JsonManager currentKeys []string jq bool pretty bool @@ -76,14 +76,14 @@ func parse(content io.Reader) (*simplejson.Json, bool) { buf, err := ioutil.ReadAll(content) if err != nil { - log.Printf("Bad contents '", err, "'") + //log.Printf("Bad contents '", err, "'") res = false } j, err := simplejson.NewJson(buf) if err != nil { - log.Printf("Bad Json Format '", err, "'") + //log.Printf("Bad Json Format '", err, "'") res = false } @@ -373,15 +373,6 @@ func (e *Engine) getCurrentKeys(json *simplejson.Json) []string { return keys } -func isEmptyJson(j *simplejson.Json) bool { - switch j.Interface().(type) { - case nil: - return true - default: - return false - } -} - func (e *Engine) draw(rows []string) { termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) diff --git a/exception.go b/exception.go new file mode 100644 index 0000000..223dc4d --- /dev/null +++ b/exception.go @@ -0,0 +1,19 @@ +package jig + +import ( + "fmt" +) + +type QueryParseError struct { + Message string +} + +func NewQueryParseError(s string) error { + return &QueryParseError{ + Message: s, + } +} + +func (e *QueryParseError) Error() string { + return fmt.Sprintf("Query parse error: %s", e.Message) +} diff --git a/json_manager.go b/json_manager.go new file mode 100644 index 0000000..7d2e970 --- /dev/null +++ b/json_manager.go @@ -0,0 +1,152 @@ +package jig + +import ( + "github.com/bitly/go-simplejson" + "github.com/pkg/errors" + "io" + "io/ioutil" + "regexp" + "sort" + "strconv" +) + +type JsonManager struct { + current *simplejson.Json + origin *simplejson.Json +} + +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, + } + + return json, nil +} + +func (jm *JsonManager) Get(q QueryInterface) (string, error) { + json, _ := jm.GetFilteredData(q) + + data, enc_err := json.Encode() + + if enc_err != nil { + return "", errors.Wrap(enc_err, "failure json encode") + } + + return string(data), nil +} + +func (jm *JsonManager) GetPretty(q QueryInterface) (string, error) { + json, _ := jm.GetFilteredData(q) + s, err := json.EncodePretty() + if err != nil { + return "", errors.Wrap(err, "failure json encode") + } + return string(s), nil +} + +func (jm *JsonManager) GetFilteredData(q QueryInterface) (*simplejson.Json, error) { + json := jm.origin + + lastKeyword, _ := q.StringPopKeyword() + for _, keyword := range q.StringGetKeywords() { + json, _ = getItem(json, keyword) + } + if j, _ := getItem(json, lastKeyword); !isEmptyJson(j) { + json = j + } else if b, _ := getCandidateKeyItem(json, lastKeyword); !b { + json = j + } + return json, nil +} + +func getItem(json *simplejson.Json, s string) (*simplejson.Json, error) { + var result *simplejson.Json + re := regexp.MustCompile(`\[([0-9]+)\]`) + matches := re.FindStringSubmatch(s) + if len(matches) > 0 { + index, _ := strconv.Atoi(matches[1]) + result = json.GetIndex(index) + } else { + result = json.Get(s) + } + return result, nil +} + +func getCandidateKeyItem(json *simplejson.Json, s string) (bool, string) { + re := regexp.MustCompile(`\[([0-9]+)\]`) + matches := re.FindStringSubmatch(s) + if len(matches) > 0 { + index, _ := strconv.Atoi(matches[1]) + a, err := json.Array() + if err != nil { + return false, "" + } + if len(a)-1 < index { + return false, "" + } + } else { + reg := regexp.MustCompile("(?i)^" + s) + var candidate string + result := false + for _, key := range getCurrentKeys(json) { + if reg.MatchString(key) { + result = true + if candidate == "" { + candidate = key + } else { + axis := candidate + if len(candidate) > len(key) { + axis = key + } + max := 0 + for i, _ := range axis { + if candidate[i] == key[i] { + max = i + } + } + candidate = candidate[0 : max+1] + } + } + } + candidate = reg.ReplaceAllString(candidate, "") + return result, candidate + } + return true, "" +} + +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 isEmptyJson(j *simplejson.Json) bool { + switch j.Interface().(type) { + case nil: + return true + default: + return false + } +} diff --git a/json_manager_test.go b/json_manager_test.go new file mode 100644 index 0000000..cebe546 --- /dev/null +++ b/json_manager_test.go @@ -0,0 +1,263 @@ +package jig + +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, + }) + assert.Nil(e) + + assert.Equal(jm.current.Get("name").MustString(), "go") +} + +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, err := jm.Get(q) + + assert.Nil(err) + assert.Equal(`"go"`, result) + + // 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, err = jm.Get(q) + assert.Nil(err) + assert.Equal(`"2AA2"`, result) + + // case 3 + q = NewQueryWithString(".abcde_fgh") + result, err = jm.Get(q) + assert.Nil(err) + assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result) + + // case 4 + q = NewQueryWithString(".abcde_fgh.aaa[2]") + result, err = jm.Get(q) + assert.Nil(err) + assert.Equal(`[1,2]`, result) + + // case 5 + q = NewQueryWithString(".abcde_fgh.aaa[3]") + result, err = jm.Get(q) + assert.Nil(err) + assert.Equal(`null`, result) + + // case 6 + q = NewQueryWithString(".abcde_fgh.aa") + result, err = jm.Get(q) + assert.Nil(err) + assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result) + + // case 7 + q = NewQueryWithString(".abcde_fgh.ac") + result, err = jm.Get(q) + assert.Nil(err) + assert.Equal(`null`, result) +} + +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) + + assert.Nil(err) + assert.Equal("\"go\"", result) +} + +func TestGetFilteredData(t *testing.T) { + var assert = assert.New(t) + + // data + data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}` + r := bytes.NewBufferString(data) + jm, _ := NewJsonManager(r) + + // case 1 + q := NewQueryWithString(".abcde") + result, err := jm.GetFilteredData(q) + assert.Nil(err) + d, _ := result.Encode() + assert.Equal(`"2AA2"`, string(d)) + + // case 2 + q = NewQueryWithString(".abcde_fgh") + result, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) + + // case 3 + q = NewQueryWithString(".abcde_fgh.aaa[2]") + result, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`[1,2]`, string(d)) + + // case 4 + q = NewQueryWithString(".abcde_fgh.aaa[3]") + result, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`null`, string(d)) + + // case 5 + q = NewQueryWithString(".abcde_fgh.aa") + result, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) +} + +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, "name") + result, _ := d.Encode() + assert.Equal(string(result), "\"go\"") + + // 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(string(result), "20") + + // 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(string(result2), `"go"`) + assert.Equal(string(result3), `20`) + + // 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(string(result), `"go"`) +} + +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 TestGetCandidateKeyItem(t *testing.T) { + var assert = assert.New(t) + rr := bytes.NewBufferString(`{"name":"simeji-github", "naming":"simeji", "nickname":"simejisimeji"}`) + buf, _ := ioutil.ReadAll(rr) + json, _ := simplejson.NewJson(buf) + + b, candidate := getCandidateKeyItem(json, "na") + assert.True(b) + assert.Equal("m", candidate) + + rr = bytes.NewBufferString(`{"abcde":"simeji", "abcdef":"github", "abcdabc":"simejinet"}`) + buf, _ = ioutil.ReadAll(rr) + json, _ = simplejson.NewJson(buf) + + b, candidate = getCandidateKeyItem(json, "a") + assert.True(b) + + rr = bytes.NewBufferString(`{"abcde":"simeji", "abcdef":"github", "abcdabc":"simejinet"}`) + buf, _ = ioutil.ReadAll(rr) + json, _ = simplejson.NewJson(buf) + + b, candidate = getCandidateKeyItem(json, "ba") + assert.False(b) + assert.Equal("", candidate) + + rr = bytes.NewBufferString(`{"abcde":"simeji", "abcdef":"github", "abcdabc":"simejinet"}`) + buf, _ = ioutil.ReadAll(rr) + json, _ = simplejson.NewJson(buf) + + b, candidate = getCandidateKeyItem(json, "abcde") + assert.True(b) + assert.Equal("", candidate) + + rr = bytes.NewBufferString(`[1, 2, 3, "test"]`) + buf, _ = ioutil.ReadAll(rr) + json, _ = simplejson.NewJson(buf) + + b, candidate = getCandidateKeyItem(json, "3") + assert.False(b) + assert.Equal("", candidate) +} + +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{})) +} diff --git a/query.go b/query.go index 3e6f570..f503538 100644 --- a/query.go +++ b/query.go @@ -1,19 +1,41 @@ package jig import ( + "regexp" "strings" ) +type QueryInterface interface { + Get() []rune + Set(query []rune) []rune + Add(query []rune) []rune + Delete(i int) []rune + Clear() []rune + GetKeywords() [][]rune + GetLastKeyword() []rune + PopKeyword() ([]rune, []rune) + StringGet() string + StringSet(query string) string + StringAdd(query string) string + StringGetKeywords() []string + StringPopKeyword() (string, []rune) +} + type Query struct { query *[]rune complete *[]rune } func NewQuery(query []rune) *Query { - return &Query{ - query: &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 { @@ -21,7 +43,9 @@ func (q *Query) Get() []rune { } func (q *Query) Set(query []rune) []rune { - q.query = &query + if validate(query) { + q.query = &query + } return q.Get() } @@ -45,13 +69,29 @@ func (q *Query) Clear() []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 { - keywords = append(keywords, []rune(keyword)) + 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 @@ -63,15 +103,27 @@ func (q *Query) GetLastKeyword() []rune { } func (q *Query) PopKeyword() ([]rune, []rune) { - lastSepIdx := 0 - for i, e := range *q.query { + 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 } } - qq := q.Get() - keyword := qq[lastSepIdx+1:] - query := q.Set(qq[0 : lastSepIdx+1]) + + 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 } @@ -88,16 +140,34 @@ func (q *Query) StringAdd(query string) string { } func (q *Query) StringGetKeywords() []string { - query := string(*q.query) + var keywords []string + for _, keyword := range q.GetKeywords() { + keywords = append(keywords, string(keyword)) + } + return keywords +} - splitQuery := strings.Split(query, ".") - lastIdx := len(splitQuery) - 1 +func (q *Query) StringPopKeyword() (string, []rune) { + keyword, query := q.PopKeyword() + return string(keyword), query +} - keywords := []string{} - for i, keyword := range splitQuery { - if keyword != "" || i == lastIdx { - keywords = append(keywords, keyword) - } +func validate(r []rune) bool { + s := string(r) + if regexp.MustCompile(`^[^.]`).MatchString(s) { + return false } - return keywords + 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 } diff --git a/query_test.go b/query_test.go index f3e5915..f72c4e6 100644 --- a/query_test.go +++ b/query_test.go @@ -5,47 +5,103 @@ import ( "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("name")) + 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") + v := []rune(".test") q := NewQuery(v) - assert.Equal(q.Get(), []rune("test")) + assert.Equal(q.Get(), []rune(".test")) } func TestQuerySet(t *testing.T) { var assert = assert.New(t) - v := []rune("hello") + v := []rune(".hello") + q := NewQuery(v) + + assert.Equal(q.Set([]rune(".world")), []rune(".world")) +} + +func TestQuerySetWithInvalidQuery(t *testing.T) { + var assert = assert.New(t) + + v := []rune(".hello") q := NewQuery(v) - assert.Equal(q.Set([]rune("world")), []rune("world")) + assert.Equal(q.Set([]rune("world")), []rune(".hello")) } func TestQueryAdd(t *testing.T) { var assert = assert.New(t) - v := []rune("hello") + v := []rune(".hello") q := NewQuery(v) - assert.Equal(q.Add([]rune("world")), []rune("helloworld")) + assert.Equal(q.Add([]rune("world")), []rune(".helloworld")) } func TestQueryClear(t *testing.T) { var assert = assert.New(t) - v := []rune("test") + v := []rune(".test") q := NewQuery(v) assert.Equal(q.Clear(), []rune("")) @@ -54,12 +110,12 @@ func TestQueryClear(t *testing.T) { func TestQueryDelete(t *testing.T) { var assert = assert.New(t) - v := []rune("helloworld") + v := []rune(".helloworld") q := NewQuery(v) - assert.Equal(q.Delete(1), []rune("helloworl")) - assert.Equal(q.Delete(1), []rune("hellowor")) - assert.Equal(q.Delete(2), []rune("hellow")) + assert.Equal(q.Delete(1), []rune(".helloworl")) + assert.Equal(q.Delete(1), []rune(".hellowor")) + assert.Equal(q.Delete(2), []rune(".hellow")) assert.Equal(q.Delete(8), []rune("")) } @@ -73,32 +129,56 @@ func TestGetKeywords(t *testing.T) { []rune("name"), }) - v = []rune("test.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("test...name") + v = []rune(".hello") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ - []rune("test"), - []rune("name"), + []rune("hello"), }) - v = []rune("hello") + v = []rune(".hello.") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("hello"), + []rune(""), }) - v = []rune("..hello..") + v = []rune(".hello[0]") q = NewQuery(v) assert.Equal(q.GetKeywords(), [][]rune{ []rune("hello"), - []rune(""), + []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) { @@ -124,42 +204,42 @@ func TestPopKeyword(t *testing.T) { q := NewQuery(v) k, query := q.PopKeyword() assert.Equal(k, []rune("name")) - assert.Equal(query, []rune(".test.")) - assert.Equal(q.Get(), []rune(".test.")) + assert.Equal(query, []rune(".test")) + assert.Equal(q.Get(), []rune(".test")) - v = []rune("test.name.") + 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.")) + 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") + v := []rune(".test") q := NewQuery(v) - assert.Equal(q.StringGet(), "test") + assert.Equal(q.StringGet(), ".test") } func TestQueryStringSet(t *testing.T) { var assert = assert.New(t) - v := []rune("hello") + v := []rune(".hello") q := NewQuery(v) - assert.Equal(q.StringSet("world"), "world") + assert.Equal(q.StringSet(".world"), ".world") } func TestQueryStringAdd(t *testing.T) { var assert = assert.New(t) - v := []rune("hello") + v := []rune(".hello") q := NewQuery(v) - assert.Equal(q.StringAdd("world"), "helloworld") + assert.Equal(q.StringAdd("world"), ".helloworld") } func TestStringGetKeywords(t *testing.T) { @@ -172,10 +252,35 @@ func TestStringGetKeywords(t *testing.T) { "name", }) - v = []rune("test.name") + v = []rune(".test.name") q = NewQuery(v) assert.Equal(q.StringGetKeywords(), []string{ "test", "name", }) } + +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")) +} From a13e5dab16cc4640740ccc0681bbecd369e436c3 Mon Sep 17 00:00:00 2001 From: simeji Date: Wed, 24 Aug 2016 09:44:14 +0900 Subject: [PATCH 3/5] refactoring: Add suggestion module --- engine.go | 528 +++++++++++++++++++------------------------ engine_test.go | 15 +- json_manager.go | 110 +++------ json_manager_test.go | 178 +++++++-------- query.go | 12 +- query_test.go | 14 ++ suggestion.go | 100 ++++++++ suggestion_test.go | 59 +++++ 8 files changed, 535 insertions(+), 481 deletions(-) create mode 100644 suggestion.go create mode 100644 suggestion_test.go diff --git a/engine.go b/engine.go index c629bd9..7cf11b9 100644 --- a/engine.go +++ b/engine.go @@ -2,13 +2,8 @@ package jig import ( "fmt" - "github.com/bitly/go-simplejson" "github.com/nsf/termbox-go" "io" - "io/ioutil" - "regexp" - "sort" - "strconv" "strings" ) @@ -18,51 +13,42 @@ const ( FilterPrompt string = "[Filter]> " ) -var ( - complete *[]rune -) - type Engine struct { - json *simplejson.Json - orgJson *simplejson.Json - manager *JsonManager - currentKeys []string - jq bool - pretty bool - query *Query + manager *JsonManager + jq bool + pretty bool + query *Query } func NewEngine(s io.Reader, q bool, p bool) *Engine { - j, err := parse(s) - if err != true { + j, err := NewJsonManager(s) + if err != nil { return &Engine{} } e := &Engine{ - json: j, - orgJson: j, - currentKeys: []string{}, - jq: q, - pretty: p, - query: NewQuery([]rune("")), + manager: j, + jq: q, + pretty: p, + query: NewQuery([]rune("")), } return e } func (e Engine) Run() int { - if !e.render(e.json) { + if !e.render() { return 2 } if e.jq { fmt.Printf("%s", e.query.StringGet()) } else if e.pretty { - s, err := e.json.EncodePretty() + s, _, err := e.manager.GetPretty(e.query) if err != nil { return 1 } - fmt.Printf("%s", string(s)) + fmt.Printf("%s", s) } else { - s, err := e.json.Encode() + s, _, err := e.manager.Get(e.query) if err != nil { return 1 } @@ -71,27 +57,8 @@ func (e Engine) Run() int { return 0 } -func parse(content io.Reader) (*simplejson.Json, bool) { - res := true - buf, err := ioutil.ReadAll(content) - - if err != nil { - //log.Printf("Bad contents '", err, "'") - res = false - } - - j, err := simplejson.NewJson(buf) - - if err != nil { - //log.Printf("Bad Json Format '", err, "'") - res = false - } - - return j, res -} - // fix:me -func (e *Engine) render(json *simplejson.Json) bool { +func (e *Engine) render() bool { err := termbox.Init() if err != nil { @@ -99,34 +66,30 @@ func (e *Engine) render(json *simplejson.Json) bool { } defer termbox.Close() - complete = &[]rune{} - - contents := e.prettyContents() + contents, _, _ := e.manager.GetPretty(e.query) keymode := false + var complete string + for { - var flgFilter bool - e.json, flgFilter = e.filterJson(e.orgJson, e.query.StringGet()) - e.currentKeys = e.getCurrentKeys(e.json) - if !flgFilter { - e.suggest() - } + //var flgFilter bool + contents, complete, _ = e.manager.GetPretty(e.query) if keymode { - ckeys := []string{} - kws := e.query.StringGetKeywords() - if lkw := kws[len(kws)-1]; lkw != "" && !flgFilter { - for k, _ := range e.getFilteredCurrentKeys(e.json, lkw) { - ckeys = append(ckeys, e.currentKeys[k]) - } - sort.Strings(ckeys) - contents = ckeys - } else { - contents = e.currentKeys - } + //ckeys := []string{} + //kws := e.query.StringGetKeywords() + //if lkw := kws[len(kws)-1]; lkw != "" && !flgFilter { + //for k, _ := range e.getFilteredCurrentKeys(e.json, lkw) { + //ckeys = append(ckeys, e.currentKeys[k]) + //} + //sort.Strings(ckeys) + //contents = ckeys + //} else { + //contents = e.currentKeys + //} } else { - contents = e.prettyContents() + //contents, complete, _ = e.manager.GetPretty(e.query) } - e.draw(contents) + e.draw(e.query.StringGet(), complete, strings.Split(contents, "\n")) switch ev := termbox.PollEvent(); ev.Type { case termbox.EventKey: switch ev.Key { @@ -138,24 +101,11 @@ func (e *Engine) render(json *simplejson.Json) bool { _ = e.query.StringAdd(" ") case termbox.KeyCtrlW: //delete whole word to period - s := e.query.StringGet() - kws := strings.Split(s, ".") - lki := len(kws) - 1 - var lk string - lk, kws = kws[lki], kws[:lki] - s = strings.Join(kws, ".") - if lk != "" { - s = s + "." - } - _ = e.query.StringSet(s[0:len(s)]) + _, _ = e.query.PopKeyword() case termbox.KeyBackspace, termbox.KeyBackspace2: _ = e.query.Delete(1) case termbox.KeyTab: - if len(*complete) > 0 { - e.autoComplete() - } else { - e.suggest() - } + _ = e.query.StringAdd(complete) case termbox.KeyEnter: return true case 0: @@ -170,215 +120,211 @@ func (e *Engine) render(json *simplejson.Json) bool { } } -func (e *Engine) autoComplete() { - _ = e.query.Add(*complete) - *complete = []rune("") -} - -func (e *Engine) suggest() bool { - s := e.query.StringGet() - if arr, _ := e.json.Array(); arr != nil { - if l := len(s); l < 1 { - *complete = []rune("") - return false - } - le := s[len(s)-1:] - if le == "." { - *complete = []rune("") - return false - } - var rs string - ds := regexp.MustCompile("\\[([0-9]*)?\\]?$").FindString(s) - if len(arr) > 1 { - if ds == "" { - rs = "[" - } else if le != "]" { - rs = "]" - } - } else { - rs = "[0]" - } - cs := strings.Replace(rs, ds, "", -1) - *complete = []rune(cs) - return true - } - kws := strings.Split(s, ".") - lki := len(kws) - 1 - if lki == 0 { - return false - } - lkw, tkws := kws[lki], kws[:lki] - - re, err := regexp.Compile("(?i)^" + lkw) - if err != nil { - return false - } - m := e.getFilteredCurrentKeys(e.json, lkw) - - if len(m) == 1 { - for k, v := range m { - kw := re.ReplaceAllString(e.currentKeys[k], "") - *complete = []rune(kw) - s = strings.Join(tkws, ".") + "." + v - } - _ = e.query.StringSet(s) - return true - } else { - var sw []rune - cnt := 0 - for k, _ := range m { - tsw := []rune{} - v := []rune(e.currentKeys[k]) - if cnt == 0 { - sw = v - cnt = cnt + 1 - continue - } - swl := len(sw) - 1 - for i, s := range v { - if i > swl { - break - } - if sw[i] != s { - break - } - tsw = append(tsw, s) - } - sw = tsw - cnt = cnt + 1 - } - if len(sw) >= 0 { - kw := re.ReplaceAllString(string(sw), "") - *complete = []rune(kw) - s = strings.Join(tkws, ".") + "." + lkw - } - _ = e.query.StringSet(s) - return true - } - *complete = []rune("") - return false -} - -func (e *Engine) getFilteredCurrentKeys(json *simplejson.Json, kw string) map[int]string { - m := map[int]string{} - - re, err := regexp.Compile("(?i)^" + kw) - if err != nil { - return m - } - - currentKeys := e.getCurrentKeys(json) - for i, k := range currentKeys { - if str := re.FindString(k); str != "" { - m[i] = str - } - } - return m -} - -func (e *Engine) prettyContents() []string { - s, _ := e.json.EncodePretty() - return strings.Split(string(s), "\n") -} - -func (e *Engine) filterJson(json *simplejson.Json, q string) (*simplejson.Json, bool) { - if len(q) < 1 { - return json, false - } - keywords := strings.Split(q, ".") - - // check start "." - if keywords[0] != "" { - return &simplejson.Json{}, false - } - - keywords = keywords[1:] - - re := regexp.MustCompile("\\[[0-9]*\\]") - delre := regexp.MustCompile("\\[([0-9]+)?") - - lastIdx := len(keywords) - 1 - - flgMatchLastKw := false - - //eachFlg := false - for ki, keyword := range keywords { - if len(keyword) == 0 { - if ki != lastIdx { - json = &simplejson.Json{} - } - break - } - // abc[0] - if keyword[:1] == "[" { - matchIndexes := re.FindAllStringIndex(keyword, -1) - lmi := len(matchIndexes) - 1 - for idx, m := range matchIndexes { - i, _ := strconv.Atoi(keyword[m[0]+1 : m[1]-1]) - if idx == lmi && m[1]-m[0] == 2 { - //eachFlg = true - } else if tj := json.GetIndex(i); !isEmptyJson(tj) { - json = tj - } - } - } else if keyword[len(keyword)-1:] == "]" { - matchIndexes := re.FindAllStringIndex(keyword, -1) - kw := re.ReplaceAllString(keyword, "") - - tj := json.Get(kw) - if ki != lastIdx { - json = tj - } else if !isEmptyJson(tj) { - json = tj - flgMatchLastKw = true - } - lmi := len(matchIndexes) - 1 - for idx, m := range matchIndexes { - i, _ := strconv.Atoi(keyword[m[0]+1 : m[1]-1]) - if idx == lmi && m[1]-m[0] == 2 { - //eachFlg = true - } else if tj := json.GetIndex(i); !isEmptyJson(tj) { - json = tj - flgMatchLastKw = true - } - } - } else { - kw := delre.ReplaceAllString(keyword, "") - tj := json.Get(kw) - if ki != lastIdx { - json = tj - } else if len(e.getFilteredCurrentKeys(json, kw)) < 1 { - json = tj - } else if !isEmptyJson(tj) { - json = tj - flgMatchLastKw = true - } - } - } - return json, flgMatchLastKw -} - -func (e *Engine) 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 (e *Engine) draw(rows []string) { +// +//func (e *Engine) suggest() bool { +// s := e.query.StringGet() +// if arr, _ := e.json.Array(); arr != nil { +// if l := len(s); l < 1 { +// *complete = []rune("") +// return false +// } +// le := s[len(s)-1:] +// if le == "." { +// *complete = []rune("") +// return false +// } +// var rs string +// ds := regexp.MustCompile("\\[([0-9]*)?\\]?$").FindString(s) +// if len(arr) > 1 { +// if ds == "" { +// rs = "[" +// } else if le != "]" { +// rs = "]" +// } +// } else { +// rs = "[0]" +// } +// cs := strings.Replace(rs, ds, "", -1) +// *complete = []rune(cs) +// return true +// } +// kws := strings.Split(s, ".") +// lki := len(kws) - 1 +// if lki == 0 { +// return false +// } +// lkw, tkws := kws[lki], kws[:lki] +// +// re, err := regexp.Compile("(?i)^" + lkw) +// if err != nil { +// return false +// } +// m := e.getFilteredCurrentKeys(e.json, lkw) +// +// if len(m) == 1 { +// for k, v := range m { +// kw := re.ReplaceAllString(e.currentKeys[k], "") +// *complete = []rune(kw) +// s = strings.Join(tkws, ".") + "." + v +// } +// _ = e.query.StringSet(s) +// return true +// } else { +// var sw []rune +// cnt := 0 +// for k, _ := range m { +// tsw := []rune{} +// v := []rune(e.currentKeys[k]) +// if cnt == 0 { +// sw = v +// cnt = cnt + 1 +// continue +// } +// swl := len(sw) - 1 +// for i, s := range v { +// if i > swl { +// break +// } +// if sw[i] != s { +// break +// } +// tsw = append(tsw, s) +// } +// sw = tsw +// cnt = cnt + 1 +// } +// if len(sw) >= 0 { +// kw := re.ReplaceAllString(string(sw), "") +// *complete = []rune(kw) +// s = strings.Join(tkws, ".") + "." + lkw +// } +// _ = e.query.StringSet(s) +// return true +// } +// *complete = []rune("") +// return false +//} + +// func (e *Engine) getFilteredCurrentKeys(json *simplejson.Json, kw string) map[int]string { +// m := map[int]string{} +// +// re, err := regexp.Compile("(?i)^" + kw) +// if err != nil { +// return m +// } +// +// currentKeys := e.getCurrentKeys(json) +// for i, k := range currentKeys { +// if str := re.FindString(k); str != "" { +// m[i] = str +// } +// } +// return m +// } +// +//func (e *Engine) prettyContents() []string { +// s, _ := e.json.EncodePretty() +// return strings.Split(string(s), "\n") +//} + +//func (e *Engine) filterJson(json *simplejson.Json, q string) (*simplejson.Json, bool) { +// if len(q) < 1 { +// return json, false +// } +// keywords := strings.Split(q, ".") +// +// // check start "." +// if keywords[0] != "" { +// return &simplejson.Json{}, false +// } +// +// keywords = keywords[1:] +// +// re := regexp.MustCompile("\\[[0-9]*\\]") +// delre := regexp.MustCompile("\\[([0-9]+)?") +// +// lastIdx := len(keywords) - 1 +// +// flgMatchLastKw := false +// +// //eachFlg := false +// for ki, keyword := range keywords { +// if len(keyword) == 0 { +// if ki != lastIdx { +// json = &simplejson.Json{} +// } +// break +// } +// // abc[0] +// if keyword[:1] == "[" { +// matchIndexes := re.FindAllStringIndex(keyword, -1) +// lmi := len(matchIndexes) - 1 +// for idx, m := range matchIndexes { +// i, _ := strconv.Atoi(keyword[m[0]+1 : m[1]-1]) +// if idx == lmi && m[1]-m[0] == 2 { +// //eachFlg = true +// } else if tj := json.GetIndex(i); !isEmptyJson(tj) { +// json = tj +// } +// } +// } else if keyword[len(keyword)-1:] == "]" { +// matchIndexes := re.FindAllStringIndex(keyword, -1) +// kw := re.ReplaceAllString(keyword, "") +// +// tj := json.Get(kw) +// if ki != lastIdx { +// json = tj +// } else if !isEmptyJson(tj) { +// json = tj +// flgMatchLastKw = true +// } +// lmi := len(matchIndexes) - 1 +// for idx, m := range matchIndexes { +// i, _ := strconv.Atoi(keyword[m[0]+1 : m[1]-1]) +// if idx == lmi && m[1]-m[0] == 2 { +// //eachFlg = true +// } else if tj := json.GetIndex(i); !isEmptyJson(tj) { +// json = tj +// flgMatchLastKw = true +// } +// } +// } else { +// kw := delre.ReplaceAllString(keyword, "") +// tj := json.Get(kw) +// if ki != lastIdx { +// json = tj +// } else if len(e.getFilteredCurrentKeys(json, kw)) < 1 { +// json = tj +// } else if !isEmptyJson(tj) { +// json = tj +// flgMatchLastKw = true +// } +// } +// } +// return json, flgMatchLastKw +//} + +//func (e *Engine) 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 (e *Engine) draw(query string, complete string, rows []string) { termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) - fs := FilterPrompt + e.query.StringGet() - cs := string(*complete) + fs := FilterPrompt + query + cs := complete drawln(0, 0, fs+cs, []([]int){[]int{len(fs), len(fs) + len(cs)}}) termbox.SetCursor(len(fs), 0) diff --git a/engine_test.go b/engine_test.go index bfea131..20fa603 100644 --- a/engine_test.go +++ b/engine_test.go @@ -17,18 +17,5 @@ func TestNewEngine(t *testing.T) { r := bytes.NewBufferString("{\"name\":\"go\"}") e = NewEngine(r, false, false) assert.NotEqual(e, &Engine{}, "they should be not equal") - assert.Equal(e.json.Get("name").MustString(), "go", "they should be equal") -} - -func TestParse(t *testing.T) { - var assert = assert.New(t) - - r := bytes.NewBufferString("{\"name\":\"go\"}") - - _, e := parse(r) - assert.True(e) - - r2 := bytes.NewBufferString("{\"name\":\"go\"") - _, e2 := parse(r2) - assert.False(e2) + //assert.Equal(e.json.Get("name").MustString(), "go", "they should be equal") } diff --git a/json_manager.go b/json_manager.go index 7d2e970..6029072 100644 --- a/json_manager.go +++ b/json_manager.go @@ -6,13 +6,14 @@ import ( "io" "io/ioutil" "regexp" - "sort" "strconv" + //"strings" ) type JsonManager struct { - current *simplejson.Json - origin *simplejson.Json + current *simplejson.Json + origin *simplejson.Json + suggestion *Suggestion } func NewJsonManager(reader io.Reader) (*JsonManager, error) { @@ -29,53 +30,67 @@ func NewJsonManager(reader io.Reader) (*JsonManager, error) { } json := &JsonManager{ - origin: j, - current: j, + origin: j, + current: j, + suggestion: NewSuggestion(), } return json, nil } -func (jm *JsonManager) Get(q QueryInterface) (string, error) { - json, _ := jm.GetFilteredData(q) +func (jm *JsonManager) Get(q QueryInterface) (string, string, error) { + json, suggestion, _ := jm.GetFilteredData(q) data, enc_err := json.Encode() if enc_err != nil { - return "", errors.Wrap(enc_err, "failure json encode") + return "", "", errors.Wrap(enc_err, "failure json encode") } - return string(data), nil + return string(data), suggestion, nil } -func (jm *JsonManager) GetPretty(q QueryInterface) (string, error) { - json, _ := jm.GetFilteredData(q) +func (jm *JsonManager) GetPretty(q QueryInterface) (string, string, error) { + json, suggestion, _ := jm.GetFilteredData(q) s, err := json.EncodePretty() if err != nil { - return "", errors.Wrap(err, "failure json encode") + return "", "", errors.Wrap(err, "failure json encode") } - return string(s), nil + return string(s), suggestion, nil } -func (jm *JsonManager) GetFilteredData(q QueryInterface) (*simplejson.Json, error) { +func (jm *JsonManager) GetFilteredData(q QueryInterface) (*simplejson.Json, string, error) { json := jm.origin - lastKeyword, _ := q.StringPopKeyword() - for _, keyword := range q.StringGetKeywords() { + lastKeyword := q.StringGetLastKeyword() + keywords := q.StringGetKeywords() + idx := 0 + if l := len(keywords); l > 0 { + idx = l - 1 + } + for _, keyword := range keywords[0:idx] { json, _ = getItem(json, keyword) } - if j, _ := getItem(json, lastKeyword); !isEmptyJson(j) { + reg := regexp.MustCompile(`\[[0-9]*$`) + + suggest := jm.suggestion.Get(json, lastKeyword) + + if len(reg.FindString(lastKeyword)) > 0 { + } else if j, _ := getItem(json, lastKeyword); !isEmptyJson(j) { json = j - } else if b, _ := getCandidateKeyItem(json, lastKeyword); !b { + suggest = jm.suggestion.Get(json, "") + } else if len(jm.suggestion.GetCandidateKeys(json, lastKeyword)) < 1 { json = j } - return json, nil + return json, suggest, nil } func getItem(json *simplejson.Json, s string) (*simplejson.Json, error) { var result *simplejson.Json + re := regexp.MustCompile(`\[([0-9]+)\]`) matches := re.FindStringSubmatch(s) + if len(matches) > 0 { index, _ := strconv.Atoi(matches[1]) result = json.GetIndex(index) @@ -85,63 +100,6 @@ func getItem(json *simplejson.Json, s string) (*simplejson.Json, error) { return result, nil } -func getCandidateKeyItem(json *simplejson.Json, s string) (bool, string) { - re := regexp.MustCompile(`\[([0-9]+)\]`) - matches := re.FindStringSubmatch(s) - if len(matches) > 0 { - index, _ := strconv.Atoi(matches[1]) - a, err := json.Array() - if err != nil { - return false, "" - } - if len(a)-1 < index { - return false, "" - } - } else { - reg := regexp.MustCompile("(?i)^" + s) - var candidate string - result := false - for _, key := range getCurrentKeys(json) { - if reg.MatchString(key) { - result = true - if candidate == "" { - candidate = key - } else { - axis := candidate - if len(candidate) > len(key) { - axis = key - } - max := 0 - for i, _ := range axis { - if candidate[i] == key[i] { - max = i - } - } - candidate = candidate[0 : max+1] - } - } - } - candidate = reg.ReplaceAllString(candidate, "") - return result, candidate - } - return true, "" -} - -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 isEmptyJson(j *simplejson.Json) bool { switch j.Interface().(type) { case nil: diff --git a/json_manager_test.go b/json_manager_test.go index cebe546..f033729 100644 --- a/json_manager_test.go +++ b/json_manager_test.go @@ -19,8 +19,9 @@ func TestNewJson(t *testing.T) { sj, _ := simplejson.NewJson(buf) assert.Equal(jm, &JsonManager{ - current: sj, - origin: sj, + current: sj, + origin: sj, + suggestion: NewSuggestion(), }) assert.Nil(e) @@ -43,10 +44,11 @@ func TestGet(t *testing.T) { r := bytes.NewBufferString("{\"name\":\"go\"}") jm, _ := NewJsonManager(r) q := NewQueryWithString(".name") - result, err := jm.Get(q) + result, suggest, err := jm.Get(q) assert.Nil(err) assert.Equal(`"go"`, result) + assert.Equal(``, suggest) // data data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}` @@ -55,39 +57,41 @@ func TestGet(t *testing.T) { // case 2 q = NewQueryWithString(".abcde") - result, err = jm.Get(q) + result, suggest, err = jm.Get(q) assert.Nil(err) assert.Equal(`"2AA2"`, result) + assert.Equal(``, suggest) // case 3 q = NewQueryWithString(".abcde_fgh") - result, err = jm.Get(q) + result, suggest, err = jm.Get(q) assert.Nil(err) assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result) // case 4 q = NewQueryWithString(".abcde_fgh.aaa[2]") - result, err = jm.Get(q) - assert.Nil(err) + result, suggest, err = jm.Get(q) assert.Equal(`[1,2]`, result) // case 5 q = NewQueryWithString(".abcde_fgh.aaa[3]") - result, err = jm.Get(q) + result, suggest, err = jm.Get(q) assert.Nil(err) assert.Equal(`null`, result) // case 6 q = NewQueryWithString(".abcde_fgh.aa") - result, err = jm.Get(q) + result, suggest, err = jm.Get(q) assert.Nil(err) assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result) + assert.Equal(`a`, suggest) // case 7 q = NewQueryWithString(".abcde_fgh.ac") - result, err = jm.Get(q) + result, suggest, err = jm.Get(q) assert.Nil(err) assert.Equal(`null`, result) + assert.Equal(``, suggest) } func TestGetPretty(t *testing.T) { @@ -96,54 +100,10 @@ func TestGetPretty(t *testing.T) { r := bytes.NewBufferString("{\"name\":\"go\"}") jm, _ := NewJsonManager(r) q := NewQueryWithString(".name") - result, err := jm.GetPretty(q) - - assert.Nil(err) - assert.Equal("\"go\"", result) -} - -func TestGetFilteredData(t *testing.T) { - var assert = assert.New(t) + result, _, err := jm.GetPretty(q) - // data - data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}` - r := bytes.NewBufferString(data) - jm, _ := NewJsonManager(r) - - // case 1 - q := NewQueryWithString(".abcde") - result, err := jm.GetFilteredData(q) assert.Nil(err) - d, _ := result.Encode() - assert.Equal(`"2AA2"`, string(d)) - - // case 2 - q = NewQueryWithString(".abcde_fgh") - result, err = jm.GetFilteredData(q) - assert.Nil(err) - d, _ = result.Encode() - assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) - - // case 3 - q = NewQueryWithString(".abcde_fgh.aaa[2]") - result, err = jm.GetFilteredData(q) - assert.Nil(err) - d, _ = result.Encode() - assert.Equal(`[1,2]`, string(d)) - - // case 4 - q = NewQueryWithString(".abcde_fgh.aaa[3]") - result, err = jm.GetFilteredData(q) - assert.Nil(err) - d, _ = result.Encode() - assert.Equal(`null`, string(d)) - - // case 5 - q = NewQueryWithString(".abcde_fgh.aa") - result, err = jm.GetFilteredData(q) - assert.Nil(err) - d, _ = result.Encode() - assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) + assert.Equal(`"go"`, result) } func TestGetItem(t *testing.T) { @@ -193,6 +153,70 @@ func TestGetItem(t *testing.T) { assert.Equal(string(result), `"go"`) } +func TestGetFilteredData(t *testing.T) { + var assert = assert.New(t) + + // data + data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}` + r := bytes.NewBufferString(data) + jm, _ := NewJsonManager(r) + + // case 1 + q := NewQueryWithString(".abcde") + result, s, err := jm.GetFilteredData(q) + assert.Nil(err) + d, _ := result.Encode() + assert.Equal(`"2AA2"`, string(d)) + assert.Equal(``, s) + + // case 2 + q = NewQueryWithString(".abcde_fgh") + result, s, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) + assert.Equal(``, s) + + // case 3 + q = NewQueryWithString(".abcde_fgh.aaa[2]") + result, _, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`[1,2]`, string(d)) + + // case 4 + q = NewQueryWithString(".abcde_fgh.aaa[3]") + result, s, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`null`, string(d)) + assert.Equal(``, s) + + // case 5 + q = NewQueryWithString(".abcde_fgh.aaa") + result, s, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`[123,"cccc",[1,2]]`, string(d)) + assert.Equal(`[`, s) + + // case 6 + q = NewQueryWithString(".abcde_fgh.aa") + result, s, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) + assert.Equal(`a`, s) + + // case 7 + q = NewQueryWithString(".abcde_fgh.aaa[") + result, s, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`[123,"cccc",[1,2]]`, string(d)) + assert.Equal(``, s) +} + func TestGetCurrentKeys(t *testing.T) { var assert = assert.New(t) r := bytes.NewBufferString(`{"name":"go","age":20,"weight":60}`) @@ -210,48 +234,6 @@ func TestGetCurrentKeys(t *testing.T) { assert.Equal([]string{}, keys) } -func TestGetCandidateKeyItem(t *testing.T) { - var assert = assert.New(t) - rr := bytes.NewBufferString(`{"name":"simeji-github", "naming":"simeji", "nickname":"simejisimeji"}`) - buf, _ := ioutil.ReadAll(rr) - json, _ := simplejson.NewJson(buf) - - b, candidate := getCandidateKeyItem(json, "na") - assert.True(b) - assert.Equal("m", candidate) - - rr = bytes.NewBufferString(`{"abcde":"simeji", "abcdef":"github", "abcdabc":"simejinet"}`) - buf, _ = ioutil.ReadAll(rr) - json, _ = simplejson.NewJson(buf) - - b, candidate = getCandidateKeyItem(json, "a") - assert.True(b) - - rr = bytes.NewBufferString(`{"abcde":"simeji", "abcdef":"github", "abcdabc":"simejinet"}`) - buf, _ = ioutil.ReadAll(rr) - json, _ = simplejson.NewJson(buf) - - b, candidate = getCandidateKeyItem(json, "ba") - assert.False(b) - assert.Equal("", candidate) - - rr = bytes.NewBufferString(`{"abcde":"simeji", "abcdef":"github", "abcdabc":"simejinet"}`) - buf, _ = ioutil.ReadAll(rr) - json, _ = simplejson.NewJson(buf) - - b, candidate = getCandidateKeyItem(json, "abcde") - assert.True(b) - assert.Equal("", candidate) - - rr = bytes.NewBufferString(`[1, 2, 3, "test"]`) - buf, _ = ioutil.ReadAll(rr) - json, _ = simplejson.NewJson(buf) - - b, candidate = getCandidateKeyItem(json, "3") - assert.False(b) - assert.Equal("", candidate) -} - func TestIsEmptyJson(t *testing.T) { var assert = assert.New(t) r := bytes.NewBufferString(`{"name":"go"}`) diff --git a/query.go b/query.go index f503538..df9ea44 100644 --- a/query.go +++ b/query.go @@ -18,6 +18,7 @@ type QueryInterface interface { StringSet(query string) string StringAdd(query string) string StringGetKeywords() []string + StringGetLastKeyword() string StringPopKeyword() (string, []rune) } @@ -79,7 +80,7 @@ func (q *Query) GetKeywords() [][]rune { keywords := [][]rune{} for i, keyword := range splitQuery { if keyword != "" || i == lastIdx { - re := regexp.MustCompile(`\[[0-9]+\]`) + re := regexp.MustCompile(`\[[0-9]*\]?`) matchIndexes := re.FindAllStringIndex(keyword, -1) if len(matchIndexes) < 1 { keywords = append(keywords, []rune(keyword)) @@ -99,7 +100,14 @@ func (q *Query) GetKeywords() [][]rune { func (q *Query) GetLastKeyword() []rune { keywords := q.GetKeywords() - return keywords[len(keywords)-1] + 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) { diff --git a/query_test.go b/query_test.go index f72c4e6..f0e305a 100644 --- a/query_test.go +++ b/query_test.go @@ -154,6 +154,20 @@ func TestGetKeywords(t *testing.T) { []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{ diff --git a/suggestion.go b/suggestion.go new file mode 100644 index 0000000..0c5d9bf --- /dev/null +++ b/suggestion.go @@ -0,0 +1,100 @@ +package jig + +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 Suggestion struct { +} + +func NewSuggestion() *Suggestion { + return &Suggestion{} +} + +func (s *Suggestion) Get(json *simplejson.Json, keyword string) string { + var result string + var suggestion string + + if a, err := json.Array(); err == nil { + if len(a) > 1 { + if keyword == "" { + return "[" + } else { + return "" + } + } else { + return strings.Replace(`[0]`, keyword, "", -1) + } + } + if keyword == "" { + return "" + } + + for _, key := range s.GetCandidateKeys(json, keyword) { + if suggestion == "" { + suggestion = key + } else { + axis := suggestion + if len(suggestion) > len(key) { + axis = key + } + max := 0 + for i, _ := range axis { + if suggestion[i] == key[i] { + max = i + } + } + suggestion = suggestion[0 : max+1] + } + } + if reg, err := regexp.Compile("(?i)^" + keyword); err == nil { + result = reg.ReplaceAllString(suggestion, "") + } + return result +} + +func (s *Suggestion) GetCandidateKeys(json *simplejson.Json, keyword string) []string { + var 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 +} diff --git a/suggestion_test.go b/suggestion_test.go new file mode 100644 index 0000000..3822627 --- /dev/null +++ b/suggestion_test.go @@ -0,0 +1,59 @@ +package jig + +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", "naming":"simeji", "nickname":"simejisimeji"}`) + s := NewSuggestion() + assert.Equal(s.Get(j, "na"), "m") + + j = createJson(`{"abcde":"simeji-github", "abcdef":"simeji", "ab":"simejisimeji"}`) + assert.Equal("", s.Get(j, "")) + assert.Equal("b", s.Get(j, "a")) + assert.Equal("de", s.Get(j, "abc")) + assert.Equal("", s.Get(j, "abcde")) + + j = createJson(`["zero"]`) + assert.Equal("[0]", s.Get(j, "")) + assert.Equal("0]", s.Get(j, "[")) + assert.Equal("]", s.Get(j, "[0")) + + j = createJson(`["zero", "one"]`) + assert.Equal("[", s.Get(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")) + + j = createJson(`{"abcde":"simeji-github", "abcdef":"simeji", "ab":"simejisimeji"}`) + assert.Equal([]string{"abcde", "abcdef"}, s.GetCandidateKeys(j, "abcde")) + + 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 +} From 7a725d662b3556f3cb949ed527d698280c8455b7 Mon Sep 17 00:00:00 2001 From: simeji Date: Sun, 4 Sep 2016 12:17:23 +0900 Subject: [PATCH 4/5] add Tests add new behabior (in test) Pass all test (but it have some bugs) --- engine.go | 280 +++++++------------------------------------ engine_test.go | 59 ++++++++- json_manager.go | 31 +++-- json_manager_test.go | 60 +++++++--- query_test.go | 16 +++ suggestion.go | 47 ++++++-- suggestion_test.go | 32 +++-- 7 files changed, 244 insertions(+), 281 deletions(-) diff --git a/engine.go b/engine.go index 7cf11b9..810e910 100644 --- a/engine.go +++ b/engine.go @@ -14,10 +14,11 @@ const ( ) type Engine struct { - manager *JsonManager - jq bool - pretty bool - query *Query + manager *JsonManager + jq bool + pretty bool + query *Query + complete []string } func NewEngine(s io.Reader, q bool, p bool) *Engine { @@ -26,10 +27,11 @@ func NewEngine(s io.Reader, q bool, p bool) *Engine { return &Engine{} } e := &Engine{ - manager: j, - jq: q, - pretty: p, - query: NewQuery([]rune("")), + manager: j, + jq: q, + pretty: p, + query: NewQuery([]rune("")), + complete: []string{"", ""}, } return e } @@ -57,7 +59,6 @@ func (e Engine) Run() int { return 0 } -// fix:me func (e *Engine) render() bool { err := termbox.Init() @@ -66,50 +67,39 @@ func (e *Engine) render() bool { } defer termbox.Close() - contents, _, _ := e.manager.GetPretty(e.query) keymode := false - var complete string + var contents []string + var c string for { //var flgFilter bool - contents, complete, _ = e.manager.GetPretty(e.query) if keymode { - //ckeys := []string{} - //kws := e.query.StringGetKeywords() - //if lkw := kws[len(kws)-1]; lkw != "" && !flgFilter { - //for k, _ := range e.getFilteredCurrentKeys(e.json, lkw) { - //ckeys = append(ckeys, e.currentKeys[k]) - //} - //sort.Strings(ckeys) - //contents = ckeys - //} else { - //contents = e.currentKeys - //} + contents = e.manager.GetCandidateKeys(e.query) } else { - //contents, complete, _ = e.manager.GetPretty(e.query) + c, e.complete, _ = e.manager.GetPretty(e.query) + contents = strings.Split(c, "\n") } - e.draw(e.query.StringGet(), complete, strings.Split(contents, "\n")) + e.draw(e.query.StringGet(), e.complete[0], contents) switch ev := termbox.PollEvent(); ev.Type { case termbox.EventKey: switch ev.Key { - case termbox.KeyEsc, termbox.KeyCtrlC: - return false + case 0: + e.inputAction(ev.Ch) + case termbox.KeyBackspace, termbox.KeyBackspace2: + e.backspaceAction() + case termbox.KeyTab: + e.tabAction() case termbox.KeyCtrlK: keymode = !keymode case termbox.KeySpace: - _ = e.query.StringAdd(" ") + e.spaceAction() case termbox.KeyCtrlW: - //delete whole word to period - _, _ = e.query.PopKeyword() - case termbox.KeyBackspace, termbox.KeyBackspace2: - _ = e.query.Delete(1) - case termbox.KeyTab: - _ = e.query.StringAdd(complete) + e.ctrlwAction() case termbox.KeyEnter: return true - case 0: - _ = e.query.StringAdd(string(ev.Ch)) + case termbox.KeyEsc, termbox.KeyCtrlC: + return false default: } case termbox.EventError: @@ -120,204 +110,26 @@ func (e *Engine) render() bool { } } -// -//func (e *Engine) suggest() bool { -// s := e.query.StringGet() -// if arr, _ := e.json.Array(); arr != nil { -// if l := len(s); l < 1 { -// *complete = []rune("") -// return false -// } -// le := s[len(s)-1:] -// if le == "." { -// *complete = []rune("") -// return false -// } -// var rs string -// ds := regexp.MustCompile("\\[([0-9]*)?\\]?$").FindString(s) -// if len(arr) > 1 { -// if ds == "" { -// rs = "[" -// } else if le != "]" { -// rs = "]" -// } -// } else { -// rs = "[0]" -// } -// cs := strings.Replace(rs, ds, "", -1) -// *complete = []rune(cs) -// return true -// } -// kws := strings.Split(s, ".") -// lki := len(kws) - 1 -// if lki == 0 { -// return false -// } -// lkw, tkws := kws[lki], kws[:lki] -// -// re, err := regexp.Compile("(?i)^" + lkw) -// if err != nil { -// return false -// } -// m := e.getFilteredCurrentKeys(e.json, lkw) -// -// if len(m) == 1 { -// for k, v := range m { -// kw := re.ReplaceAllString(e.currentKeys[k], "") -// *complete = []rune(kw) -// s = strings.Join(tkws, ".") + "." + v -// } -// _ = e.query.StringSet(s) -// return true -// } else { -// var sw []rune -// cnt := 0 -// for k, _ := range m { -// tsw := []rune{} -// v := []rune(e.currentKeys[k]) -// if cnt == 0 { -// sw = v -// cnt = cnt + 1 -// continue -// } -// swl := len(sw) - 1 -// for i, s := range v { -// if i > swl { -// break -// } -// if sw[i] != s { -// break -// } -// tsw = append(tsw, s) -// } -// sw = tsw -// cnt = cnt + 1 -// } -// if len(sw) >= 0 { -// kw := re.ReplaceAllString(string(sw), "") -// *complete = []rune(kw) -// s = strings.Join(tkws, ".") + "." + lkw -// } -// _ = e.query.StringSet(s) -// return true -// } -// *complete = []rune("") -// return false -//} - -// func (e *Engine) getFilteredCurrentKeys(json *simplejson.Json, kw string) map[int]string { -// m := map[int]string{} -// -// re, err := regexp.Compile("(?i)^" + kw) -// if err != nil { -// return m -// } -// -// currentKeys := e.getCurrentKeys(json) -// for i, k := range currentKeys { -// if str := re.FindString(k); str != "" { -// m[i] = str -// } -// } -// return m -// } -// -//func (e *Engine) prettyContents() []string { -// s, _ := e.json.EncodePretty() -// return strings.Split(string(s), "\n") -//} - -//func (e *Engine) filterJson(json *simplejson.Json, q string) (*simplejson.Json, bool) { -// if len(q) < 1 { -// return json, false -// } -// keywords := strings.Split(q, ".") -// -// // check start "." -// if keywords[0] != "" { -// return &simplejson.Json{}, false -// } -// -// keywords = keywords[1:] -// -// re := regexp.MustCompile("\\[[0-9]*\\]") -// delre := regexp.MustCompile("\\[([0-9]+)?") -// -// lastIdx := len(keywords) - 1 -// -// flgMatchLastKw := false -// -// //eachFlg := false -// for ki, keyword := range keywords { -// if len(keyword) == 0 { -// if ki != lastIdx { -// json = &simplejson.Json{} -// } -// break -// } -// // abc[0] -// if keyword[:1] == "[" { -// matchIndexes := re.FindAllStringIndex(keyword, -1) -// lmi := len(matchIndexes) - 1 -// for idx, m := range matchIndexes { -// i, _ := strconv.Atoi(keyword[m[0]+1 : m[1]-1]) -// if idx == lmi && m[1]-m[0] == 2 { -// //eachFlg = true -// } else if tj := json.GetIndex(i); !isEmptyJson(tj) { -// json = tj -// } -// } -// } else if keyword[len(keyword)-1:] == "]" { -// matchIndexes := re.FindAllStringIndex(keyword, -1) -// kw := re.ReplaceAllString(keyword, "") -// -// tj := json.Get(kw) -// if ki != lastIdx { -// json = tj -// } else if !isEmptyJson(tj) { -// json = tj -// flgMatchLastKw = true -// } -// lmi := len(matchIndexes) - 1 -// for idx, m := range matchIndexes { -// i, _ := strconv.Atoi(keyword[m[0]+1 : m[1]-1]) -// if idx == lmi && m[1]-m[0] == 2 { -// //eachFlg = true -// } else if tj := json.GetIndex(i); !isEmptyJson(tj) { -// json = tj -// flgMatchLastKw = true -// } -// } -// } else { -// kw := delre.ReplaceAllString(keyword, "") -// tj := json.Get(kw) -// if ki != lastIdx { -// json = tj -// } else if len(e.getFilteredCurrentKeys(json, kw)) < 1 { -// json = tj -// } else if !isEmptyJson(tj) { -// json = tj -// flgMatchLastKw = true -// } -// } -// } -// return json, flgMatchLastKw -//} - -//func (e *Engine) 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 (e *Engine) spaceAction() { + _ = e.query.StringAdd(" ") +} +func (e *Engine) backspaceAction() { + _ = e.query.Delete(1) +} +func (e *Engine) ctrlwAction() { + _, _ = e.query.PopKeyword() + _ = e.query.StringAdd(".") +} +func (e *Engine) tabAction() { + if (e.complete[0] != e.complete[1]) && e.complete[0] != "" { + _, _ = e.query.PopKeyword() + _ = e.query.StringAdd(".") + } + _ = e.query.StringAdd(e.complete[1]) +} +func (e *Engine) inputAction(ch rune) { + _ = e.query.StringAdd(string(ch)) +} func (e *Engine) draw(query string, complete string, rows []string) { diff --git a/engine_test.go b/engine_test.go index 20fa603..9479507 100644 --- a/engine_test.go +++ b/engine_test.go @@ -17,5 +17,62 @@ func TestNewEngine(t *testing.T) { r := bytes.NewBufferString("{\"name\":\"go\"}") e = NewEngine(r, false, false) assert.NotEqual(e, &Engine{}, "they should be not equal") - //assert.Equal(e.json.Get("name").MustString(), "go", "they should be equal") +} + +func TestSpaceAction(t *testing.T) { + var assert = assert.New(t) + r := bytes.NewBufferString(`{"name":"go"}`) + e := NewEngine(r, false, false) + e.query.StringSet(".name") + + e.spaceAction() + assert.Equal(".name ", e.query.StringGet()) +} + +func TestBackSpaceAction(t *testing.T) { + var assert = assert.New(t) + r := bytes.NewBufferString(`{"name":"go"}`) + e := NewEngine(r, false, false) + e.query.StringSet(".name") + + e.backspaceAction() + assert.Equal(".nam", e.query.StringGet()) +} + +func TestCtrlwAction(t *testing.T) { + var assert = assert.New(t) + r := bytes.NewBufferString(`{"name":"go"}`) + e := NewEngine(r, false, false) + e.query.StringSet(".name") + + e.ctrlwAction() + assert.Equal(".", e.query.StringGet()) +} + +func TestTabAction(t *testing.T) { + var assert = assert.New(t) + r := bytes.NewBufferString(`{"name":"go","NameTest":[1,2,3]}`) + e := NewEngine(r, false, false) + e.query.StringSet(".namet") + e.complete = []string{"est", "NameTest"} + + e.tabAction() + assert.Equal(".NameTest", e.query.StringGet()) + + _, e.complete, _ = e.manager.GetPretty(e.query) + e.tabAction() + assert.Equal(".NameTest[", e.query.StringGet()) +} + +func TestInputAction(t *testing.T) { + var assert = assert.New(t) + r := bytes.NewBufferString(`{"name":"go"}`) + e := NewEngine(r, false, false) + e.query.StringSet(".name") + + e.inputAction('n') + assert.Equal(".namen", e.query.StringGet()) + + e.inputAction('.') + assert.Equal(".namen.", e.query.StringGet()) } diff --git a/json_manager.go b/json_manager.go index 6029072..bf012d8 100644 --- a/json_manager.go +++ b/json_manager.go @@ -38,28 +38,28 @@ func NewJsonManager(reader io.Reader) (*JsonManager, error) { return json, nil } -func (jm *JsonManager) Get(q QueryInterface) (string, string, error) { +func (jm *JsonManager) Get(q QueryInterface) (string, []string, error) { json, suggestion, _ := jm.GetFilteredData(q) data, enc_err := json.Encode() if enc_err != nil { - return "", "", errors.Wrap(enc_err, "failure json encode") + return "", []string{"", ""}, errors.Wrap(enc_err, "failure json encode") } return string(data), suggestion, nil } -func (jm *JsonManager) GetPretty(q QueryInterface) (string, string, error) { +func (jm *JsonManager) GetPretty(q QueryInterface) (string, []string, error) { json, suggestion, _ := jm.GetFilteredData(q) s, err := json.EncodePretty() if err != nil { - return "", "", errors.Wrap(err, "failure json encode") + return "", []string{"", ""}, errors.Wrap(err, "failure json encode") } return string(s), suggestion, nil } -func (jm *JsonManager) GetFilteredData(q QueryInterface) (*simplejson.Json, string, error) { +func (jm *JsonManager) GetFilteredData(q QueryInterface) (*simplejson.Json, []string, error) { json := jm.origin lastKeyword := q.StringGetLastKeyword() @@ -74,17 +74,24 @@ func (jm *JsonManager) GetFilteredData(q QueryInterface) (*simplejson.Json, stri reg := regexp.MustCompile(`\[[0-9]*$`) suggest := jm.suggestion.Get(json, lastKeyword) - - if len(reg.FindString(lastKeyword)) > 0 { - } else if j, _ := getItem(json, lastKeyword); !isEmptyJson(j) { - json = j - suggest = jm.suggestion.Get(json, "") - } else if len(jm.suggestion.GetCandidateKeys(json, lastKeyword)) < 1 { - json = j + candidateKeys := jm.suggestion.GetCandidateKeys(json, lastKeyword) + if len(reg.FindString(lastKeyword)) < 1 { + candidateNum := len(candidateKeys) + if j, _ := getItem(json, lastKeyword); !isEmptyJson(j) && candidateNum == 1 { + json = j + suggest = jm.suggestion.Get(json, "") + } else if candidateNum < 1 { + json = j + } else { + } } return json, suggest, 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, error) { var result *simplejson.Json diff --git a/json_manager_test.go b/json_manager_test.go index f033729..e79dd7e 100644 --- a/json_manager_test.go +++ b/json_manager_test.go @@ -25,7 +25,7 @@ func TestNewJson(t *testing.T) { }) assert.Nil(e) - assert.Equal(jm.current.Get("name").MustString(), "go") + assert.Equal("go", jm.current.Get("name").MustString()) } func TestNewJsonWithError(t *testing.T) { @@ -48,7 +48,7 @@ func TestGet(t *testing.T) { assert.Nil(err) assert.Equal(`"go"`, result) - assert.Equal(``, suggest) + assert.Equal([]string{``, ``}, suggest) // data data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}` @@ -59,8 +59,9 @@ func TestGet(t *testing.T) { q = NewQueryWithString(".abcde") result, suggest, err = jm.Get(q) assert.Nil(err) - assert.Equal(`"2AA2"`, result) - assert.Equal(``, suggest) + //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") @@ -84,14 +85,14 @@ func TestGet(t *testing.T) { result, suggest, err = jm.Get(q) assert.Nil(err) assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result) - assert.Equal(`a`, suggest) + assert.Equal([]string{`a`, `aaa`}, suggest) // case 7 q = NewQueryWithString(".abcde_fgh.ac") result, suggest, err = jm.Get(q) assert.Nil(err) assert.Equal(`null`, result) - assert.Equal(``, suggest) + assert.Equal([]string{``, ``}, suggest) } func TestGetPretty(t *testing.T) { @@ -166,8 +167,9 @@ func TestGetFilteredData(t *testing.T) { result, s, err := jm.GetFilteredData(q) assert.Nil(err) d, _ := result.Encode() - assert.Equal(`"2AA2"`, string(d)) - assert.Equal(``, s) + assert.Equal(`{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}`, string(d)) + //assert.Equal(`"2AA2"`, string(d)) + assert.Equal([]string{``, `abcde`}, s) // case 2 q = NewQueryWithString(".abcde_fgh") @@ -175,14 +177,15 @@ func TestGetFilteredData(t *testing.T) { assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) - assert.Equal(``, s) + assert.Equal([]string{`.`, `.`}, s) // case 3 q = NewQueryWithString(".abcde_fgh.aaa[2]") - result, _, err = jm.GetFilteredData(q) + result, s, err = jm.GetFilteredData(q) assert.Nil(err) d, _ = result.Encode() assert.Equal(`[1,2]`, string(d)) + assert.Equal([]string{``, `[`}, s) // case 4 q = NewQueryWithString(".abcde_fgh.aaa[3]") @@ -190,7 +193,7 @@ func TestGetFilteredData(t *testing.T) { assert.Nil(err) d, _ = result.Encode() assert.Equal(`null`, string(d)) - assert.Equal(``, s) + assert.Equal([]string{``, `[`}, s) // case 5 q = NewQueryWithString(".abcde_fgh.aaa") @@ -198,7 +201,7 @@ func TestGetFilteredData(t *testing.T) { assert.Nil(err) d, _ = result.Encode() assert.Equal(`[123,"cccc",[1,2]]`, string(d)) - assert.Equal(`[`, s) + assert.Equal([]string{`[`, `[`}, s) // case 6 q = NewQueryWithString(".abcde_fgh.aa") @@ -206,7 +209,7 @@ func TestGetFilteredData(t *testing.T) { assert.Nil(err) d, _ = result.Encode() assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d)) - assert.Equal(`a`, s) + assert.Equal([]string{`a`, `aaa`}, s) // case 7 q = NewQueryWithString(".abcde_fgh.aaa[") @@ -214,7 +217,36 @@ func TestGetFilteredData(t *testing.T) { assert.Nil(err) d, _ = result.Encode() assert.Equal(`[123,"cccc",[1,2]]`, string(d)) - assert.Equal(``, s) + assert.Equal([]string{``, `[`}, s) +} + +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, err := jm.GetFilteredData(q) + assert.Nil(err) + d, _ := result.Encode() + assert.Equal(`[1,2,3]`, string(d)) + assert.Equal([]string{"[", "["}, s) + + q = NewQueryWithString(`.naming`) + result, s, err = jm.GetFilteredData(q) + assert.Nil(err) + d, _ = result.Encode() + assert.Equal(`{"account":"simeji"}`, string(d)) + assert.Equal([]string{".", "."}, s) + + q = NewQueryWithString(`.test`) + result, s, err = jm.GetFilteredData(q) + 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) } func TestGetCurrentKeys(t *testing.T) { diff --git a/query_test.go b/query_test.go index f0e305a..625c7a4 100644 --- a/query_test.go +++ b/query_test.go @@ -211,6 +211,22 @@ func TestGetLastKeyword(t *testing.T) { assert.Equal(q.GetLastKeyword(), []rune("test")) } +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") +} + func TestPopKeyword(t *testing.T) { var assert = assert.New(t) diff --git a/suggestion.go b/suggestion.go index 0c5d9bf..bff1f86 100644 --- a/suggestion.go +++ b/suggestion.go @@ -8,10 +8,21 @@ import ( ) type SuggestionInterface interface { - Get(json *simplejson.Json, keyword string) string + 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 { } @@ -19,26 +30,29 @@ func NewSuggestion() *Suggestion { return &Suggestion{} } -func (s *Suggestion) Get(json *simplejson.Json, keyword string) string { +func (s *Suggestion) Get(json *simplejson.Json, keyword string) []string { var result string var suggestion string + candidateKeys := s.GetCandidateKeys(json, keyword) + if a, err := json.Array(); err == nil { if len(a) > 1 { - if keyword == "" { - return "[" - } else { - return "" + rep := "" + if len(keyword) > 0 { + rep = keyword[0:1] } - } else { - return strings.Replace(`[0]`, keyword, "", -1) + return []string{strings.Replace(`[`, rep, "", -1), `[`} } + return []string{strings.Replace(`[0]`, keyword, "", -1), `[0]`} } if keyword == "" { - return "" + if _, err := json.Map(); err == nil { + return []string{".", "."} + } } - for _, key := range s.GetCandidateKeys(json, keyword) { + for _, key := range candidateKeys { if suggestion == "" { suggestion = key } else { @@ -58,7 +72,7 @@ func (s *Suggestion) Get(json *simplejson.Json, keyword string) string { if reg, err := regexp.Compile("(?i)^" + keyword); err == nil { result = reg.ReplaceAllString(suggestion, "") } - return result + return []string{result, suggestion} } func (s *Suggestion) GetCandidateKeys(json *simplejson.Json, keyword string) []string { @@ -98,3 +112,14 @@ func getCurrentKeys(json *simplejson.Json) []string { 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 +} diff --git a/suggestion_test.go b/suggestion_test.go index 3822627..f9ce4af 100644 --- a/suggestion_test.go +++ b/suggestion_test.go @@ -17,21 +17,35 @@ func TestSuggestionGet(t *testing.T) { var assert = assert.New(t) j := createJson(`{"name":"simeji-github", "naming":"simeji", "nickname":"simejisimeji"}`) s := NewSuggestion() - assert.Equal(s.Get(j, "na"), "m") + assert.Equal([]string{"m", "nam"}, s.Get(j, "na")) j = createJson(`{"abcde":"simeji-github", "abcdef":"simeji", "ab":"simejisimeji"}`) - assert.Equal("", s.Get(j, "")) - assert.Equal("b", s.Get(j, "a")) - assert.Equal("de", s.Get(j, "abc")) - assert.Equal("", s.Get(j, "abcde")) + 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("[0]", s.Get(j, "")) - assert.Equal("0]", s.Get(j, "[")) - assert.Equal("]", s.Get(j, "[0")) + 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("[", s.Get(j, "")) + assert.Equal([]string{"[", "["}, s.Get(j, "")) + + assert.Equal([]string{"", "["}, s.Get(j, "[")) +} + +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)) } func TestSuggestionGetCandidateKeys(t *testing.T) { From ae9accfbe3b8a8bf330f977012a9bce6365bf5af Mon Sep 17 00:00:00 2001 From: simeji Date: Tue, 11 Oct 2016 03:02:16 +0900 Subject: [PATCH 5/5] Add candidate mode (interacive selection) --- .gitignore | 2 + engine.go | 147 ++++++++++++++++++++++++++----------------- engine_test.go | 65 ++++++++++++++++++- json_manager.go | 42 ++++++++----- json_manager_test.go | 112 ++++++++++++++++++++++++++------- suggestion.go | 41 +++++++----- suggestion_test.go | 23 ++++++- terminal.go | 97 ++++++++++++++++++++++++++++ 8 files changed, 412 insertions(+), 117 deletions(-) create mode 100644 terminal.go diff --git a/.gitignore b/.gitignore index eca5e9f..08de947 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ _testmain.go *.test *.prof *.swp +*.test +test.* # jig package jig diff --git a/engine.go b/engine.go index 810e910..d896272 100644 --- a/engine.go +++ b/engine.go @@ -8,17 +8,22 @@ import ( ) const ( - PROMPT = "[jig]>> " DefaultY int = 1 FilterPrompt string = "[Filter]> " ) type Engine struct { - manager *JsonManager - jq bool - pretty bool - query *Query - complete []string + manager *JsonManager + jq bool + pretty bool + query *Query + term *Terminal + complete []string + keymode bool + candidatemode bool + candidateidx int + contentOffset int + queryConfirm bool } func NewEngine(s io.Reader, q bool, p bool) *Engine { @@ -27,11 +32,17 @@ func NewEngine(s io.Reader, q bool, p bool) *Engine { return &Engine{} } e := &Engine{ - manager: j, - jq: q, - pretty: p, - query: NewQuery([]rune("")), - complete: []string{"", ""}, + manager: j, + jq: q, + pretty: p, + term: NewTerminal(FilterPrompt, DefaultY), + query: NewQuery([]rune("")), + complete: []string{"", ""}, + keymode: false, + candidatemode: false, + candidateidx: 0, + contentOffset: 0, + queryConfirm: false, } return e } @@ -44,13 +55,13 @@ func (e Engine) Run() int { if e.jq { fmt.Printf("%s", e.query.StringGet()) } else if e.pretty { - s, _, err := e.manager.GetPretty(e.query) + s, _, _, err := e.manager.GetPretty(e.query, true) if err != nil { return 1 } fmt.Printf("%s", s) } else { - s, _, err := e.manager.Get(e.query) + s, _, _, err := e.manager.Get(e.query, true) if err != nil { return 1 } @@ -67,20 +78,33 @@ func (e *Engine) render() bool { } defer termbox.Close() - keymode := false - var contents []string + var candidates []string var c string for { - //var flgFilter bool - if keymode { - contents = e.manager.GetCandidateKeys(e.query) + c, e.complete, candidates, _ = e.manager.GetPretty(e.query, e.queryConfirm) + e.queryConfirm = false + if e.keymode { + contents = candidates } else { - c, e.complete, _ = e.manager.GetPretty(e.query) contents = strings.Split(c, "\n") } - e.draw(e.query.StringGet(), e.complete[0], contents) + if l := len(candidates); e.complete[0] == "" && l > 1 { + //e.candidatemode = true + if e.candidateidx >= l { + e.candidateidx = 0 + } + } else { + e.candidatemode = false + } + if !e.candidatemode { + e.candidateidx = 0 + candidates = []string{} + } + + e.term.draw(e.query.StringGet(), e.complete[0], contents, candidates, e.candidateidx, e.contentOffset) + switch ev := termbox.PollEvent(); ev.Type { case termbox.EventKey: switch ev.Key { @@ -91,14 +115,27 @@ func (e *Engine) render() bool { case termbox.KeyTab: e.tabAction() case termbox.KeyCtrlK: - keymode = !keymode + e.ctrlkAction() + case termbox.KeyCtrlJ: + e.ctrljAction() + case termbox.KeyCtrlL: + e.ctrllAction() case termbox.KeySpace: e.spaceAction() case termbox.KeyCtrlW: e.ctrlwAction() + case termbox.KeyEsc: + e.escAction() case termbox.KeyEnter: - return true - case termbox.KeyEsc, termbox.KeyCtrlC: + if !e.candidatemode { + return true + } + _, _ = e.query.PopKeyword() + _ = e.query.StringAdd(".") + _ = e.query.StringAdd(candidates[e.candidateidx]) + e.queryConfirm = true + + case termbox.KeyCtrlC: return false default: } @@ -116,49 +153,41 @@ func (e *Engine) spaceAction() { func (e *Engine) backspaceAction() { _ = e.query.Delete(1) } -func (e *Engine) ctrlwAction() { - _, _ = e.query.PopKeyword() - _ = e.query.StringAdd(".") +func (e *Engine) ctrljAction() { + e.contentOffset++ } -func (e *Engine) tabAction() { - if (e.complete[0] != e.complete[1]) && e.complete[0] != "" { - _, _ = e.query.PopKeyword() - _ = e.query.StringAdd(".") +func (e *Engine) ctrlkAction() { + if o := e.contentOffset - 1; o >= 0 { + e.contentOffset = o } - _ = e.query.StringAdd(e.complete[1]) } -func (e *Engine) inputAction(ch rune) { - _ = e.query.StringAdd(string(ch)) +func (e *Engine) ctrllAction() { + e.keymode = !e.keymode } - -func (e *Engine) draw(query string, complete string, rows []string) { - - termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) - - fs := FilterPrompt + query - cs := complete - drawln(0, 0, fs+cs, []([]int){[]int{len(fs), len(fs) + len(cs)}}) - termbox.SetCursor(len(fs), 0) - - for idx, row := range rows { - drawln(0, idx+DefaultY, row, nil) +func (e *Engine) ctrlwAction() { + if k, _ := e.query.StringPopKeyword(); k != "" && !strings.Contains(k, "[") { + _ = e.query.StringAdd(".") } - - termbox.Flush() } - -func drawln(x int, y int, str string, matches [][]int) { - color := termbox.ColorDefault - backgroundColor := termbox.ColorDefault - - var c termbox.Attribute - for i, s := range str { - c = color - for _, match := range matches { - if i >= match[0] && i < match[1] { - c = termbox.ColorGreen +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(".") } } - termbox.SetCell(x+i, y, s, c, backgroundColor) + if e.query.StringGet() == "" { + _ = e.query.StringAdd(".") + } + _ = e.query.StringAdd(e.complete[1]) + } else { + e.candidateidx = e.candidateidx + 1 } } +func (e *Engine) escAction() { + e.candidatemode = false +} +func (e *Engine) inputAction(ch rune) { + _ = e.query.StringAdd(string(ch)) +} diff --git a/engine_test.go b/engine_test.go index 9479507..e0b282a 100644 --- a/engine_test.go +++ b/engine_test.go @@ -47,6 +47,50 @@ func TestCtrlwAction(t *testing.T) { e.ctrlwAction() assert.Equal(".", e.query.StringGet()) + + e.query.StringSet(".name[1]") + e.ctrlwAction() + assert.Equal(".name", e.query.StringGet()) + + e.query.StringSet(".name[") + e.ctrlwAction() + assert.Equal(".name", e.query.StringGet()) +} + +func TestCtrlKAction(t *testing.T) { + var assert = assert.New(t) + r := bytes.NewBufferString(`{"name":"go","NameTest":[1,2,3]}`) + e := NewEngine(r, false, false) + assert.Equal(0, e.contentOffset) + e.ctrlkAction() + assert.Equal(0, e.contentOffset) + e.contentOffset = 5 + e.ctrlkAction() + assert.Equal(4, e.contentOffset) + e.ctrlkAction() + assert.Equal(3, e.contentOffset) +} + +func TestCtrlJAction(t *testing.T) { + var assert = assert.New(t) + r := bytes.NewBufferString(`{"name":"go","NameTest":[1,2,3]}`) + e := NewEngine(r, false, false) + e.ctrljAction() + assert.Equal(1, e.contentOffset) + e.ctrljAction() + e.ctrljAction() + assert.Equal(3, e.contentOffset) +} + +func TestCtrllAction(t *testing.T) { + var assert = assert.New(t) + r := bytes.NewBufferString(`{"name":"go","NameTest":[1,2,3]}`) + e := NewEngine(r, false, false) + assert.False(e.keymode) + e.ctrllAction() + assert.True(e.keymode) + e.ctrllAction() + assert.False(e.keymode) } func TestTabAction(t *testing.T) { @@ -56,12 +100,31 @@ func TestTabAction(t *testing.T) { 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) + _, 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) + r := bytes.NewBufferString(`{"name":"go","NameTest":[1,2,3]}`) + e := NewEngine(r, false, false) + assert.False(e.candidatemode) + e.escAction() + assert.False(e.candidatemode) + e.candidatemode = true + e.escAction() + assert.False(e.candidatemode) } func TestInputAction(t *testing.T) { diff --git a/json_manager.go b/json_manager.go index bf012d8..23cb12d 100644 --- a/json_manager.go +++ b/json_manager.go @@ -38,32 +38,33 @@ func NewJsonManager(reader io.Reader) (*JsonManager, error) { return json, nil } -func (jm *JsonManager) Get(q QueryInterface) (string, []string, error) { - json, suggestion, _ := jm.GetFilteredData(q) +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{"", ""}, errors.Wrap(enc_err, "failure json encode") + return "", []string{"", ""}, []string{"", ""}, errors.Wrap(enc_err, "failure json encode") } - return string(data), suggestion, nil + return string(data), suggestion, candidates, nil } -func (jm *JsonManager) GetPretty(q QueryInterface) (string, []string, error) { - json, suggestion, _ := jm.GetFilteredData(q) +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{"", ""}, errors.Wrap(err, "failure json encode") + return "", []string{"", ""}, []string{"", ""}, errors.Wrap(err, "failure json encode") } - return string(s), suggestion, nil + return string(s), suggestion, candidates, nil } -func (jm *JsonManager) GetFilteredData(q QueryInterface) (*simplejson.Json, []string, error) { +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 { idx = l - 1 @@ -75,36 +76,47 @@ func (jm *JsonManager) GetFilteredData(q QueryInterface) (*simplejson.Json, []st suggest := jm.suggestion.Get(json, lastKeyword) candidateKeys := jm.suggestion.GetCandidateKeys(json, lastKeyword) + // hash if len(reg.FindString(lastKeyword)) < 1 { candidateNum := len(candidateKeys) - if j, _ := getItem(json, lastKeyword); !isEmptyJson(j) && candidateNum == 1 { + if j, exist := getItem(json, lastKeyword); exist && (confirm || candidateNum == 1) { json = j + candidateKeys = []string{} suggest = jm.suggestion.Get(json, "") } else if candidateNum < 1 { json = j - } else { + suggest = jm.suggestion.Get(json, "") } } - return json, suggest, nil + 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, error) { +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 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 = json.Get(s) + result, exist = json.CheckGet(s) + if result == nil { + result = &simplejson.Json{} + } } - return result, nil + return result, exist } func isEmptyJson(j *simplejson.Json) bool { diff --git a/json_manager_test.go b/json_manager_test.go index e79dd7e..da1ff4c 100644 --- a/json_manager_test.go +++ b/json_manager_test.go @@ -44,11 +44,12 @@ func TestGet(t *testing.T) { r := bytes.NewBufferString("{\"name\":\"go\"}") jm, _ := NewJsonManager(r) q := NewQueryWithString(".name") - result, suggest, err := jm.Get(q) + 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"}}` @@ -57,7 +58,7 @@ func TestGet(t *testing.T) { // case 2 q = NewQueryWithString(".abcde") - result, suggest, err = jm.Get(q) + 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) @@ -65,31 +66,31 @@ func TestGet(t *testing.T) { // case 3 q = NewQueryWithString(".abcde_fgh") - result, suggest, err = jm.Get(q) + result, suggest, candidateKeys, err = jm.Get(q, false) assert.Nil(err) assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result) // case 4 q = NewQueryWithString(".abcde_fgh.aaa[2]") - result, suggest, err = jm.Get(q) + result, suggest, candidateKeys, err = jm.Get(q, false) assert.Equal(`[1,2]`, result) // case 5 q = NewQueryWithString(".abcde_fgh.aaa[3]") - result, suggest, err = jm.Get(q) + result, suggest, candidateKeys, err = jm.Get(q, false) assert.Nil(err) assert.Equal(`null`, result) // case 6 q = NewQueryWithString(".abcde_fgh.aa") - result, suggest, err = jm.Get(q) + 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, err = jm.Get(q) + result, suggest, candidateKeys, err = jm.Get(q, false) assert.Nil(err) assert.Equal(`null`, result) assert.Equal([]string{``, ``}, suggest) @@ -101,7 +102,7 @@ func TestGetPretty(t *testing.T) { r := bytes.NewBufferString("{\"name\":\"go\"}") jm, _ := NewJsonManager(r) q := NewQueryWithString(".name") - result, _, err := jm.GetPretty(q) + result, _, _, err := jm.GetPretty(q, true) assert.Nil(err) assert.Equal(`"go"`, result) @@ -158,46 +159,48 @@ func TestGetFilteredData(t *testing.T) { var assert = assert.New(t) // data - data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}` + 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, err := jm.GetFilteredData(q) + 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"}}`, string(d)) + 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, err = jm.GetFilteredData(q) + 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{``, ``}, s) + assert.Equal([]string{}, c) // case 3 q = NewQueryWithString(".abcde_fgh.aaa[2]") - result, s, err = jm.GetFilteredData(q) + result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`[1,2]`, string(d)) - assert.Equal([]string{``, `[`}, s) + assert.Equal([]string{`[`, `[`}, s) // case 4 q = NewQueryWithString(".abcde_fgh.aaa[3]") - result, s, err = jm.GetFilteredData(q) + 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{``, ``}, s) // case 5 q = NewQueryWithString(".abcde_fgh.aaa") - result, s, err = jm.GetFilteredData(q) + result, s, c, err = jm.GetFilteredData(q, false) assert.Nil(err) d, _ = result.Encode() assert.Equal(`[123,"cccc",[1,2]]`, string(d)) @@ -205,7 +208,7 @@ func TestGetFilteredData(t *testing.T) { // case 6 q = NewQueryWithString(".abcde_fgh.aa") - result, s, err = jm.GetFilteredData(q) + 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)) @@ -213,11 +216,41 @@ func TestGetFilteredData(t *testing.T) { // case 7 q = NewQueryWithString(".abcde_fgh.aaa[") - result, s, err = jm.GetFilteredData(q) + 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) } func TestGetFilteredDataWithMatchQuery(t *testing.T) { @@ -228,25 +261,56 @@ func TestGetFilteredDataWithMatchQuery(t *testing.T) { jm, _ := NewJsonManager(r) q := NewQueryWithString(`.name`) - result, s, err := jm.GetFilteredData(q) + 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, err = jm.GetFilteredData(q) + 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{"account", "account"}, s) + assert.Equal([]string{}, c) q = NewQueryWithString(`.test`) - result, s, err = jm.GetFilteredData(q) + 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) { diff --git a/suggestion.go b/suggestion.go index bff1f86..0109eb1 100644 --- a/suggestion.go +++ b/suggestion.go @@ -31,29 +31,35 @@ func NewSuggestion() *Suggestion { } func (s *Suggestion) Get(json *simplejson.Json, keyword string) []string { - var result string + var completion string var suggestion string - candidateKeys := s.GetCandidateKeys(json, keyword) - if a, err := json.Array(); err == nil { if len(a) > 1 { - rep := "" - if len(keyword) > 0 { - rep = keyword[0:1] + kw := regexp.MustCompile(`\[([0-9]+)?\]?`).FindString(keyword) + if kw == "" { + return []string{"[", "["} + } else if kw == "[" { + return []string{"", "["} } - return []string{strings.Replace(`[`, rep, "", -1), `[`} + return []string{strings.Replace(kw+"]", kw, "", -1), kw + "]"} } return []string{strings.Replace(`[0]`, keyword, "", -1), `[0]`} } + + candidateKeys := s.GetCandidateKeys(json, keyword) + if keyword == "" { - if _, err := json.Map(); err == nil { - return []string{".", "."} + if l := len(candidateKeys); l > 1 { + return []string{"", ""} + } else if l == 1 { + return []string{candidateKeys[0], candidateKeys[0]} } } for _, key := range candidateKeys { - if suggestion == "" { + // first + if suggestion == "" && key != "" { suggestion = key } else { axis := suggestion @@ -62,21 +68,26 @@ func (s *Suggestion) Get(json *simplejson.Json, keyword string) []string { } max := 0 for i, _ := range axis { - if suggestion[i] == key[i] { - max = i + 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 { - result = reg.ReplaceAllString(suggestion, "") + completion = reg.ReplaceAllString(suggestion, "") } - return []string{result, suggestion} + return []string{completion, suggestion} } func (s *Suggestion) GetCandidateKeys(json *simplejson.Json, keyword string) []string { - var candidates []string + candidates := []string{} if _, err := json.Array(); err == nil { return []string{} diff --git a/suggestion_test.go b/suggestion_test.go index f9ce4af..136251b 100644 --- a/suggestion_test.go +++ b/suggestion_test.go @@ -15,12 +15,14 @@ func TestNewSuggestion(t *testing.T) { func TestSuggestionGet(t *testing.T) { var assert = assert.New(t) - j := createJson(`{"name":"simeji-github", "naming":"simeji", "nickname":"simejisimeji"}`) + 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{"", ""}, 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")) @@ -32,8 +34,16 @@ func TestSuggestionGet(t *testing.T) { 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) { @@ -56,10 +66,17 @@ func TestSuggestionGetCandidateKeys(t *testing.T) { 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, "[")) diff --git a/terminal.go b/terminal.go new file mode 100644 index 0000000..f42f06b --- /dev/null +++ b/terminal.go @@ -0,0 +1,97 @@ +package jig + +import ( + "github.com/nsf/termbox-go" + "regexp" +) + +type Terminal struct { + defaultY int + prompt string + cursorPos []int +} + +func NewTerminal(prompt string, defaultY int) *Terminal { + return &Terminal{ + prompt: prompt, + defaultY: defaultY, + cursorPos: []int{0, 0}, + } +} + +func (t *Terminal) draw(query string, complete string, rows []string, candidates []string, candidateidx int, contentOffsetY int) { + + termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) + + fs := t.prompt + query + cs := complete + y := t.defaultY + + t.drawln(0, 0, fs+cs, []([]int){[]int{len(fs), len(fs) + len(cs)}}) + t.cursorPos = []int{len(fs), 0} + termbox.SetCursor(t.cursorPos[0], t.cursorPos[1]) + + if len(candidates) > 0 { + y = t.drawCandidates(0, t.defaultY, candidateidx, candidates) + } + + for idx, row := range rows { + if i := idx - contentOffsetY; i >= 0 { + t.drawln(0, i+y, row, nil) + } + } + + termbox.Flush() +} + +func (t *Terminal) drawln(x int, y int, str string, matches [][]int) { + color := termbox.ColorDefault + backgroundColor := termbox.ColorDefault + + var c termbox.Attribute + for i, s := range str { + c = color + for _, match := range matches { + if i >= match[0]+1 && i < match[1] { + c = termbox.ColorGreen + } + } + termbox.SetCell(x+i, y, s, c, backgroundColor) + } +} + +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 + for ii, s := range row { + c = color + backgroundColor = termbox.ColorMagenta + if match != nil && ii >= match[0]+1 && ii < match[1]-1 { + backgroundColor = termbox.ColorWhite + } + termbox.SetCell(x+ii, y+i, s, c, backgroundColor) + } + } + return y + len(rows) +}