Repository: TomWright/dasel
Pull request #180 (2021-11-03 17:31:35)

Add type selector
Adds type selector: `[@]`.

Return values are `array`, `map`, `string`, `int`, `float`, `bool`.


Patch:
diff --git a/condition_equal.go b/condition_equal.go
index 7b0c4c3..b5119a7 100644
--- a/condition_equal.go
+++ b/condition_equal.go
@@ -36,21 +36,19 @@ func (c EqualCondition) Check(other reflect.Value) (bool, error) {
 		return c.check(value.Interface(), c.Value)
 	}
 
-	switch value.Kind() {
-	case reflect.Map, reflect.Slice:
-		subRootNode := New(value.Interface())
-		foundNode, err := subRootNode.Query(c.Key)
-		if err != nil {
-			var valueNotFound = &ValueNotFound{}
-			if errors.As(err, &valueNotFound) {
-				return false, nil
-			}
-
-			return false, fmt.Errorf("subquery failed: %w", err)
+	subRootNode := New(value.Interface())
+	foundNode, err := subRootNode.Query(c.Key)
+	if err != nil {
+		var valueNotFound = &ValueNotFound{}
+		if errors.As(err, &valueNotFound) {
+			return false, nil
+		}
+		var unsupportedType = &UnsupportedTypeForSelector{}
+		if errors.As(err, &unsupportedType) {
+			return false, nil
 		}
 
-		return c.check(foundNode.InterfaceValue(), c.Value)
+		return false, fmt.Errorf("subquery failed: %w", err)
 	}
-
-	return false, &UnhandledCheckType{Value: value.String()}
+	return c.check(foundNode.InterfaceValue(), c.Value)
 }
diff --git a/condition_more_than.go b/condition_more_than.go
index 22c2ba6..c35429d 100644
--- a/condition_more_than.go
+++ b/condition_more_than.go
@@ -31,37 +31,32 @@ func (c SortedComparisonCondition) Check(other reflect.Value) (bool, error) {
 		return fmt.Sprint(value.Interface()) == c.Value, nil
 	}
 
-	switch value.Kind() {
-	case reflect.Map, reflect.Slice:
-		subRootNode := New(value.Interface())
-		foundNode, err := subRootNode.Query(c.Key)
-		if err != nil {
-			var valueNotFound = &ValueNotFound{}
-			if errors.As(err, &valueNotFound) {
-				return false, nil
-			}
-
-			return false, fmt.Errorf("subquery failed: %w", err)
+	subRootNode := New(value.Interface())
+	foundNode, err := subRootNode.Query(c.Key)
+	if err != nil {
+		var valueNotFound = &ValueNotFound{}
+		if errors.As(err, &valueNotFound) {
+			return false, nil
 		}
 
-		foundValueStr := fmt.Sprint(foundNode.InterfaceValue())
+		return false, fmt.Errorf("subquery failed: %w", err)
+	}
 
-		// Check if the values are equal
-		if foundValueStr == c.Value {
-			return c.Equal, nil
-		}
+	foundValueStr := fmt.Sprint(foundNode.InterfaceValue())
 
-		sortedVals := []string{foundValueStr, c.Value}
-		sort.Strings(sortedVals)
+	// Check if the values are equal
+	if foundValueStr == c.Value {
+		return c.Equal, nil
+	}
 
-		if !c.After && sortedVals[1] == c.Value {
-			return true, nil
-		} else if c.After && sortedVals[0] == c.Value {
-			return true, nil
-		}
+	sortedVals := []string{foundValueStr, c.Value}
+	sort.Strings(sortedVals)
 
-		return false, nil
+	if !c.After && sortedVals[1] == c.Value {
+		return true, nil
+	} else if c.After && sortedVals[0] == c.Value {
+		return true, nil
 	}
 
-	return false, &UnhandledCheckType{Value: value.String()}
+	return false, nil
 }
diff --git a/node_query.go b/node_query.go
index 0b60e1b..94fbc11 100644
--- a/node_query.go
+++ b/node_query.go
@@ -218,6 +218,37 @@ func findValueLength(n *Node, createIfNotExists bool) (reflect.Value, error) {
 	return nilValue(), &UnsupportedTypeForSelector{Selector: n.Selector, Value: value}
 }
 
+// findValueType returns the type of the current node.
+func findValueType(n *Node, createIfNotExists bool) (reflect.Value, error) {
+	if !isValid(n.Previous.Value) {
+		return nilValue(), &UnexpectedPreviousNilValue{Selector: n.Previous.Selector.Current}
+	}
+
+	value := unwrapValue(n.Previous.Value)
+
+	switch value.Kind() {
+	case reflect.Slice:
+		return reflect.ValueOf("array"), nil
+
+	case reflect.Map:
+		return reflect.ValueOf("map"), nil
+
+	case reflect.String:
+		return reflect.ValueOf("string"), nil
+
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return reflect.ValueOf("int"), nil
+
+	case reflect.Float32, reflect.Float64:
+		return reflect.ValueOf("float"), nil
+
+	case reflect.Bool:
+		return reflect.ValueOf("bool"), nil
+	}
+
+	return nilValue(), &UnsupportedTypeForSelector{Selector: n.Selector, Value: value}
+}
+
 // findValue finds the value for the given node.
 // The value is essentially pulled from the previous node, using the (already parsed) selector
 // information stored on the current node.
@@ -242,6 +273,8 @@ func findValue(n *Node, createIfNotExists bool) (reflect.Value, error) {
 		return findValueDynamic(n, createIfNotExists)
 	case "LENGTH":
 		return findValueLength(n, createIfNotExists)
+	case "TYPE":
+		return findValueType(n, createIfNotExists)
 	default:
 		return nilValue(), &UnsupportedSelector{Selector: n.Selector.Raw}
 	}
diff --git a/node_query_multiple.go b/node_query_multiple.go
index 02b8d78..da2d7a8 100644
--- a/node_query_multiple.go
+++ b/node_query_multiple.go
@@ -459,6 +459,61 @@ func findNodesLength(selector Selector, previousValue reflect.Value) ([]*Node, e
 	return nil, &UnsupportedTypeForSelector{Selector: selector, Value: value}
 }
 
+// findNodesType returns the length
+func findNodesType(selector Selector, previousValue reflect.Value) ([]*Node, error) {
+	if !isValid(previousValue) {
+		return nil, &UnexpectedPreviousNilValue{Selector: selector.Raw}
+	}
+
+	value := unwrapValue(previousValue)
+
+	switch value.Kind() {
+	case reflect.Slice:
+		node := &Node{
+			Value:    reflect.ValueOf("array"),
+			Selector: selector,
+		}
+		return []*Node{node}, nil
+
+	case reflect.Map:
+		node := &Node{
+			Value:    reflect.ValueOf("map"),
+			Selector: selector,
+		}
+		return []*Node{node}, nil
+
+	case reflect.String:
+		node := &Node{
+			Value:    reflect.ValueOf("string"),
+			Selector: selector,
+		}
+		return []*Node{node}, nil
+
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		node := &Node{
+			Value:    reflect.ValueOf("int"),
+			Selector: selector,
+		}
+		return []*Node{node}, nil
+
+	case reflect.Float32, reflect.Float64:
+		node := &Node{
+			Value:    reflect.ValueOf("float"),
+			Selector: selector,
+		}
+		return []*Node{node}, nil
+
+	case reflect.Bool:
+		node := &Node{
+			Value:    reflect.ValueOf("bool"),
+			Selector: selector,
+		}
+		return []*Node{node}, nil
+	}
+
+	return nil, &UnsupportedTypeForSelector{Selector: selector, Value: value}
+}
+
 func initialiseEmptyValue(selector Selector, previousValue reflect.Value) reflect.Value {
 	switch selector.Type {
 	case "PROPERTY":
@@ -489,6 +544,8 @@ func findNodes(selector Selector, previousNode *Node, createIfNotExists bool) ([
 		res, err = findNodesAnyIndex(selector, previousNode.Value)
 	case "LENGTH":
 		res, err = findNodesLength(selector, previousNode.Value)
+	case "TYPE":
+		res, err = findNodesType(selector, previousNode.Value)
 	case "DYNAMIC":
 		res, err = findNodesDynamic(selector, previousNode.Value, createIfNotExists)
 	case "SEARCH":
diff --git a/parse_selector.go b/parse_selector.go
index 7196031..2e3d032 100644
--- a/parse_selector.go
+++ b/parse_selector.go
@@ -37,6 +37,8 @@ func ParseSelector(selector string) (Selector, error) {
 		sel, err = processParseSelectorIndexAny(nextSel, sel)
 	case nextSel == "[#]":
 		sel, err = processParseSelectorLength(nextSel, sel)
+	case nextSel == "[@]":
+		sel, err = processParseSelectorType(nextSel, sel)
 	case strings.HasPrefix(nextSel, "[") && strings.HasSuffix(nextSel, "]"):
 		sel, err = processParseSelectorIndex(nextSel, sel)
 	default:
@@ -168,6 +170,11 @@ func processParseSelectorLength(selector string, sel Selector) (Selector, error)
 	return sel, nil
 }
 
+func processParseSelectorType(selector string, sel Selector) (Selector, error) {
+	sel.Type = "TYPE"
+	return sel, nil
+}
+
 func processParseSelectorIndex(selector string, sel Selector) (Selector, error) {
 	sel.Type = "INDEX"
 	indexStr := selector[1 : len(selector)-1]


Test patch:
diff --git a/condition_test.go b/condition_test.go
index eebcbf0..99cf2fc 100644
--- a/condition_test.go
+++ b/condition_test.go
@@ -67,11 +67,6 @@ func TestEqualCondition_Check(t *testing.T) {
 		nil,
 		false, &dasel.UnhandledCheckType{Value: nil},
 	))
-	t.Run("String", conditionTest(
-		c,
-		"",
-		false, &dasel.UnhandledCheckType{Value: ""},
-	))
 }
 
 func TestSortedComparisonCondition_Check(t *testing.T) {
@@ -163,11 +158,6 @@ func TestSortedComparisonCondition_Check(t *testing.T) {
 		nil,
 		false, &dasel.UnhandledCheckType{Value: nil},
 	))
-	t.Run("String", conditionTest(
-		&dasel.SortedComparisonCondition{Key: "x", Value: "4"},
-		"",
-		false, &dasel.UnhandledCheckType{Value: ""},
-	))
 }
 
 func TestKeyEqualCondition_Check(t *testing.T) {
diff --git a/internal/command/root_select_test.go b/internal/command/root_select_test.go
index 2b7f769..a37e910 100644
--- a/internal/command/root_select_test.go
+++ b/internal/command/root_select_test.go
@@ -493,6 +493,44 @@ func TestRootCmd_Select_JSON(t *testing.T) {
 }
 `, nil, "--escape-html=false"))
 
+	t.Run("MixedDynamicSelectors", selectTest(`{
+  "plugins": [
+    "@semantic-release/commit-analyzer",
+    "@semantic-release/release-notes-generator",
+    "@semantic-release/gitlab",
+    [
+      "@semantic-release/git",
+      {
+        "assets": [
+          "tbump.toml",
+          "**/pyproject.toml",
+          "**/setup.py",
+          "README.md"
+        ],
+        "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
+      }
+    ],
+	[
+      "@semantic-release/git",
+      {
+        "assets": [
+          "y"
+        ],
+        "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
+      }
+    ]
+  ]
+}`, "json", `.plugins.([@]=array).([@]=map).assets`, `[
+  "tbump.toml",
+  "**/pyproject.toml",
+  "**/setup.py",
+  "README.md"
+]
+[
+  "y"
+]
+`, nil, "-m"))
+
 }
 
 func TestRootCmd_Select_YAML(t *testing.T) {
diff --git a/node_query_internal_test.go b/node_query_internal_test.go
index 2542a79..aaf9403 100644
--- a/node_query_internal_test.go
+++ b/node_query_internal_test.go
@@ -145,32 +145,6 @@ func TestFindValueDynamic(t *testing.T) {
 			return
 		}
 	})
-	t.Run("UnsupportedCheckType", func(t *testing.T) {
-		itemVal := 1
-		val := []interface{}{
-			itemVal,
-		}
-		n := getNodeWithValue(val)
-		n.Selector.Current = "(name=x)"
-		n.Selector.Conditions = []Condition{
-			&EqualCondition{Key: "name", Value: "x"},
-		}
-		got, err := findValueDynamic(n, false)
-		assertQueryResult(t, nilValue(), &UnhandledCheckType{Value: reflect.TypeOf(itemVal).Kind().String()}, got, err)
-	})
-	t.Run("UnsupportedCheckTypeMap", func(t *testing.T) {
-		itemVal := 1
-		val := map[string]interface{}{
-			"x": itemVal,
-		}
-		n := getNodeWithValue(val)
-		n.Selector.Current = "(name=x)"
-		n.Selector.Conditions = []Condition{
-			&EqualCondition{Key: "name", Value: "x"},
-		}
-		got, err := findValueDynamic(n, false)
-		assertQueryResult(t, nilValue(), &UnhandledCheckType{Value: reflect.TypeOf(itemVal).Kind().String()}, got, err)
-	})
 	t.Run("UnsupportedType", func(t *testing.T) {
 		val := 0
 		n := getNodeWithValue(val)
@@ -227,6 +201,57 @@ func TestFindValueLength(t *testing.T) {
 	})
 }
 
+func TestFindValueType(t *testing.T) {
+	t.Run("NilValue", func(t *testing.T) {
+		n := getNodeWithValue(nil)
+		n.Previous.Selector.Current = ".[#]"
+		got, err := findValueType(n, false)
+		assertQueryResult(t, nilValue(), &UnexpectedPreviousNilValue{Selector: ".[#]"}, got, err)
+	})
+	t.Run("Int", func(t *testing.T) {
+		val := 0
+		n := getNodeWithValue(val)
+		n.Selector.Current = ".[#]"
+		got, err := findValueType(n, false)
+		assertQueryResult(t, reflect.ValueOf("int"), nil, got, err)
+	})
+	t.Run("Float", func(t *testing.T) {
+		val := 1.1
+		n := getNodeWithValue(val)
+		n.Selector.Current = ".[#]"
+		got, err := findValueType(n, false)
+		assertQueryResult(t, reflect.ValueOf("float"), nil, got, err)
+	})
+	t.Run("Bool", func(t *testing.T) {
+		val := true
+		n := getNodeWithValue(val)
+		n.Selector.Current = ".[#]"
+		got, err := findValueType(n, false)
+		assertQueryResult(t, reflect.ValueOf("bool"), nil, got, err)
+	})
+	t.Run("String", func(t *testing.T) {
+		val := "x"
+		n := getNodeWithValue(val)
+		n.Selector.Current = ".[#]"
+		got, err := findValueType(n, false)
+		assertQueryResult(t, reflect.ValueOf("string"), nil, got, err)
+	})
+	t.Run("Map", func(t *testing.T) {
+		val := map[string]interface{}{"x": 1}
+		n := getNodeWithValue(val)
+		n.Selector.Current = ".[#]"
+		got, err := findValueType(n, false)
+		assertQueryResult(t, reflect.ValueOf("map"), nil, got, err)
+	})
+	t.Run("Array", func(t *testing.T) {
+		val := []interface{}{1}
+		n := getNodeWithValue(val)
+		n.Selector.Current = ".[#]"
+		got, err := findValueType(n, false)
+		assertQueryResult(t, reflect.ValueOf("array"), nil, got, err)
+	})
+}
+
 func TestFindValue(t *testing.T) {
 	t.Run("MissingPreviousNode", func(t *testing.T) {
 		n := New(nil)
diff --git a/node_query_multiple_internal_test.go b/node_query_multiple_internal_test.go
index fa52774..b937108 100644
--- a/node_query_multiple_internal_test.go
+++ b/node_query_multiple_internal_test.go
@@ -131,6 +131,50 @@ func TestFindNodesLength(t *testing.T) {
 	})
 }
 
+func TestFindNodesType(t *testing.T) {
+	t.Run("NilValue", func(t *testing.T) {
+		selector := Selector{Current: ".[#]", Raw: ".[#]"}
+		got, err := findNodesType(selector, nilValue())
+		assertQueryMultipleResult(t, []reflect.Value{}, &UnexpectedPreviousNilValue{Selector: ".[#]"}, got, err)
+	})
+	t.Run("Int", func(t *testing.T) {
+		selector := Selector{Current: ".[#]", Raw: ".[#]"}
+		val := 0
+		got, err := findNodesType(selector, reflect.ValueOf(val))
+		assertQueryMultipleResult(t, []reflect.Value{reflect.ValueOf("int")}, nil, got, err)
+	})
+	t.Run("Float", func(t *testing.T) {
+		selector := Selector{Current: ".[#]", Raw: ".[#]"}
+		val := 1.1
+		got, err := findNodesType(selector, reflect.ValueOf(val))
+		assertQueryMultipleResult(t, []reflect.Value{reflect.ValueOf("float")}, nil, got, err)
+	})
+	t.Run("Bool", func(t *testing.T) {
+		selector := Selector{Current: ".[#]", Raw: ".[#]"}
+		val := true
+		got, err := findNodesType(selector, reflect.ValueOf(val))
+		assertQueryMultipleResult(t, []reflect.Value{reflect.ValueOf("bool")}, nil, got, err)
+	})
+	t.Run("String", func(t *testing.T) {
+		selector := Selector{Current: ".[#]", Raw: ".[#]"}
+		val := "a"
+		got, err := findNodesType(selector, reflect.ValueOf(val))
+		assertQueryMultipleResult(t, []reflect.Value{reflect.ValueOf("string")}, nil, got, err)
+	})
+	t.Run("Map", func(t *testing.T) {
+		selector := Selector{Current: ".[#]", Raw: ".[#]"}
+		val := map[string]interface{}{"x": 1}
+		got, err := findNodesType(selector, reflect.ValueOf(val))
+		assertQueryMultipleResult(t, []reflect.Value{reflect.ValueOf("map")}, nil, got, err)
+	})
+	t.Run("Array", func(t *testing.T) {
+		selector := Selector{Current: ".[#]", Raw: ".[#]"}
+		val := []interface{}{"x"}
+		got, err := findNodesType(selector, reflect.ValueOf(val))
+		assertQueryMultipleResult(t, []reflect.Value{reflect.ValueOf("array")}, nil, got, err)
+	})
+}
+
 func TestFindNodesPropertyKeys(t *testing.T) {
 	t.Run("NilValue", func(t *testing.T) {
 		selector := Selector{Current: ".", Raw: "."}
@@ -279,32 +323,6 @@ func TestFindNodesDynamic(t *testing.T) {
 			return
 		}
 	})
-	t.Run("UnsupportedCheckType", func(t *testing.T) {
-		previousValue := reflect.ValueOf([]interface{}{
-			1,
-		})
-		selector := Selector{
-			Current: "(name=x)",
-			Conditions: []Condition{
-				&EqualCondition{Key: "name", Value: "x"},
-			},
-		}
-		got, err := findNodesDynamic(selector, previousValue, false)
-		assertQueryMultipleResult(t, []reflect.Value{}, &UnhandledCheckType{Value: previousValue.Kind().String()}, got, err)
-	})
-	t.Run("UnsupportedCheckTypeMap", func(t *testing.T) {
-		previousValue := reflect.ValueOf(map[string]interface{}{
-			"x": 1,
-		})
-		selector := Selector{
-			Current: "(name=x)",
-			Conditions: []Condition{
-				&EqualCondition{Key: "name", Value: "x"},
-			},
-		}
-		got, err := findNodesDynamic(selector, previousValue, false)
-		assertQueryMultipleResult(t, []reflect.Value{}, &UnhandledCheckType{Value: previousValue.Kind().String()}, got, err)
-	})
 	t.Run("UnsupportedType", func(t *testing.T) {
 		previousValue := reflect.ValueOf(0)
 		selector := Selector{