diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb3083b..1413a40 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: test: strategy: matrix: - go-version: [1.21.x, 1.22.x] + go-version: [1.23.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} diff --git a/bench_iteration_test.go b/bench_iteration_test.go index 71bfd59..1def1a2 100644 --- a/bench_iteration_test.go +++ b/bench_iteration_test.go @@ -25,6 +25,48 @@ func BenchmarkEach(b *testing.B) { } } +func BenchmarkEachIter(b *testing.B) { + var tmp, n int + + b.StopTimer() + sel := DocW().Find("td") + b.StartTimer() + for i := 0; i < b.N; i++ { + for range sel.EachIter() { + tmp++ + } + if n == 0 { + n = tmp + } + } + if n != 59 { + b.Fatalf("want 59, got %d", n) + } +} + +func BenchmarkEachIterWithBreak(b *testing.B) { + var tmp, n int + + b.StopTimer() + sel := DocW().Find("td") + b.StartTimer() + for i := 0; i < b.N; i++ { + tmp = 0 + for range sel.EachIter() { + tmp++ + if tmp >= 10 { + break + } + } + if n == 0 { + n = tmp + } + } + if n != 10 { + b.Fatalf("want 10, got %d", n) + } +} + func BenchmarkMap(b *testing.B) { var tmp, n int diff --git a/go.mod b/go.mod index 92b4e93..382b634 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,4 @@ require ( golang.org/x/net v0.29.0 ) -go 1.18 +go 1.23 diff --git a/iteration.go b/iteration.go index 1c98158..1ca5245 100644 --- a/iteration.go +++ b/iteration.go @@ -1,5 +1,7 @@ package goquery +import "iter" + // Each iterates over a Selection object, executing a function for each // matched element. It returns the current Selection object. The function // f is called for each element in the selection with the index of the @@ -12,6 +14,18 @@ func (s *Selection) Each(f func(int, *Selection)) *Selection { return s } +// EachIter returns an iterator that yields the Selection object in order. +// The implementation is similar to Each, but it returns an iterator instead. +func (s *Selection) EachIter() iter.Seq2[int, *Selection] { + return func(yield func(int, *Selection) bool) { + for i, n := range s.Nodes { + if !yield(i, newSingleSelection(n, s.document)) { + return + } + } + } +} + // EachWithBreak iterates over a Selection object, executing a function for each // matched element. It is identical to Each except that it is possible to break // out of the loop by returning false in the callback function. It returns the diff --git a/iteration_test.go b/iteration_test.go index dd6fefa..48cebb2 100644 --- a/iteration_test.go +++ b/iteration_test.go @@ -105,3 +105,39 @@ func TestGenericMap(t *testing.T) { t.Errorf("Expected Map array result to have a length of 3, found %v.", len(vals)) } } + +func TestEachIter(t *testing.T) { + var cnt int + + sel := Doc().Find(".hero-unit .row-fluid") + + for i, s := range sel.EachIter() { + cnt++ + t.Logf("At index %v, node %v", i, s.Nodes[0].Data) + } + + sel = sel.Find("a") + + if cnt != 4 { + t.Errorf("Expected Each() to call function 4 times, got %v times.", cnt) + } + assertLength(t, sel.Nodes, 6) +} + +func TestEachIterWithBreak(t *testing.T) { + var cnt int + + sel := Doc().Find(".hero-unit .row-fluid") + for i, s := range sel.EachIter() { + cnt++ + t.Logf("At index %v, node %v", i, s.Nodes[0].Data) + break + } + + sel = sel.Find("a") + + if cnt != 1 { + t.Errorf("Expected Each() to call function 1 time, got %v times.", cnt) + } + assertLength(t, sel.Nodes, 6) +}