Skip to content

Commit

Permalink
Implement sortBy func
Browse files Browse the repository at this point in the history
  • Loading branch information
TomWright committed Oct 20, 2024
1 parent 1b27839 commit cfa37ca
Show file tree
Hide file tree
Showing 17 changed files with 543 additions and 51 deletions.
2 changes: 2 additions & 0 deletions execution/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ func exprExecutor(opts *Options, expr ast.Expr) (expressionExecutor, error) {
return func(data *model.Value) (*model.Value, error) {
return data, nil
}, nil
case ast.SortByExpr:
return sortByExprExecutor(opts, e)
default:
return nil, fmt.Errorf("unhandled expression type: %T", e)
}
Expand Down
154 changes: 154 additions & 0 deletions execution/execute_sort_by.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package execution

import (
"fmt"
"slices"

"github.com/tomwright/dasel/v3/model"
"github.com/tomwright/dasel/v3/selector/ast"
)

func sortByExprExecutor(opts *Options, e ast.SortByExpr) (expressionExecutor, error) {
return func(data *model.Value) (*model.Value, error) {
if !data.IsSlice() {
return nil, fmt.Errorf("cannot sort by on non-slice data")
}

type sortableValue struct {
index int
value *model.Value
}
values := make([]sortableValue, 0)

if err := data.RangeSlice(func(i int, item *model.Value) error {
item, err := ExecuteAST(e.Expr, item, opts)
if err != nil {
return err
}
values = append(values, sortableValue{
index: i,
value: item,
})
return nil
}); err != nil {
return nil, fmt.Errorf("error ranging over slice: %w", err)
}

slices.SortFunc(values, func(i, j sortableValue) int {
res, err := i.value.Compare(j.value)
if err != nil {
return 0
}
if e.Descending {
return -res
}
return res
})

res := model.NewSliceValue()

for _, i := range values {
item, err := data.GetSliceIndex(i.index)
if err != nil {
return nil, fmt.Errorf("error getting slice index: %w", err)
}
if err := res.Append(item); err != nil {
return nil, fmt.Errorf("error appending item to result: %w", err)
}
}

return res, nil
}, nil
}

func sortByExprExecutor2(opts *Options, e ast.SortByExpr) (expressionExecutor, error) {
return func(data *model.Value) (*model.Value, error) {
if !data.IsSlice() {
return nil, fmt.Errorf("cannot sort by on non-slice data")
}

sortedValues := model.NewSliceValue()
sortedIndexes := make([]int, 0)

if err := data.RangeSlice(func(i int, item *model.Value) error {
item, err := ExecuteAST(e.Expr, item, opts)
if err != nil {
return err
}
if err := sortedValues.Append(item); err != nil {
return fmt.Errorf("error appending item to result: %w", err)
}
sortedIndexes = append(sortedIndexes, i)
return nil
}); err != nil {
return nil, fmt.Errorf("error ranging over slice: %w", err)
}

l, err := sortedValues.Len()
if err != nil {
return nil, fmt.Errorf("error getting length of slice: %w", err)
}

for i := 0; i < l-1; i++ {
cur, err := sortedValues.GetSliceIndex(i)
if err != nil {
return nil, fmt.Errorf("error getting slice index: %w", err)
}
curIndex := sortedIndexes[i]
next, err := sortedValues.GetSliceIndex(i + 1)
if err != nil {
return nil, fmt.Errorf("error getting slice index: %w", err)
}
nextIndex := sortedIndexes[i+1]

cmp, err := cur.Compare(next)
if err != nil {
return nil, fmt.Errorf("error comparing values: %w", err)
}

if cmp == 0 {
continue
}

if !e.Descending {
if cmp > 0 {
if err := sortedValues.SetSliceIndex(i, next); err != nil {
return nil, fmt.Errorf("error setting slice index: %w", err)
}
sortedIndexes[i] = nextIndex
if err := sortedValues.SetSliceIndex(i+1, cur); err != nil {
return nil, fmt.Errorf("error setting slice index: %w", err)
}
sortedIndexes[i+1] = curIndex
i -= 1
}
} else {
if cmp < 0 {
if err := sortedValues.SetSliceIndex(i, next); err != nil {
return nil, fmt.Errorf("error setting slice index: %w", err)
}
sortedIndexes[i] = nextIndex
if err := sortedValues.SetSliceIndex(i+1, cur); err != nil {
return nil, fmt.Errorf("error setting slice index: %w", err)
}
sortedIndexes[i+1] = curIndex
i -= 1
}
}
}

res := model.NewSliceValue()

for _, i := range sortedIndexes {
item, err := data.GetSliceIndex(i)
if err != nil {
return nil, fmt.Errorf("error getting slice index: %w", err)
}
if err := res.Append(item); err != nil {
return nil, fmt.Errorf("error appending item to result: %w", err)
}
}

return res, nil
}, nil
}
181 changes: 181 additions & 0 deletions execution/execute_sort_by_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package execution_test

