Import of https://github.com/ciena/voltctl at commit 40d61fbf3f910ed4017cf67c9c79e8e1f82a33a5
Change-Id: I8464c59e60d76cb8612891db3303878975b5416c
diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go
new file mode 100644
index 0000000..5aa24ad
--- /dev/null
+++ b/pkg/filter/filter.go
@@ -0,0 +1,140 @@
+/*
+ * 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 filter
+
+import (
+ "fmt"
+ "reflect"
+ "regexp"
+ "strings"
+)
+
+type Operation int
+
+const (
+ UK Operation = iota
+ EQ
+ NE
+ GT
+ LT
+ GE
+ LE
+ RE
+)
+
+func toOp(op string) Operation {
+ switch op {
+ case "=":
+ return EQ
+ case "!=":
+ return NE
+ case ">":
+ return GT
+ case "<":
+ return LT
+ case ">=":
+ return GE
+ case "<=":
+ return LE
+ case "~":
+ return RE
+ default:
+ return UK
+ }
+}
+
+type FilterTerm struct {
+ Op Operation
+ Value string
+ re *regexp.Regexp
+}
+
+type Filter map[string]FilterTerm
+
+var termRE = regexp.MustCompile("^\\s*([a-zA-Z_][.a-zA-Z0-9_]*)\\s*(~|<=|>=|<|>|!=|=)\\s*(.+)\\s*$")
+
+// Parse parses a comma separated list of filter terms
+func Parse(spec string) (Filter, error) {
+ filter := make(map[string]FilterTerm)
+ terms := strings.Split(spec, ",")
+ var err error
+
+ // Each term is in the form <key><op><value>
+ for _, term := range terms {
+ parts := termRE.FindAllStringSubmatch(term, -1)
+ if parts == nil {
+ return nil, fmt.Errorf("Unable to parse filter term '%s'", term)
+ }
+ ft := FilterTerm{
+ Op: toOp(parts[0][2]),
+ Value: parts[0][3],
+ }
+ if ft.Op == RE {
+ ft.re, err = regexp.Compile(ft.Value)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to parse regexp filter value '%s'", ft.Value)
+ }
+ }
+ filter[parts[0][1]] = ft
+ }
+ return filter, nil
+}
+
+func (f Filter) Process(data interface{}) (interface{}, error) {
+ slice := reflect.ValueOf(data)
+ if slice.Kind() != reflect.Slice {
+ if f.Evaluate(data) {
+ return data, nil
+ }
+ return nil, nil
+ }
+
+ var result []interface{}
+
+ for i := 0; i < slice.Len(); i++ {
+ if f.Evaluate(slice.Index(i).Interface()) {
+ result = append(result, slice.Index(i).Interface())
+ }
+ }
+
+ return result, nil
+}
+
+func (f Filter) Evaluate(item interface{}) bool {
+ val := reflect.ValueOf(item)
+
+ for k, v := range f {
+ field := val.FieldByName(k)
+ if !field.IsValid() {
+ return false
+ }
+
+ switch v.Op {
+ case RE:
+ if !v.re.MatchString(fmt.Sprintf("%v", field)) {
+ return false
+ }
+ case EQ:
+ // This seems to work for most comparisons
+ if fmt.Sprintf("%v", field) != v.Value {
+ return false
+ }
+ default:
+ // For unsupported operations, always pass
+ }
+ }
+ return true
+}