Skip to content

Commit 5929092

Browse files
authored
Merge branch 'main' into ce-cancellation-poc
2 parents 0d237b4 + ca0d1b9 commit 5929092

File tree

11 files changed

+201
-33
lines changed

11 files changed

+201
-33
lines changed

.github/workflows/publish.yaml

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ on:
55
branches:
66
- main
77

8+
permissions:
9+
#contents: write # for peaceiris/actions-gh-pages
10+
id-token: write # for NuGet trusted publishing
11+
812
jobs:
913
publish:
1014
name: Publish nuget (if new version)
@@ -24,13 +28,11 @@ jobs:
2428
# very important, since we use cmd scripts, the default is psh, and a bug prevents errorlevel to bubble
2529
shell: cmd
2630
run: ./build.cmd
27-
- name: Nuget publish
28-
# skip-duplicate ensures that the 409 error received when the package was already published,
29-
# will just issue a warning and won't have the GH action fail.
30-
# NUGET_PUBLISH_TOKEN_TASKSEQ is valid until approx. 11 Dec 2024 and will need to be updated by then:
31-
# - log in to Nuget.org using 'abelbraaksma' admin account and then refresh the token in Nuget
32-
# - copy the token
33-
# - go to https://github.com/fsprojects/FSharp.Control.TaskSeq/settings/secrets/actions
34-
# - select button "Add repository secret" or update the existing one under "Repository secrets"
35-
# - rerun the job
36-
run: dotnet nuget push packages\FSharp.Control.TaskSeq.*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_PUBLISH_TOKEN_TASKSEQ }} --skip-duplicate
31+
- name: Obtain NuGet key
32+
# this hash is v1.1.0
33+
uses: NuGet/login@d22cc5f58ff5b88bf9bd452535b4335137e24544
34+
id: login
35+
with:
36+
user: dsyme
37+
- name: Publish NuGets (if this version not published before)
38+
run: dotnet nuget push packages\FSharp.Control.TaskSeq.*.nupkg -s https://www.nuget.org/api/v2/package -k ${{ steps.login.outputs.NUGET_API_KEY }} --skip-duplicate

AGENTS.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# AGENTS.md — FSharp.Control.TaskSeq
2+
3+
## Project Overview
4+
5+
FSharp.Control.TaskSeq is an F# library providing a `taskSeq` computation expression for `IAsyncEnumerable<'T>`, along with a comprehensive `TaskSeq` module of combinators. It targets `netstandard2.1`.
6+
7+
## Repository Layout
8+
9+
- `src/FSharp.Control.TaskSeq/` — Main library (netstandard2.1)
10+
- `src/FSharp.Control.TaskSeq.Test/` — xUnit test project (net6.0)
11+
- `src/FSharp.Control.TaskSeq.SmokeTests/` — Smoke/integration tests
12+
- `src/FSharp.Control.TaskSeq.sln` — Solution file
13+
- `Version.props` — Single source of truth for the package version
14+
- `build.cmd` — Windows build/test script used by CI
15+
16+
## Build
17+
18+
The solution uses the .NET SDK. Restore tools first, then build:
19+
20+
```bash
21+
dotnet tool restore
22+
dotnet build src/FSharp.Control.TaskSeq.sln -c Release
23+
```
24+
25+
Or use the provided script (Windows):
26+
27+
```cmd
28+
./build.cmd # default: release build
29+
./build.cmd debug # debug build
30+
```
31+
32+
`build.cmd` modes: `build` (default), `test`, `ci`. Configurations: `release` (default), `debug`.
33+
34+
## Test
35+
36+
Tests use **xUnit** with `FsUnit.xUnit` assertions. The test project is at `src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj`.
37+
38+
Run tests locally:
39+
40+
```bash
41+
dotnet test src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj -c Release
42+
```
43+
44+
Or via the build script:
45+
46+
```cmd
47+
./build.cmd test # runs tests without TRX logging
48+
./build.cmd test -debug # debug configuration
49+
./build.cmd ci # CI mode: adds --blame-hang-timeout 60000ms and TRX logging
50+
./build.cmd ci -release # CI mode, release config
51+
./build.cmd ci -debug # CI mode, debug config
52+
```
53+
54+
CI runs both debug and release test configurations on `windows-latest`. Test results are output as TRX files to `src/FSharp.Control.TaskSeq.Test/TestResults/`.
55+
56+
## Code Formatting
57+
58+
Formatting is enforced by **Fantomas** (version 6.3.0-alpha-004, configured as a dotnet local tool).
59+
60+
Check formatting (CI runs this on every PR):
61+
62+
```bash
63+
dotnet tool restore
64+
dotnet fantomas . --check
65+
```
66+
67+
Apply formatting:
68+
69+
```bash
70+
dotnet fantomas .
71+
```
72+
73+
Fantomas settings are in `src/.editorconfig` under the `[*.{fs,fsx}]` section. Key settings:
74+
75+
- `max_line_length = 140`
76+
- `indent_size = 4`
77+
- `fsharp_space_before_parameter = true`
78+
- `fsharp_space_before_lowercase_invocation = true`
79+
- `fsharp_max_if_then_else_short_width = 60`
80+
- `fsharp_max_record_width = 80`
81+
- `fsharp_max_array_or_list_width = 100`
82+
83+
## CI Workflows
84+
85+
All workflows are in `.github/workflows/`:
86+
87+
| Workflow | File | Trigger | Purpose |
88+
|---|---|---|---|
89+
| **ci-build** | `build.yaml` | Pull requests | Verify formatting (`dotnet fantomas . --check`) then build release on Windows |
90+
| **ci-test** | `test.yaml` | Pull requests | Run tests in both debug and release on Windows, upload TRX artifacts |
91+
| **ci-report** | `test-report.yaml` | After `ci-test` completes | Publish test results via `dorny/test-reporter` |
92+
| **Build main** | `main.yaml` | Push to `main` | Build + test release on Windows |
93+
| **Publish** | `publish.yaml` | Push to `main` | Build, then push NuGet package (skip-duplicate) |
94+
95+
## Conventions
96+
97+
- F# source files use `.fs` extension; signature files use `.fsi`.
98+
- `TreatWarningsAsErrors` is enabled for all projects.
99+
- File ordering matters in F# — the `<Compile>` order in `.fsproj` files defines compilation order.
100+
- The library targets `netstandard2.1`; tests target `net6.0` with `FSharp.Core` pinned to `6.0.1`.
101+
- NuGet packages are output to the `packages/` directory.