import (
"testing"

"github.com/tomwright/dasel/v3/model"
)

func TestFuncSortBy(t *testing.T) {
runSortTests := func(in func() *model.Value, outAsc func() *model.Value, outDesc func() *model.Value) func(*testing.T) {
return func(t *testing.T) {
t.Run("asc default", testCase{
inFn: in,
s: `sortBy($this)`,
outFn: outAsc,
}.run)
t.Run("asc", testCase{
inFn: in,
s: `sortBy($this, asc)`,
outFn: outAsc,
}.run)
t.Run("desc", testCase{
inFn: in,
s: `sortBy($this, desc)`,
outFn: outDesc,
}.run)
}
}

t.Run("int", runSortTests(
func() *model.Value {
res := model.NewSliceValue()
if err := res.Append(model.NewIntValue(2)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewIntValue(1)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewIntValue(4)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewIntValue(3)); err != nil {
t.Fatal(err)
}
return res
},
func() *model.Value {
res := model.NewSliceValue()
if err := res.Append(model.NewIntValue(1)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewIntValue(2)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewIntValue(3)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewIntValue(4)); err != nil {
t.Fatal(err)
}
return res
},
func() *model.Value {
res := model.NewSliceValue()
if err := res.Append(model.NewIntValue(4)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewIntValue(3)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewIntValue(2)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewIntValue(1)); err != nil {
t.Fatal(err)
}
return res
},
))

t.Run("float", runSortTests(
func() *model.Value {
res := model.NewSliceValue()
if err := res.Append(model.NewFloatValue(2.23)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewFloatValue(2)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewFloatValue(5.123)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewFloatValue(4.2)); err != nil {
t.Fatal(err)
}
return res
},
func() *model.Value {
res := model.NewSliceValue()
if err := res.Append(model.NewFloatValue(2)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewFloatValue(2.23)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewFloatValue(4.2)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewFloatValue(5.123)); err != nil {
t.Fatal(err)
}
return res
},
func() *model.Value {
res := model.NewSliceValue()
if err := res.Append(model.NewFloatValue(5.123)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewFloatValue(4.2)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewFloatValue(2.23)); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewFloatValue(2)); err != nil {
t.Fatal(err)
}
return res
},
))
t.Run("string", runSortTests(
func() *model.Value {
res := model.NewSliceValue()
if err := res.Append(model.NewStringValue("def")); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewStringValue("abc")); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewStringValue("cde")); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewStringValue("bcd")); err != nil {
t.Fatal(err)
}
return res
},
func() *model.Value {
res := model.NewSliceValue()
if err := res.Append(model.NewStringValue("abc")); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewStringValue("bcd")); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewStringValue("cde")); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewStringValue("def")); err != nil {
t.Fatal(err)
}
return res
},
func() *model.Value {
res := model.NewSliceValue()
if err := res.Append(model.NewStringValue("def")); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewStringValue("cde")); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewStringValue("bcd")); err != nil {
t.Fatal(err)
}
if err := res.Append(model.NewStringValue("abc")); err != nil {
t.Fatal(err)
}
return res
},
))
}
41 changes: 41 additions & 0 deletions model/value_comparison.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
package model

func (v *Value) Compare(other *Value) (int, error) {
eq, err := v.Equal(other)
if err != nil {
return 0, err
}
eqVal, err := eq.BoolValue()
if err != nil {
return 0, err
}
if eqVal {
return 0, nil
}

lt, err := v.LessThan(other)
if err != nil {
return 0, err
}
ltVal, err := lt.BoolValue()
if err != nil {
return 0, err
}
if ltVal {
return -1, nil
}

return 1, nil
}

func (v *Value) Equal(other *Value) (*Value, error) {
if v.IsInt() && other.IsFloat() {
a, err := v.IntValue()
Expand Down Expand Up @@ -92,6 +120,19 @@ func (v *Value) LessThan(other *Value) (*Value, error) {
}
return NewValue(a < float64(b)), nil
}

if v.IsString() && other.IsString() {
a, err := v.StringValue()
if err != nil {
return nil, err
}
b, err := other.StringValue()
if err != nil {
return nil, err
}
return NewValue(a < b), nil
}

return nil, &ErrIncompatibleTypes{A: v, B: other}
}

Expand Down
Loading

0 comments on commit cfa37ca

Please sign in to comment.