Skip to content

Commit

Permalink
Feat/improver (#9)
Browse files Browse the repository at this point in the history
* feat: update readme

* feat: remove github.com/tkeel-io/core/pkg/tql

* feat: update gomod

* fix: number parse

* feat: embed collectjs & fix test

* feat: fix parse bug

* fix: unit test (GroupBy\MergeBy\KeyBy)

* feat: refactor Node

* feat: clean code

* feat: update gomod

* feat: clean code
  • Loading branch information
imneov authored Mar 2, 2022
1 parent 12e919c commit 91c2a64
Show file tree
Hide file tree
Showing 82 changed files with 1,567 additions and 6,803 deletions.
File renamed without changes.
2 changes: 1 addition & 1 deletion TDTL.g4
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ SUB: '-';
DOT: '.';
TRUE: T R U E;
FALSE: F A L S E;
INDENTIFIER: [a-zA-Z0-9_#][a-zA-Z_\-#$@0-9]*;
INDENTIFIER: [a-zA-Z_#][a-zA-Z_\-#$@0-9]*;
NUMBER: '0' | [1-9][0-9]* ;
INTEGER: ('+' | '-')? NUMBER;
FLOAT: ('+' | '-')? (NUMBER+ DOT NUMBER+ | NUMBER+ DOT | DOT NUMBER+);
Expand Down
40 changes: 40 additions & 0 deletions collect_sort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package tdtl

import "sort"

// A couple of type definitions to make the units clear.
type earthMass float64
type au float64

// By is the type of a "less" function that defines the ordering of its Planet arguments.
type By func(p1, p2 *Collect) bool

// Sort is a method on the function type, By, that sorts the argument slice according to the function.
func (by By) Sort(collects []*Collect) {
ps := &collectSorter{
collects: collects,
by: by, // The Sort method's receiver is the function (closure) that defines the sort order.
}
sort.Sort(ps)
}

// planetSorter joins a By function and a slice of Planets to be sorted.
type collectSorter struct {
collects []*Collect
by func(p1, p2 *Collect) bool // Closure used in the Less method.
}

// Len is part of sort.Interface.
func (s *collectSorter) Len() int {
return len(s.collects)
}

// Swap is part of sort.Interface.
func (s *collectSorter) Swap(i, j int) {
s.collects[i], s.collects[j] = s.collects[j], s.collects[i]
}

// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s *collectSorter) Less(i, j int) bool {
return s.by(s.collects[i], s.collects[j])
}
38 changes: 38 additions & 0 deletions collect_sort_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package tdtl

import (
"bytes"
"testing"
)

var planets = []*Collect{
New(`["Venus", 0.815, 0.7]`),
New(`["Earth", 1.0, 1.0]`),
New(`["Mars", 0.107, 1.5]`),
}

func TestSort(t *testing.T) {

// Closures that order the Planet structure.
name := func(p1, p2 *Collect) bool {
return bytes.Compare(p1.Get("[0]").Raw(), p2.Get("[0]").Raw()) > 0
}
mass := func(p1, p2 *Collect) bool {
return bytes.Compare(p1.Get("[1]").Raw(), p2.Get("[1]").Raw()) > 0
}
distance := func(p1, p2 *Collect) bool {
return bytes.Compare(p1.Get("[2]").Raw(), p2.Get("[2]").Raw()) > 0
}

// Sort the planets by the various criteria.
By(name).Sort(planets)
t.Log("By name:", string(planets[0].Raw()), string(planets[1].Raw()), string(planets[2].Raw()))

By(mass).Sort(planets)
t.Log("By mass:", string(planets[0].Raw()), string(planets[1].Raw()), string(planets[2].Raw()))

By(distance).Sort(planets)
t.Log("By distance:", string(planets[0].Raw()), string(planets[1].Raw()), string(planets[2].Raw()))

// Output:
}
33 changes: 33 additions & 0 deletions collect_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package tdtl

import (
"strings"
)

func Byte(raw string) []byte {
return []byte(raw)
}

func path2JSONPARSER(path string) []string {
keys := []string{}
if len(path) > 0 {
if path[0] == '"' && path[len(path)-1] == '"' {
return []string{path[1 : len(path)-1]}
}
path = strings.Replace(path, "[", ".[", -1)
keys = strings.Split(path, ".")
}
if len(keys) > 0 && keys[0] == "" {
return keys[1:]
}
return keys
}

func path2GJSON(path string) string {
path = strings.Replace(path, "[", ".", -1)
path = strings.Replace(path, "]", "", -1)
if len(path) > 0 && path[0] == '.' {
return path[1:]
}
return path
}
253 changes: 253 additions & 0 deletions collectjs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
package tdtl

import (
"errors"
"fmt"
"strings"

"github.com/tkeel-io/tdtl/pkg/json/jsonparser"
)

var EmptyBytes = []byte("")

type Collect = JSONNode

func New(raw interface{}) *Collect {
switch raw := raw.(type) {
case string:
return newCollect(Byte(raw))
case []byte:
return newCollect(raw)
case Result:
return newCollectFromJsonResult(raw)
}
return UNDEFINED_RESULT
}

func newCollect(data []byte) *Collect {
collect := &Collect{}
value := make([]byte, len(data))
copy(value, data)
collect.path = ""
collect.value = value
if _, jtype, _, err := jsonparser.Get(data); err == nil {
collect.datatype = datetype(jtype)
} else {
collect.err = err
}
return collect
}

func newCollectFromJsonResult(ret Result) *Collect {
collect := &Collect{}
collect.path = ""
collect.datatype = datetype(ret)
if collect.datatype == String {
collect.value = []byte(ret.Str)
} else {
collect.value = []byte(ret.Raw)
}

return collect
}

func newCollectFromJsonparserResult(dataType jsonparser.ValueType, value []byte) *Collect {
collect := &Collect{}
collect.path = ""
collect.value = []byte(value)
collect.datatype = datetype(dataType)
return collect
}

// GetError returns collect error.
func (cc *Collect) GetError() error {
return cc.err
}

func (cc *Collect) Node() Node {
return cc.To(cc.datatype)
}

func (cc *Collect) Get(path ...string) *Collect {
absPath := strings.Join(path, ".")
if absPath == "" {
return cc
}
ret := get(cc.value, absPath)
return ret
}

func (cc *Collect) Set(path string, value Node) {
cc.value, cc.err = set(cc.value, path, value.Raw())
}

func (cc *Collect) Append(path string, value Node) {
cc.value, cc.err = add(cc.value, path, value.Raw())
}

func (cc *Collect) Del(path ...string) {
cc.value = del(cc.value, path...)
}

func (cc *Collect) Copy() *JSONNode {
return newCollect(cc.value)
}

func (cc *Collect) Foreach(fn ForeachHandle) {
cc.value = forEach(cc.value, cc.datatype, fn)
}

func (cc *Collect) Map(handle MapHandle) {
ret := cc.Copy()
cc.Foreach(func(key []byte, value *Collect) {
newValue := handle(key, value)
ret.Set(string(key), newValue)
})
cc.value, cc.datatype = ret.value, ret.datatype
}

func (cc *Collect) GroupBy(path string) *Collect {
if cc.datatype != Array {
cc.err = fmt.Errorf("datatype is not array")
return cc
}

ret := New("{}")
cc.Foreach(func(key []byte, value *Collect) {
keyValue := get(value.Raw(), path).String()
if len(keyValue) == 0 {
return
}
keyValue = strings.Replace(keyValue, ".", "_", -1)
ret.Append(keyValue, value)
})
return ret
}

func (c *Collect) MergeBy(paths ...string) *Collect {
if c.datatype != Array {
c.err = fmt.Errorf("MergeBy: datatype is not array")
return c
}

var err error
ret := New("{}")
c.Foreach(func(key []byte, value *Collect) {
keys := make([]string, 0, len(paths))
for _, path := range paths {
keyValueRaw := value.Get(path).String()
if len(keyValueRaw) == 0 {
break
}
keys = append(keys, keyValueRaw)
}

if len(keys) == 0 {
return
}

keyValue := strings.Join(keys, "+")
keyValue = strings.Replace(keyValue, ".", "_", -1)

nv := ret.Get(keyValue)
nv = nv.Merge(value)
if nv.err != nil {
ret.err = err
}
ret.Set(keyValue, nv)
})
return ret
}

func (cc *Collect) SortBy(fn func(p1 *Collect, p2 *Collect) bool) {
if cc.datatype != Array && cc.datatype != Object {
cc.err = errors.New("SortBy:datatype is not array or object")
return
}
carr := make([]*Collect, 0)
cc.Foreach(func(key []byte, value *Collect) {
carr = append(carr, value)
})
By(fn).Sort(carr)

ret := New("[]")
for _, c := range carr {
ret.Append("", c)
}
cc.value = ret.value
cc.datatype = ret.datatype
}

func (c *Collect) KeyBy(path string) *Collect {
if c.datatype != Array {
c.err = errors.New("KeyBy:datatype is not array")
}

ret := New("{}")
c.Foreach(func(key []byte, value *Collect) {
keyValue := value.Get(path)
ret.Set(keyValue.String(), value)
})

return ret
}

func (cc *Collect) Merge(mc *Collect) *Collect {
if cc.datatype != Object && mc.datatype != Object {
cc.err = errors.New("datatype is not object")
return cc
}
if cc.datatype == Null {
return mc
}

mc.Foreach(func(key []byte, value *Collect) {
cc.Set(string(key), value)
})

return cc
}

//
//func (c *Collect) Sort( path string) ([]byte, error) {
// if c.datatype != Array {
// c.err = errors.New("SortBy:datatype is not array")
// return nil, errors.New("datatype is not array")
// }
//
// var err error
// ret := []byte("[]")
// c.Foreach(func(key []byte, value *Collect) {
// keyValue := get(value.Raw(), path).Raw()
// if keyValue[0] == '"' && keyValue[len(keyValue)-1] == '"' {
// keyValue = keyValue[1 : len(keyValue)-1]
// }
// if ret, err = jsonparser.Append(ret, value.Raw(), string(keyValue)); nil != err {
// c.err = err
// }
// })
//
// return ret, c.err
//}

func Combine(cKey *Collect, cValue *Collect) ([]byte, error) {
if cKey.datatype != Array {
return nil, errors.New("datatype is not array")
} else if cValue.datatype != Array {
return nil, errors.New("datatype is not array")
}

var (
idx int
err error
ret = []byte("{}")
)

cKey.Foreach(func(key []byte, value *Collect) {
if ret, err = jsonparser.Set(ret, get(cValue.value, fmt.Sprintf("[%d]", idx)).Raw(), value.String()); nil != err {
cKey.err = err
}
idx++
})
return ret, cKey.err
}
Loading

0 comments on commit 91c2a64

Please sign in to comment.