aboutsummaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorJason Song <i@wolfogre.com>2023-01-11 13:31:16 +0800
committerGitHub <noreply@github.com>2023-01-11 13:31:16 +0800
commit477a1cc40ebd3ecb116c632b0717bba748e914d2 (patch)
treef8834d481acbf410d53828d21f5aa149c21a44bd /modules
parentdc5f2cf5906ec2f87ad47ea4724cc245c401eef6 (diff)
downloadgitea-477a1cc40ebd3ecb116c632b0717bba748e914d2.tar.gz
gitea-477a1cc40ebd3ecb116c632b0717bba748e914d2.zip
Improve utils of slices (#22379)
- Move the file `compare.go` and `slice.go` to `slice.go`. - Fix `ExistsInSlice`, it's buggy - It uses `sort.Search`, so it assumes that the input slice is sorted. - It passes `func(i int) bool { return slice[i] == target })` to `sort.Search`, that's incorrect, check the doc of `sort.Search`. - Conbine `IsInt64InSlice(int64, []int64)` and `ExistsInSlice(string, []string)` to `SliceContains[T]([]T, T)`. - Conbine `IsSliceInt64Eq([]int64, []int64)` and `IsEqualSlice([]string, []string)` to `SliceSortedEqual[T]([]T, T)`. - Add `SliceEqual[T]([]T, T)` as a distinction from `SliceSortedEqual[T]([]T, T)`. - Redesign `RemoveIDFromList([]int64, int64) ([]int64, bool)` to `SliceRemoveAll[T]([]T, T) []T`. - Add `SliceContainsFunc[T]([]T, func(T) bool)` and `SliceRemoveAllFunc[T]([]T, func(T) bool)` for general use. - Add comments to explain why not `golang.org/x/exp/slices`. - Add unit tests.
Diffstat (limited to 'modules')
-rw-r--r--modules/repository/init.go6
-rw-r--r--modules/util/compare.go92
-rw-r--r--modules/util/slice.go91
-rw-r--r--modules/util/slice_test.go88
4 files changed, 173 insertions, 104 deletions
diff --git a/modules/repository/init.go b/modules/repository/init.go
index 59284a5baf..2b0d0be7bc 100644
--- a/modules/repository/init.go
+++ b/modules/repository/init.go
@@ -170,7 +170,7 @@ func LoadRepoConfig() {
}
for _, f := range customFiles {
- if !util.IsStringInSlice(f, files, true) {
+ if !util.SliceContainsString(files, f, true) {
files = append(files, f)
}
}
@@ -200,12 +200,12 @@ func LoadRepoConfig() {
// Filter out invalid names and promote preferred licenses.
sortedLicenses := make([]string, 0, len(Licenses))
for _, name := range setting.Repository.PreferredLicenses {
- if util.IsStringInSlice(name, Licenses, true) {
+ if util.SliceContainsString(Licenses, name, true) {
sortedLicenses = append(sortedLicenses, name)
}
}
for _, name := range Licenses {
- if !util.IsStringInSlice(name, setting.Repository.PreferredLicenses, true) {
+ if !util.SliceContainsString(setting.Repository.PreferredLicenses, name, true) {
sortedLicenses = append(sortedLicenses, name)
}
}
diff --git a/modules/util/compare.go b/modules/util/compare.go
deleted file mode 100644
index 9ac778dfd3..0000000000
--- a/modules/util/compare.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2017 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package util
-
-import (
- "sort"
- "strings"
-)
-
-// Int64Slice attaches the methods of Interface to []int64, sorting in increasing order.
-type Int64Slice []int64
-
-func (p Int64Slice) Len() int { return len(p) }
-func (p Int64Slice) Less(i, j int) bool { return p[i] < p[j] }
-func (p Int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
-
-// IsSliceInt64Eq returns if the two slice has the same elements but different sequences.
-func IsSliceInt64Eq(a, b []int64) bool {
- if len(a) != len(b) {
- return false
- }
- sort.Sort(Int64Slice(a))
- sort.Sort(Int64Slice(b))
- for i := 0; i < len(a); i++ {
- if a[i] != b[i] {
- return false
- }
- }
- return true
-}
-
-// ExistsInSlice returns true if string exists in slice.
-func ExistsInSlice(target string, slice []string) bool {
- i := sort.Search(len(slice),
- func(i int) bool { return slice[i] == target })
- return i < len(slice)
-}
-
-// IsStringInSlice sequential searches if string exists in slice.
-func IsStringInSlice(target string, slice []string, insensitive ...bool) bool {
- caseInsensitive := false
- if len(insensitive) != 0 && insensitive[0] {
- caseInsensitive = true
- target = strings.ToLower(target)
- }
-
- for i := 0; i < len(slice); i++ {
- if caseInsensitive {
- if strings.ToLower(slice[i]) == target {
- return true
- }
- } else {
- if slice[i] == target {
- return true
- }
- }
- }
- return false
-}
-
-// IsInt64InSlice sequential searches if int64 exists in slice.
-func IsInt64InSlice(target int64, slice []int64) bool {
- for i := 0; i < len(slice); i++ {
- if slice[i] == target {
- return true
- }
- }
- return false
-}
-
-// IsEqualSlice returns true if slices are equal.
-func IsEqualSlice(target, source []string) bool {
- if len(target) != len(source) {
- return false
- }
-
- if (target == nil) != (source == nil) {
- return false
- }
-
- sort.Strings(target)
- sort.Strings(source)
-
- for i, v := range target {
- if v != source[i] {
- return false
- }
- }
-
- return true
-}
diff --git a/modules/util/slice.go b/modules/util/slice.go
index 17345cbc49..74356f5496 100644
--- a/modules/util/slice.go
+++ b/modules/util/slice.go
@@ -1,17 +1,90 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
+// Most of the functions in this file can have better implementations with "golang.org/x/exp/slices".
+// However, "golang.org/x/exp" is experimental and unreliable, we shouldn't use it.
+// So lets waiting for the "slices" has be promoted to the main repository one day.
+
package util
-// RemoveIDFromList removes the given ID from the slice, if found.
-// It does not preserve order, and assumes the ID is unique.
-func RemoveIDFromList(list []int64, id int64) ([]int64, bool) {
- n := len(list) - 1
- for i, item := range list {
- if item == id {
- list[i] = list[n]
- return list[:n], true
+import "strings"
+
+// SliceContains returns true if the target exists in the slice.
+func SliceContains[T comparable](slice []T, target T) bool {
+ return SliceContainsFunc(slice, func(t T) bool { return t == target })
+}
+
+// SliceContainsFunc returns true if any element in the slice satisfies the targetFunc.
+func SliceContainsFunc[T any](slice []T, targetFunc func(T) bool) bool {
+ for _, v := range slice {
+ if targetFunc(v) {
+ return true
+ }
+ }
+ return false
+}
+
+// SliceContainsString sequential searches if string exists in slice.
+func SliceContainsString(slice []string, target string, insensitive ...bool) bool {
+ if len(insensitive) != 0 && insensitive[0] {
+ target = strings.ToLower(target)
+ return SliceContainsFunc(slice, func(t string) bool { return strings.ToLower(t) == target })
+ }
+
+ return SliceContains(slice, target)
+}
+
+// SliceSortedEqual returns true if the two slices will be equal when they get sorted.
+// It doesn't require that the slices have been sorted, and it doesn't sort them either.
+func SliceSortedEqual[T comparable](s1, s2 []T) bool {
+ if len(s1) != len(s2) {
+ return false
+ }
+
+ counts := make(map[T]int, len(s1))
+ for _, v := range s1 {
+ counts[v]++
+ }
+ for _, v := range s2 {
+ counts[v]--
+ }
+
+ for _, v := range counts {
+ if v != 0 {
+ return false
+ }
+ }
+ return true
+}
+
+// SliceEqual returns true if the two slices are equal.
+func SliceEqual[T comparable](s1, s2 []T) bool {
+ if len(s1) != len(s2) {
+ return false
+ }
+
+ for i, v := range s1 {
+ if s2[i] != v {
+ return false
+ }
+ }
+ return true
+}
+
+// SliceRemoveAll removes all the target elements from the slice.
+func SliceRemoveAll[T comparable](slice []T, target T) []T {
+ return SliceRemoveAllFunc(slice, func(t T) bool { return t == target })
+}
+
+// SliceRemoveAllFunc removes all elements which satisfy the targetFunc from the slice.
+func SliceRemoveAllFunc[T comparable](slice []T, targetFunc func(T) bool) []T {
+ idx := 0
+ for _, v := range slice {
+ if targetFunc(v) {
+ continue
}
+ slice[idx] = v
+ idx++
}
- return list, false
+ return slice[:idx]
}
diff --git a/modules/util/slice_test.go b/modules/util/slice_test.go
new file mode 100644
index 0000000000..b0b771a79a
--- /dev/null
+++ b/modules/util/slice_test.go
@@ -0,0 +1,88 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package util
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSliceContains(t *testing.T) {
+ assert.True(t, SliceContains([]int{2, 0, 2, 3}, 2))
+ assert.True(t, SliceContains([]int{2, 0, 2, 3}, 0))
+ assert.True(t, SliceContains([]int{2, 0, 2, 3}, 3))
+
+ assert.True(t, SliceContains([]string{"2", "0", "2", "3"}, "0"))
+ assert.True(t, SliceContains([]float64{2, 0, 2, 3}, 0))
+ assert.True(t, SliceContains([]bool{false, true, false}, true))
+
+ assert.False(t, SliceContains([]int{2, 0, 2, 3}, 4))
+ assert.False(t, SliceContains([]int{}, 4))
+ assert.False(t, SliceContains(nil, 4))
+}
+
+func TestSliceContainsString(t *testing.T) {
+ assert.True(t, SliceContainsString([]string{"c", "b", "a", "b"}, "a"))
+ assert.True(t, SliceContainsString([]string{"c", "b", "a", "b"}, "b"))
+ assert.True(t, SliceContainsString([]string{"c", "b", "a", "b"}, "A", true))
+ assert.True(t, SliceContainsString([]string{"C", "B", "A", "B"}, "a", true))
+
+ assert.False(t, SliceContainsString([]string{"c", "b", "a", "b"}, "z"))
+ assert.False(t, SliceContainsString([]string{"c", "b", "a", "b"}, "A"))
+ assert.False(t, SliceContainsString([]string{}, "a"))
+ assert.False(t, SliceContainsString(nil, "a"))
+}
+
+func TestSliceSortedEqual(t *testing.T) {
+ assert.True(t, SliceSortedEqual([]int{2, 0, 2, 3}, []int{2, 0, 2, 3}))
+ assert.True(t, SliceSortedEqual([]int{3, 0, 2, 2}, []int{2, 0, 2, 3}))
+ assert.True(t, SliceSortedEqual([]int{}, []int{}))
+ assert.True(t, SliceSortedEqual([]int(nil), nil))
+ assert.True(t, SliceSortedEqual([]int(nil), []int{}))
+ assert.True(t, SliceSortedEqual([]int{}, []int{}))
+
+ assert.True(t, SliceSortedEqual([]string{"2", "0", "2", "3"}, []string{"2", "0", "2", "3"}))
+ assert.True(t, SliceSortedEqual([]float64{2, 0, 2, 3}, []float64{2, 0, 2, 3}))
+ assert.True(t, SliceSortedEqual([]bool{false, true, false}, []bool{false, true, false}))
+
+ assert.False(t, SliceSortedEqual([]int{2, 0, 2}, []int{2, 0, 2, 3}))
+ assert.False(t, SliceSortedEqual([]int{}, []int{2, 0, 2, 3}))
+ assert.False(t, SliceSortedEqual(nil, []int{2, 0, 2, 3}))
+ assert.False(t, SliceSortedEqual([]int{2, 0, 2, 4}, []int{2, 0, 2, 3}))
+ assert.False(t, SliceSortedEqual([]int{2, 0, 0, 3}, []int{2, 0, 2, 3}))
+}
+
+func TestSliceEqual(t *testing.T) {
+ assert.True(t, SliceEqual([]int{2, 0, 2, 3}, []int{2, 0, 2, 3}))
+ assert.True(t, SliceEqual([]int{}, []int{}))
+ assert.True(t, SliceEqual([]int(nil), nil))
+ assert.True(t, SliceEqual([]int(nil), []int{}))
+ assert.True(t, SliceEqual([]int{}, []int{}))
+
+ assert.True(t, SliceEqual([]string{"2", "0", "2", "3"}, []string{"2", "0", "2", "3"}))
+ assert.True(t, SliceEqual([]float64{2, 0, 2, 3}, []float64{2, 0, 2, 3}))
+ assert.True(t, SliceEqual([]bool{false, true, false}, []bool{false, true, false}))
+
+ assert.False(t, SliceEqual([]int{3, 0, 2, 2}, []int{2, 0, 2, 3}))
+ assert.False(t, SliceEqual([]int{2, 0, 2}, []int{2, 0, 2, 3}))
+ assert.False(t, SliceEqual([]int{}, []int{2, 0, 2, 3}))
+ assert.False(t, SliceEqual(nil, []int{2, 0, 2, 3}))
+ assert.False(t, SliceEqual([]int{2, 0, 2, 4}, []int{2, 0, 2, 3}))
+ assert.False(t, SliceEqual([]int{2, 0, 0, 3}, []int{2, 0, 2, 3}))
+}
+
+func TestSliceRemoveAll(t *testing.T) {
+ assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 0), []int{2, 2, 3})
+ assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 2), []int{0, 3})
+ assert.Equal(t, SliceRemoveAll([]int{0, 0, 0, 0}, 0), []int{})
+ assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 4), []int{2, 0, 2, 3})
+ assert.Equal(t, SliceRemoveAll([]int{}, 0), []int{})
+ assert.Equal(t, SliceRemoveAll([]int(nil), 0), []int(nil))
+ assert.Equal(t, SliceRemoveAll([]int{}, 0), []int{})
+
+ assert.Equal(t, SliceRemoveAll([]string{"2", "0", "2", "3"}, "0"), []string{"2", "2", "3"})
+ assert.Equal(t, SliceRemoveAll([]float64{2, 0, 2, 3}, 0), []float64{2, 2, 3})
+ assert.Equal(t, SliceRemoveAll([]bool{false, true, false}, true), []bool{false, false})
+}