global.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"sdk": {
3+
"version": "10.0.103",
4+
"rollForward": "minor"
5+
}
6+
}

release-notes.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11

22
Release notes:
3+
4+
0.5.0
5+
- update engineering to .NET 9/10
6+
37
0.4.0
48
- overhaul all doc comments, add exceptions, improve IDE quick-info experience, #136, #220, #234
59
- new surface area functions, fixes #208:

src/FSharp.Control.TaskSeq.SmokeTests/FSharp.Control.TaskSeq.SmokeTests.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net9.0</TargetFramework>
55
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
66
</PropertyGroup>
77

src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net9.0</TargetFramework>
55
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
66
</PropertyGroup>
77

@@ -20,6 +20,7 @@
2020
<Compile Include="TaskSeq.Empty.Tests.fs" />
2121
<Compile Include="TaskSeq.ExactlyOne.Tests.fs" />
2222
<Compile Include="TaskSeq.Except.Tests.fs" />
23+
<Compile Include="TaskSeq.DistinctUntilChanged.Tests.fs" />
2324
<Compile Include="TaskSeq.Exists.Tests.fs" />
2425
<Compile Include="TaskSeq.Filter.Tests.fs" />
2526
<Compile Include="TaskSeq.FindIndex.Tests.fs" />

src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ module SideEffect =
249249
let mutable i = 0
250250

251251
taskSeq {
252-
yield ResizeArray { 1..10 }
253-
yield ResizeArray { 1..10 }
252+
yield ResizeArray [ 1..10 ]
253+
yield ResizeArray [ 1..10 ]
254254

255255
yield
256256
ResizeArray(
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module TaskSeq.Tests.DistinctUntilChanged
2+
3+
open Xunit
4+
open FsUnit.Xunit
5+
6+
open FSharp.Control
7+
8+
//
9+
// TaskSeq.distinctUntilChanged
10+
//
11+
12+
13+
module EmptySeq =
14+
[<Fact>]
15+
let ``TaskSeq-distinctUntilChanged with null source raises`` () = assertNullArg <| fun () -> TaskSeq.distinctUntilChanged null
16+
17+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
18+
let ``TaskSeq-distinctUntilChanged has no effect`` variant = task {
19+
do!
20+
Gen.getEmptyVariant variant
21+
|> TaskSeq.distinctUntilChanged
22+
|> TaskSeq.toListAsync
23+
|> Task.map (List.isEmpty >> should be True)
24+
}
25+
26+
module Functionality =
27+
[<Fact>]
28+
let ``TaskSeq-distinctUntilChanged should return no consecutive duplicates`` () = task {
29+
let ts =
30+
[ 'A'; 'A'; 'B'; 'Z'; 'C'; 'C'; 'Z'; 'C'; 'D'; 'D'; 'D'; 'Z' ]
31+
|> TaskSeq.ofList
32+
33+
let! xs = ts |> TaskSeq.distinctUntilChanged |> TaskSeq.toListAsync
34+
35+
xs
36+
|> List.map string
37+
|> String.concat ""
38+
|> should equal "ABZCZCDZ"
39+
}

src/FSharp.Control.TaskSeq/TaskSeq.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ type TaskSeq private () =
358358
static member except itemsToExclude source = Internal.except itemsToExclude source
359359
static member exceptOfSeq itemsToExclude source = Internal.exceptOfSeq itemsToExclude source
360360

361+
static member distinctUntilChanged source = Internal.distinctUntilChanged source
362+
361363
static member forall predicate source = Internal.forall (Predicate predicate) source
362364
static member forallAsync predicate source = Internal.forall (PredicateAsync predicate) source
363365

src/FSharp.Control.TaskSeq/TaskSeq.fsi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,16 @@ type TaskSeq =
12971297
/// <exception cref="T:ArgumentNullException">Thrown when either of the two input task sequences is null.</exception>
12981298
static member exceptOfSeq<'T when 'T: equality> : itemsToExclude: seq<'T> -> source: TaskSeq<'T> -> TaskSeq<'T>
12991299

1300+
/// <summary>
1301+
/// Returns a new task sequence without consecutive duplicate elements.
1302+
/// </summary>
1303+
///
1304+
/// <param name="source">The input task sequence whose consecutive duplicates will be removed.</param>
1305+
/// <returns>A sequence without consecutive duplicates elements.</returns>
1306+
///
1307+
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequences is null.</exception>
1308+
static member distinctUntilChanged<'T when 'T: equality> : source: TaskSeq<'T> -> TaskSeq<'T>
1309+
13001310
/// <summary>
13011311
/// Combines the two task sequences into a new task sequence of pairs. The two sequences need not have equal lengths:
13021312
/// when one sequence is exhausted any remaining elements in the other sequence are ignored.

0 commit comments

Comments
 (0)