mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Identify task (#1839)
* Add identify task * Change type naming * Debounce folder select text input * Add generic slice comparison function
This commit is contained in:
60
pkg/utils/collections.go
Normal file
60
pkg/utils/collections.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package utils
|
||||
|
||||
import "reflect"
|
||||
|
||||
// SliceSame returns true if the two provided lists have the same elements,
|
||||
// regardless of order. Panics if either parameter is not a slice.
|
||||
func SliceSame(a, b interface{}) bool {
|
||||
v1 := reflect.ValueOf(a)
|
||||
v2 := reflect.ValueOf(b)
|
||||
|
||||
if (v1.IsValid() && v1.Kind() != reflect.Slice) || (v2.IsValid() && v2.Kind() != reflect.Slice) {
|
||||
panic("not a slice")
|
||||
}
|
||||
|
||||
v1Len := 0
|
||||
v2Len := 0
|
||||
|
||||
v1Valid := v1.IsValid()
|
||||
v2Valid := v2.IsValid()
|
||||
|
||||
if v1Valid {
|
||||
v1Len = v1.Len()
|
||||
}
|
||||
if v2Valid {
|
||||
v2Len = v2.Len()
|
||||
}
|
||||
|
||||
if !v1Valid || !v2Valid {
|
||||
return v1Len == v2Len
|
||||
}
|
||||
|
||||
if v1Len != v2Len {
|
||||
return false
|
||||
}
|
||||
|
||||
if v1.Type() != v2.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
visited := make(map[int]bool)
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
found := false
|
||||
for j := 0; j < v2.Len(); j++ {
|
||||
if visited[j] {
|
||||
continue
|
||||
}
|
||||
if reflect.DeepEqual(v1.Index(i).Interface(), v2.Index(j).Interface()) {
|
||||
found = true
|
||||
visited[j] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
92
pkg/utils/collections_test.go
Normal file
92
pkg/utils/collections_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSliceSame(t *testing.T) {
|
||||
objs := []struct {
|
||||
a string
|
||||
b int
|
||||
}{
|
||||
{"1", 2},
|
||||
{"1", 2},
|
||||
{"2", 1},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
a interface{}
|
||||
b interface{}
|
||||
want bool
|
||||
}{
|
||||
{"nil values", nil, nil, true},
|
||||
{"empty", []int{}, []int{}, true},
|
||||
{"nil and empty", nil, []int{}, true},
|
||||
{
|
||||
"different type",
|
||||
[]string{"1"},
|
||||
[]int{1},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"different length",
|
||||
[]int{1, 2, 3},
|
||||
[]int{1, 2},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equal",
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"different order",
|
||||
[]int{5, 4, 3, 2, 1},
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"different",
|
||||
[]int{5, 4, 3, 2, 6},
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"same with duplicates",
|
||||
[]int{1, 1, 2, 3, 4},
|
||||
[]int{1, 2, 3, 4, 1},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"subset",
|
||||
[]int{1, 1, 2, 2, 3},
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"superset",
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
[]int{1, 1, 2, 2, 3},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"structs equal",
|
||||
objs[0:1],
|
||||
objs[0:1],
|
||||
true,
|
||||
},
|
||||
{
|
||||
"structs not equal",
|
||||
objs[0:2],
|
||||
objs[1:3],
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := SliceSame(tt.a, tt.b); got != tt.want {
|
||||
t.Errorf("SliceSame() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package utils
|
||||
|
||||
import "strconv"
|
||||
|
||||
// IntIndex returns the first index of the provided int value in the provided
|
||||
// int slice. It returns -1 if it is not found.
|
||||
func IntIndex(vs []int, t int) int {
|
||||
@@ -50,3 +52,13 @@ func IntExclude(vs []int, toExclude []int) []int {
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// IntSliceToStringSlice converts a slice of ints to a slice of strings.
|
||||
func IntSliceToStringSlice(ss []int) []string {
|
||||
ret := make([]string, len(ss))
|
||||
for i, v := range ss {
|
||||
ret[i] = strconv.Itoa(v)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
30
pkg/utils/reflect.go
Normal file
30
pkg/utils/reflect.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package utils
|
||||
|
||||
import "reflect"
|
||||
|
||||
// NotNilFields returns the matching tag values of fields from an object that are not nil.
|
||||
// Panics if the provided object is not a struct.
|
||||
func NotNilFields(subject interface{}, tag string) []string {
|
||||
value := reflect.ValueOf(subject)
|
||||
structType := value.Type()
|
||||
|
||||
if structType.Kind() != reflect.Struct {
|
||||
panic("subject must be struct")
|
||||
}
|
||||
|
||||
var ret []string
|
||||
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
field := value.Field(i)
|
||||
|
||||
kind := field.Type().Kind()
|
||||
if (kind == reflect.Ptr || kind == reflect.Slice) && !field.IsNil() {
|
||||
tagValue := structType.Field(i).Tag.Get(tag)
|
||||
if tagValue != "" {
|
||||
ret = append(ret, tagValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
83
pkg/utils/reflect_test.go
Normal file
83
pkg/utils/reflect_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNotNilFields(t *testing.T) {
|
||||
v := "value"
|
||||
var zeroStr string
|
||||
|
||||
type testObject struct {
|
||||
ptrField *string `tag:"ptrField"`
|
||||
noTagField *string
|
||||
otherTagField *string `otherTag:"otherTagField"`
|
||||
sliceField []string `tag:"sliceField"`
|
||||
}
|
||||
|
||||
type args struct {
|
||||
subject interface{}
|
||||
tag string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
"basic",
|
||||
args{
|
||||
testObject{
|
||||
ptrField: &v,
|
||||
noTagField: &v,
|
||||
otherTagField: &v,
|
||||
sliceField: []string{v},
|
||||
},
|
||||
"tag",
|
||||
},
|
||||
[]string{"ptrField", "sliceField"},
|
||||
},
|
||||
{
|
||||
"empty",
|
||||
args{
|
||||
testObject{},
|
||||
"tag",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"zero values",
|
||||
args{
|
||||
testObject{
|
||||
ptrField: &zeroStr,
|
||||
noTagField: &zeroStr,
|
||||
otherTagField: &zeroStr,
|
||||
sliceField: []string{},
|
||||
},
|
||||
"tag",
|
||||
},
|
||||
[]string{"ptrField", "sliceField"},
|
||||
},
|
||||
{
|
||||
"other tag",
|
||||
args{
|
||||
testObject{
|
||||
ptrField: &v,
|
||||
noTagField: &v,
|
||||
otherTagField: &v,
|
||||
sliceField: []string{v},
|
||||
},
|
||||
"otherTag",
|
||||
},
|
||||
[]string{"otherTagField"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NotNilFields(tt.args.subject, tt.args.tag); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NotNilFields() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user