Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ This will make your code free of `any` and type assertions.
```go
var results []string

From(sentences).
FromSlice(sentences).
// split sentences to words
SelectManyT(func(sentence string) Query {
return From(strings.Split(sentence, " "))
Expand Down Expand Up @@ -183,6 +183,7 @@ Available constructors:
- `FromSlice` - creates a query from a slice
- `FromMap` - creates a query from a map
- `FromChannel` - creates a query from a channel
- `FromChannelWithContext` - creates a query from a channel with `Context` support
- `FromString` - creates a query from a string (iterating over runes)
- `FromIterable` - creates a query from a custom collection implementing the `Iterable` interface

Expand All @@ -194,7 +195,8 @@ significantly less efficient. For all new code, it’s recommended to use the ex
```text
v4.0.0 (2025-10-12)
* Breaking change: Migrated to standard Go iterator pattern. (thanks @kalaninja!)
* Added typed constructors: FromSlice(), FromMap(), FromChannel(), FromString().
* Added typed constructors: FromSlice(), FromMap(), FromChannel(),
FromChannelWithContext(), FromString().
* Breaking change: Removed FromChannelT() in favor of FromChannel().

v3.2.0 (2020-12-29)
Expand Down
26 changes: 26 additions & 0 deletions from.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package linq

import (
"context"
"fmt"
"iter"
"reflect"
Expand Down Expand Up @@ -68,6 +69,31 @@ func FromChannel[T any](source <-chan T) Query {
}
}

// FromChannelWithContext initializes a linq query with a passed channel
// and stops iterating either when the channel is closed or when the context is canceled.
func FromChannelWithContext[T any](ctx context.Context, source <-chan T) Query {
return Query{
Iterate: func(yield func(any) bool) {
for {
select {
case <-ctx.Done():
// Context canceled or deadline exceeded
return
case item, ok := <-source:
if !ok {
// Channel closed
return
}
if !yield(item) {
// Consumer stopped early
return
}
}
}
},
}
}

// FromString initializes a query from a string, iterating over its runes.
func FromString[S ~string](source S) Query {
return Query{
Expand Down
39 changes: 38 additions & 1 deletion from_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package linq

import "testing"
import (
"context"
"testing"
"time"
)

func TestFromSlice(t *testing.T) {
s := [3]int{1, 2, 3}
Expand Down Expand Up @@ -34,6 +38,39 @@ func TestFromChannel(t *testing.T) {
}
}

func TestFromChannelWithContext_Cancel(t *testing.T) {
c := make(chan int, 3)
defer close(c)
c <- 10
c <- 15
c <- -3

w := []any{10, 15, -3}

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

if q := FromChannelWithContext(ctx, c); !validateQuery(q, w) {
t.Errorf("FromChannelWithContext() failed expected %v", w)
}
}

func TestFromChannelWithContext_Closed(t *testing.T) {
c := make(chan int, 3)
c <- 10
c <- 15
c <- -3
close(c)

w := []any{10, 15, -3}

ctx := context.Background()

if q := FromChannelWithContext(ctx, c); !validateQuery(q, w) {
t.Errorf("FromChannelWithContext() failed expected %v", w)
}
}

func TestFromString(t *testing.T) {
s := "string"
w := []any{'s', 't', 'r', 'i', 'n', 'g'}
Expand Down
Loading