Import of https://github.com/ciena/voltctl at commit 40d61fbf3f910ed4017cf67c9c79e8e1f82a33a5
Change-Id: I8464c59e60d76cb8612891db3303878975b5416c
diff --git a/pkg/order/order.go b/pkg/order/order.go
new file mode 100644
index 0000000..715b302
--- /dev/null
+++ b/pkg/order/order.go
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package order
+
+import (
+ "fmt"
+ "reflect"
+ "sort"
+ "strings"
+)
+
+type Operation int
+
+const (
+ ASC Operation = iota
+ DSC
+)
+
+type SortTerm struct {
+ Op Operation
+ Name string
+}
+
+func (o Operation) String() string {
+ switch o {
+ default:
+ fallthrough
+ case ASC:
+ return "ASC"
+ case DSC:
+ return "DSC"
+ }
+}
+
+type Sorter []SortTerm
+
+func split(term string) SortTerm {
+ st := SortTerm{}
+ if len(term) > 0 {
+ switch term[0] {
+ case '+':
+ fallthrough
+ case '>':
+ st.Op = ASC
+ st.Name = term[1:]
+ case '-':
+ fallthrough
+ case '<':
+ st.Op = DSC
+ st.Name = term[1:]
+ default:
+ st.Op = ASC
+ st.Name = term
+ }
+ } else {
+ st.Op = ASC
+ st.Name = term
+ }
+ return st
+}
+
+// Parse parses a comma separated list of filter terms
+func Parse(spec string) (Sorter, error) {
+ terms := strings.Split(spec, ",")
+ s := make([]SortTerm, 0)
+ for _, term := range terms {
+ s = append(s, split(term))
+ }
+
+ return s, nil
+}
+
+func (s Sorter) Process(data interface{}) (interface{}, error) {
+ slice := reflect.ValueOf(data)
+ if slice.Kind() != reflect.Slice {
+ return data, nil
+ }
+
+ sort.SliceStable(data, func(i, j int) bool {
+ left := reflect.ValueOf(slice.Index(i).Interface())
+ right := reflect.ValueOf(slice.Index(j).Interface())
+ for _, term := range s {
+ fleft := left.FieldByName(term.Name)
+ fright := right.FieldByName(term.Name)
+ switch fleft.Kind() {
+ case reflect.Uint:
+ fallthrough
+ case reflect.Uint8:
+ fallthrough
+ case reflect.Uint16:
+ fallthrough
+ case reflect.Uint32:
+ fallthrough
+ case reflect.Uint64:
+ ileft := fleft.Uint()
+ iright := fright.Uint()
+ switch term.Op {
+ case ASC:
+ if ileft < iright {
+ return true
+ } else if ileft > iright {
+ return false
+ }
+ case DSC:
+ if ileft > iright {
+ return true
+ } else if ileft < iright {
+ return false
+ }
+ }
+ case reflect.Int:
+ fallthrough
+ case reflect.Int8:
+ fallthrough
+ case reflect.Int16:
+ fallthrough
+ case reflect.Int32:
+ fallthrough
+ case reflect.Int64:
+ ileft := fleft.Int()
+ iright := fright.Int()
+ switch term.Op {
+ case ASC:
+ if ileft < iright {
+ return true
+ } else if ileft > iright {
+ return false
+ }
+ case DSC:
+ if ileft > iright {
+ return true
+ } else if ileft < iright {
+ return false
+ }
+ }
+ default:
+ sleft := fmt.Sprintf("%v", left.FieldByName(term.Name))
+ sright := fmt.Sprintf("%v", right.FieldByName(term.Name))
+ diff := strings.Compare(sleft, sright)
+ if term.Op != DSC {
+ if diff == -1 {
+ return true
+ } else if diff == 1 {
+ return false
+ }
+ } else {
+ if diff == 1 {
+ return true
+ } else if diff == -1 {
+ return false
+ }
+ }
+ }
+ }
+ return false
+ })
+
+ return data, nil
+}
diff --git a/pkg/order/order_test.go b/pkg/order/order_test.go
new file mode 100644
index 0000000..484d0a6
--- /dev/null
+++ b/pkg/order/order_test.go
@@ -0,0 +1,231 @@
+package order
+
+import (
+ "testing"
+)
+
+type SortTestStruct struct {
+ Id int
+ One string
+ Two string
+ Three uint
+ Four int
+}
+
+var testSetOne = []SortTestStruct{
+ {
+ Id: 0,
+ One: "a",
+ Two: "x",
+ Three: 10,
+ Four: 1,
+ },
+ {
+ Id: 1,
+ One: "a",
+ Two: "c",
+ Three: 1,
+ Four: 10,
+ },
+ {
+ Id: 2,
+ One: "a",
+ Two: "b",
+ Three: 2,
+ Four: 1000,
+ },
+ {
+ Id: 3,
+ One: "a",
+ Two: "a",
+ Three: 3,
+ Four: 100,
+ },
+ {
+ Id: 4,
+ One: "b",
+ Two: "a",
+ Three: 3,
+ Four: 0,
+ },
+}
+
+var testSetTwo = []SortTestStruct{
+ {
+ Id: 0,
+ One: "a",
+ Two: "x",
+ Three: 10,
+ Four: 10,
+ },
+ {
+ Id: 1,
+ One: "a",
+ Two: "y",
+ Three: 1,
+ Four: 1,
+ },
+}
+
+func Verify(v []SortTestStruct, order []int) bool {
+ for i, item := range v {
+ if item.Id != order[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func TestSort(t *testing.T) {
+ s, err := Parse("+One,-Two")
+ if err != nil {
+ t.Errorf("Unable to parse sort specification")
+ }
+ o, err := s.Process(testSetOne)
+ if err != nil {
+ t.Errorf("Sort failed: %s", err.Error())
+ }
+
+ if !Verify(o.([]SortTestStruct), []int{0, 1, 2, 3, 4}) {
+ t.Errorf("incorrect sort")
+ }
+}
+
+func TestSortASC(t *testing.T) {
+ s, err := Parse("+One,Two")
+ if err != nil {
+ t.Errorf("Unable to parse sort specification")
+ }
+ o, err := s.Process(testSetTwo)
+ if err != nil {
+ t.Errorf("Sort failed: %s", err.Error())
+ }
+
+ if !Verify(o.([]SortTestStruct), []int{0, 1}) {
+ t.Errorf("incorrect sort")
+ }
+}
+
+func TestSortUintASC(t *testing.T) {
+ s, err := Parse("Three,One")
+ if err != nil {
+ t.Errorf("Unable to parse sort specification")
+ }
+ o, err := s.Process(testSetOne)
+ if err != nil {
+ t.Errorf("Sort failed: %s", err.Error())
+ }
+
+ if !Verify(o.([]SortTestStruct), []int{1, 2, 3, 4, 0}) {
+ t.Errorf("incorrect sort")
+ }
+}
+
+func TestSortUintDSC(t *testing.T) {
+ s, err := Parse("-Three,One")
+ if err != nil {
+ t.Errorf("Unable to parse sort specification")
+ }
+ o, err := s.Process(testSetOne)
+ if err != nil {
+ t.Errorf("Sort failed: %s", err.Error())
+ }
+
+ if !Verify(o.([]SortTestStruct), []int{0, 3, 4, 2, 1}) {
+ t.Errorf("incorrect sort")
+ }
+}
+
+func TestSortUintDSC2(t *testing.T) {
+ s, err := Parse("-Three,One")
+ if err != nil {
+ t.Errorf("Unable to parse sort specification")
+ }
+ o, err := s.Process(testSetTwo)
+ if err != nil {
+ t.Errorf("Sort failed: %s", err.Error())
+ }
+
+ if !Verify(o.([]SortTestStruct), []int{0, 1}) {
+ t.Errorf("incorrect sort")
+ }
+}
+
+func TestSortIntASC(t *testing.T) {
+ s, err := Parse("Four,One")
+ if err != nil {
+ t.Errorf("Unable to parse sort specification")
+ }
+ o, err := s.Process(testSetOne)
+ if err != nil {
+ t.Errorf("Sort failed: %s", err.Error())
+ }
+ if !Verify(o.([]SortTestStruct), []int{4, 0, 1, 3, 2}) {
+ t.Errorf("incorrect sort")
+ }
+}
+
+func TestSortIntDSC(t *testing.T) {
+ s, err := Parse("-Four,One")
+ if err != nil {
+ t.Errorf("Unable to parse sort specification")
+ }
+ o, err := s.Process(testSetOne)
+ if err != nil {
+ t.Errorf("Sort failed: %s", err.Error())
+ }
+ if !Verify(o.([]SortTestStruct), []int{2, 3, 1, 0, 4}) {
+ t.Errorf("incorrect sort")
+ }
+}
+
+func TestSortIntDSC2(t *testing.T) {
+ s, err := Parse("-Four,One")
+ if err != nil {
+ t.Errorf("Unable to parse sort specification")
+ }
+ o, err := s.Process(testSetTwo)
+ if err != nil {
+ t.Errorf("Sort failed: %s", err.Error())
+ }
+ if !Verify(o.([]SortTestStruct), []int{0, 1}) {
+ t.Errorf("incorrect sort")
+ }
+}
+
+func TestOperString(t *testing.T) {
+ if ASC.String() != "ASC" {
+ t.Errorf("ASC to string failed")
+ }
+ if DSC.String() != "DSC" {
+ t.Errorf("DSC to string failed")
+ }
+ var o Operation = 5 // Invalid
+ if o.String() != "ASC" {
+ t.Errorf("to string default failed")
+ }
+}
+
+func TestSortSingle(t *testing.T) {
+ s, err := Parse("-Four,One")
+ if err != nil {
+ t.Errorf("Unable to parse sort specification")
+ }
+ o, err := s.Process(testSetOne[0])
+ if err != nil {
+ t.Errorf("Sort failed: %s", err.Error())
+ }
+
+ if o == nil {
+ t.Errorf("expected value, got nil")
+ }
+
+ r, ok := o.(SortTestStruct)
+ if !ok {
+ t.Errorf("Unexpected result type")
+ }
+
+ if r.Id != testSetOne[0].Id {
+ t.Errorf("results don't match input")
+ }
+}