1diff --git a/pkg/config/config.go b/pkg/config/config.go
2index ba1614f6fa318316db0b44a319846bf4a275bba6..776c69888056005fa086fd14509354449665baa4 100644
3--- a/pkg/config/config.go
4+++ b/pkg/config/config.go
5@@ -5,6 +5,7 @@ "fmt"
6 "io"
7 "strconv"
8
9+ "git.gabrielgio.me/cerrado/pkg/u"
10 "git.sr.ht/~emersion/go-scfg"
11 )
12
13@@ -56,9 +57,9 @@ return setBool(public, &scan.Public)
14 }
15
16 func setBool(dir *scfg.Directive, field *bool) error {
17-
18 if dir != nil {
19- p1 := first(dir.Params)
20+
21+ p1, _ := u.First(dir.Params)
22 v, err := strconv.ParseBool(p1)
23 if err != nil {
24 return fmt.Errorf("Error parsing bool param of %s: %w", dir.Name, err)
25@@ -70,15 +71,7 @@ }
26
27 func setString(dir *scfg.Directive, field *string) error {
28 if dir != nil {
29- *field = first(dir.Params)
30+ *field, _ = u.First(dir.Params)
31 }
32 return nil
33 }
34-
35-func first[T any](v []T) T {
36- if len(v) == 0 {
37- var zero T
38- return zero
39- }
40- return v[0]
41-}
42diff --git a/pkg/u/util.go b/pkg/u/util.go
43new file mode 100644
44index 0000000000000000000000000000000000000000..34eafd11492cecb0eddfe8cc7ad4c4df8bf96511
45--- /dev/null
46+++ b/pkg/u/util.go
47@@ -0,0 +1,17 @@
48+package u
49+
50+func First[T any](v []T) (T, bool) {
51+ if len(v) == 0 {
52+ var zero T
53+ return zero, false
54+ }
55+ return v[0], true
56+}
57+
58+func ChunkBy[T any](items []T, chunkSize int) [][]T {
59+ var chunks = make([][]T, 0, (len(items)/chunkSize)+1)
60+ for chunkSize < len(items) {
61+ items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize])
62+ }
63+ return append(chunks, items)
64+}
65diff --git a/pkg/u/util_test.go b/pkg/u/util_test.go
66new file mode 100644
67index 0000000000000000000000000000000000000000..a6d84c7eaa6ceb401da5f22978f0f285ea1b7478
68--- /dev/null
69+++ b/pkg/u/util_test.go
70@@ -0,0 +1,96 @@
71+// go:build unit
72+
73+package u
74+
75+import (
76+ "testing"
77+
78+ "github.com/google/go-cmp/cmp"
79+)
80+
81+func TestFirst(t *testing.T) {
82+ testCases := []struct {
83+ name string
84+ slice []int
85+ first int
86+ exist bool
87+ }{
88+ {
89+ name: "multiple items slice",
90+ slice: []int{1, 2, 3},
91+ first: 1,
92+ exist: true,
93+ },
94+ {
95+ name: "single item slice",
96+ slice: []int{1},
97+ first: 1,
98+ exist: true,
99+ },
100+ {
101+ name: "empty slice",
102+ slice: []int{},
103+ first: 0,
104+ exist: false,
105+ },
106+ }
107+ for _, tc := range testCases {
108+ t.Run(tc.name, func(t *testing.T) {
109+
110+ first, empty := First(tc.slice)
111+
112+ if first != tc.first {
113+ t.Errorf("Error first, want %d got %d", tc.first, first)
114+ }
115+
116+ if empty != tc.exist {
117+ t.Errorf("Error empty, want %t got %t", tc.exist, empty)
118+ }
119+
120+ })
121+ }
122+}
123+
124+func TestSubList(t *testing.T) {
125+ testCases := []struct {
126+ name string
127+ slice []int
128+ size int
129+ want [][]int
130+ }{
131+ {
132+ name: "sigle size sub list",
133+ slice: []int{1, 2, 3},
134+ size: 1,
135+ want: [][]int{{1}, {2}, {3}},
136+ },
137+ {
138+ name: "multiple size sub list",
139+ slice: []int{1, 2, 3, 4},
140+ size: 2,
141+ want: [][]int{{1, 2}, {3, 4}},
142+ },
143+ {
144+ name: "uneven multiple size sub list",
145+ slice: []int{1, 2, 3, 4, 5},
146+ size: 2,
147+ want: [][]int{{1, 2}, {3, 4}, {5}},
148+ },
149+ {
150+ name: "empty sub list",
151+ slice: []int{},
152+ size: 2,
153+ want: [][]int{{}},
154+ },
155+ }
156+ for _, tc := range testCases {
157+ t.Run(tc.name, func(t *testing.T) {
158+
159+ subList := ChunkBy(tc.slice, tc.size)
160+
161+ if diff := cmp.Diff(tc.want, subList); diff != "" {
162+ t.Errorf("Wrong result given - wanted + got\n %s", diff)
163+ }
164+ })
165+ }
166+}