diff --git a/file/file.go b/file/file.go index 8108ace..ae0015a 100644 --- a/file/file.go +++ b/file/file.go @@ -1,7 +1,9 @@ package file import ( + "fmt" "os" + "path/filepath" ) // The MSDN docs appear to say that a normal path that is 248 bytes long will work; @@ -42,3 +44,24 @@ func DeleteDir(path string) error { return os.Remove(path) } + +// ExpandFileList takes a list of file globs and expands them into a list +// of matching file paths. It returns the expanded file list and any errors +// from glob matching. This allows safely passing user input globs through to +// glob matching. +func ExpandFileList(fileList []string) ([]string, error) { + var result []string + + for _, glob := range fileList { + globbed, err := filepath.Glob(glob) + if err != nil { + return result, fmt.Errorf("failed to match %s: %w", glob, err) + } + + if globbed != nil { + result = append(result, globbed...) + } + } + + return result, nil +} diff --git a/slice/slice.go b/slice/slice.go new file mode 100644 index 0000000..38be57d --- /dev/null +++ b/slice/slice.go @@ -0,0 +1,44 @@ +package slice + +// SetDifference returns a slice containing the elements in slice a that are not in slice b. +func SetDifference[T comparable](a, b []T, unique bool) []T { + result := make([]T, 0) + + if unique { + a = Unique(a) + } + + for _, aItem := range a { + found := false + + for _, bItem := range b { + if aItem == bItem { + found = true + + break + } + } + + if !found { + result = append(result, aItem) + } + } + + return result +} + +// Unique returns a slice containing only the unique elements of the given slice. +func Unique[T comparable](s []T) []T { + seen := make(map[T]struct{}) + result := make([]T, 0) + + for _, v := range s { + if _, ok := seen[v]; !ok { + seen[v] = struct{}{} + + result = append(result, v) + } + } + + return result +} diff --git a/slice/slice_test.go b/slice/slice_test.go new file mode 100644 index 0000000..cada233 --- /dev/null +++ b/slice/slice_test.go @@ -0,0 +1,91 @@ +package slice + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSetDifference(t *testing.T) { + tests := []struct { + name string + a []string + b []string + expected []string + unique bool + }{ + { + name: "both empty", + a: []string{}, + b: []string{}, + expected: []string{}, + unique: false, + }, + { + name: "remove common element", + a: []string{"a", "b", "c"}, + b: []string{"b"}, + expected: []string{"a", "c"}, + unique: false, + }, + { + name: "remove a and c", + a: []string{"a", "b", "c"}, + b: []string{"a", "c"}, + expected: []string{"b"}, + unique: false, + }, + { + name: "no common elements", + a: []string{"a", "b", "c"}, + b: []string{"d", "e"}, + expected: []string{"a", "b", "c"}, + unique: false, + }, + { + name: "remove duplicates", + a: []string{"a", "a", "b"}, + b: []string{"b"}, + expected: []string{"a"}, + unique: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := SetDifference(tt.a, tt.b, tt.unique) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestUnique(t *testing.T) { + tests := []struct { + name string + input []int + want []int + }{ + { + name: "empty slice", + input: []int{}, + want: []int{}, + }, + { + name: "single value", + input: []int{1}, + want: []int{1}, + }, + { + name: "duplicates", + input: []int{1, 2, 3, 2}, + want: []int{1, 2, 3}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Unique(tt.input) + assert.Equal(t, tt.want, got) + }) + } +}