diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..3c0225f --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,34 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-reportgenerator-globaltool": { + "version": "5.4.7", + "commands": [ + "reportgenerator" + ], + "rollForward": false + }, + "fsharp-analyzers": { + "version": "0.31.0", + "commands": [ + "fsharp-analyzers" + ], + "rollForward": false + }, + "fantomas": { + "version": "7.0.2", + "commands": [ + "fantomas" + ], + "rollForward": false + }, + "fsdocs-tool": { + "version": "20.0.1", + "commands": [ + "fsdocs" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..96c07a3 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,17 @@ +# [Choice] Debian version (use bullseye on local arm64/Apple Silicon): bookworm, bullseye, buster +ARG VARIANT="bookworm" +FROM buildpack-deps:${VARIANT}-curl + + +ENV \ + # Enable detection of running in a container + DOTNET_RUNNING_IN_CONTAINER=true \ + DOTNET_ROOT=/usr/share/dotnet/ \ + DOTNET_NOLOGO=true \ + DOTNET_CLI_TELEMETRY_OPTOUT=false\ + DOTNET_USE_POLLING_FILE_WATCHER=true + + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..c7955dd --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,67 @@ +{ + "name": "dotnet", + // Set the build context one level higher so we can grab metadata like global.json + "context": "..", + "dockerFile": "Dockerfile", + "forwardPorts": [ + 0 + ], + "features": { + // https://github.com/devcontainers/features/blob/main/src/common-utils/README.md + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": true, + "installOhMyZshConfig": true, + "configureZshAsDefaultShell": true, + "username": "vscode", + "userUid": "1000", + "userGid": "1000", + "upgradePackages": true + }, + // https://github.com/devcontainers/features/blob/main/src/github-cli/README.md + "ghcr.io/devcontainers/features/github-cli:1": {}, + // https://github.com/devcontainers/features/blob/main/src/dotnet/README.md + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "9.0", + "additionalVersions": "8.0" + } + }, + "overrideFeatureInstallOrder": [ + "ghcr.io/devcontainers/features/common-utils", + "ghcr.io/devcontainers/features/github-cli", + "ghcr.io/devcontainers/features/dotnet" + ], + "customizations": { + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-dotnettools.csharp", + "Ionide.Ionide-fsharp", + "tintoy.msbuild-project-tools", + "ionide.ionide-paket", + "usernamehw.errorlens", + "alefragnani.Bookmarks", + "oderwat.indent-rainbow", + "vscode-icons-team.vscode-icons", + "EditorConfig.EditorConfig", + "ms-azuretools.vscode-docker", + "GitHub.vscode-pull-request-github", + "github.vscode-github-actions" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "zsh", + "csharp.suppressDotnetInstallWarning": true + } + } + }, + "remoteUser": "vscode", + "containerUser": "vscode", + "containerEnv": { + // Expose the local environment variable to the container + // They are used for releasing and publishing from the container + "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" + }, + "postAttachCommand": { + "restore": "dotnet tool restore && dotnet restore" + }, + "waitFor": "updateContentCommand" +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..869005a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,296 @@ +# EditorConfig is awesome: +http://EditorConfig.org + +# top-most EditorConfig file +root = true + +############################### +# Core EditorConfig Options # +############################### +# All files +[*] # Do not apply to all files not to break something +guidelines = 120 dashed, 130 +# Either crlf | lf, default is system-dependent (when not specified at all) +# end_of_line=crlf +# Remove whitespace at the end of any line + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# Code files +[*.{cs,csx,fs,fsi,fsx}] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space # default=space +indent_size = 4 # default=4 +charset = utf-8 + +# Project files and app specific XML files +[*.{csproj,fsproj,shproj,sfproj,projitems,props,xaml,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +# XML configuration files +[{app.config,nuget.config,packages.config,web.config}] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +# XML files +[*.xml] +trim_trailing_whitespace = false # do not trim as it affects CData +insert_final_newline = true +indent_style = space +indent_size = 2 + +# JSON and YAML files +[*.{json,yml,yaml}] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +# Proto files +[*.proto] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +# Markdown Files +[*.{md,mdx}] +trim_trailing_whitespace = false + +# Bash Files +[*.{sh}] +end_of_line = lf + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Powershell Files +[*.{ps1, psm1}] +end_of_line = crlf + +# Paket files +[paket.*] +trim_trailing_whitespace = true +indent_size = 2 + +[*.paket.references] +trim_trailing_whitespace = true +indent_size = 2 + +############################### +# F# Coding Conventions # +############################### +# https://fsprojects.github.io/fantomas/docs/end-users/Configuration.html + +# filetypes that need to be formatted by Fantomas: +[*.{fs,fsi,fsx}] + +# files to be ignored for Fantomas may go into this file, if present: +# .fantomasignore + +# indentation size, default=4 +indent_size=4 + +# line length before it gets broken down into multiple lines +# default 120 +max_line_length=130 + +# Either crlf | lf, default is system-dependent (when not specified at all) +# end_of_line=crlf + +# Whether end-of-file has a newline, default=true +insert_final_newline=true + +# false: someLineOfCode +# true: someLineOfCode; +# default false +fsharp_semicolon_at_end_of_line=false + +# false: f(1,2) +# true: f(1, 2) +# default true +fsharp_space_before_parameter=true + +# false: Option.map(fun x -> x) +# true: Option.map (fun x -> x) +# default true +fsharp_space_before_lowercase_invocation=true + +# false: x.ToString() +# true: x.ToString () +# default false +fsharp_space_before_uppercase_invocation=true + +# false: new Ship(withBeans) +# true: new Ship (withBeans) +# default false +fsharp_space_before_class_constructor=true + +# false: __.member Foo(x) = x +# true: __.member Foo (x) = x +# default false +fsharp_space_before_member=true + +# false: type Point = { x: int; y: int } +# true: type Point = { x : int; y : int } +# default false +fsharp_space_before_colon=true + +# false: (a,b,c) +# true: (a, b, c) +# default true +fsharp_space_after_comma=true + +# false: [a; b; 42] +# true: [a ; b ; 42] +# default false +fsharp_space_before_semicolon=false + +# false: [a;b;42] +# true: [a; b; 42] +# default true +fsharp_space_after_semicolon=true + +# false: let a = [1;2;3] +# true: let a = [ 1;2;3 ] +# default true +fsharp_space_around_delimiter=true + +# breaks an if-then-else in smaller parts if it is on one line +# default 40 +fsharp_max_if_then_else_short_width=60 + +# breaks an infix operator expression if it is on one line +# infix: a + b + c +# default 50 +fsharp_max_infix_operator_expression=60 + +# breaks a single-line record declaration +# i.e. if this gets too wide: { X = 10; Y = 12 } +# default 40 +fsharp_max_record_width=80 + +# breaks a record into one item per line if items exceed this number +# i.e. if set to 1, this will be on three lines: { X = 10; Y = 12 } +# requires fsharp_record_multiline_formatter=number_of_items to take effect +# default 1 +fsharp_max_record_number_of_items=1 + +# whether to use line-length (by counting chars) or items (by counting fields) +# for the record settings above +# either number_of_items or character_width +# default character_width +fsharp_record_multiline_formatter=character_width + +# breaks a single line array or list if it exceeds this size +# default 40 +fsharp_max_array_or_list_width=100 + +# breaks an array or list into one item per line if items exceeds this number +# i.e. if set to 1, this will be shown on three lines [1; 2; 3] +# requires fsharp_array_or_list_multiline_formatter=number_of_items to take effect +# default 1 +fsharp_max_array_or_list_number_of_items=1 + +# whether to use line-length (by counting chars) or items (by counting fields) +# for the list and array settings above +# either number_of_items or character_width +# default character_width +fsharp_array_or_list_multiline_formatter=character_width + +# maximum with of a value binding, does not include keyword "let" +# default 80 +fsharp_max_value_binding_width=100 + +# maximum width for function and member binding (rh-side) +# default 40 +fsharp_max_function_binding_width=80 + +# maximum width for expressions like X.DoY().GetZ(10).Help() +# default 50 +fsharp_max_dot_get_expression_width=80 + +# whether open/close brackets go on the same column +# cramped: type Range = +# { From: float +# To: float } +# aligned: type Range = +# { +# From: float +# To: float +# } +# stroustrup: type Range = { +# From: float +# To: float +# } +# default cramped +fsharp_multiline_bracket_style=stroustrup + +# whether to move the beginning of compuitation expression to the new line +# true: let x = +# computation { +# ... +# } +# false: let x = computation { +# .. +# } +fsharp_newline_before_multiline_computation_expression=false + +# whether a newline should be placed before members +# false: type Range = +# { From: float } +# member this.Length = this.To - this.From +# true: type Range = +# { From: float } +# +# member this.Length = this.To - this.From +# default false +fsharp_newline_between_type_definition_and_members=true + +# if a function sign exceeds max_line_length, then: +# false: do not place the equal-sign on a single line +# true: place the equal-sign on a single line +# default false +fsharp_align_function_signature_to_indentation=false + +# see docs: https://github.com/fsprojects/fantomas/blob/master/docs/Documentation.md#fsharp_alternative_long_member_definitions +# default false +fsharp_alternative_long_member_definitions=true + +# places closing paren in lambda on a newline in multiline lambdas +# default false +fsharp_multi_line_lambda_closing_newline=true + +# allows the 'else'-branch to be aligned at same level as 'else' if the ret type allows it +# false: match x with +# | null -> () +# | _ -> () +# true: match x with +# | null -> () +# | _ -> +# () +# default false +fsharp_keep_indent_in_branch=true + +# whether a bar is placed before DU +# false: type MyDU = Short of int +# true: type MyDU = | Short of int +# default false +fsharp_bar_before_discriminated_union_declaration=false + +# multiline, nested expressions must be surrounded by blank lines +# default true +fsharp_blank_lines_around_nested_multiline_expressions=false + +# set maximal number of consecutive blank lines to keep from original source +# it doesn't change number of new blank lines generated by Fantomas +fsharp_keep_max_number_of_blank_lines=2 diff --git a/.fantomasignore b/.fantomasignore new file mode 100644 index 0000000..d9f2aa7 --- /dev/null +++ b/.fantomasignore @@ -0,0 +1,2 @@ +# Ignore AssemblyInfo files +AssemblyInfo.fs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..9c8817e --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# This file contains a list of git hashes of revisions to be ignored by git +# These revisions are considered "unimportant" in +# that they are unlikely to be what you are interested in when blaming. +# Like formatting with Fantomas +# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view +# Add formatting commits here diff --git a/.gitattributes b/.gitattributes index 206349c..cb6f883 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,9 @@ # Auto detect text files -* text=auto +* text=auto # Custom for Visual Studio *.cs diff=csharp text=auto eol=lf +*.vb diff=csharp text=auto eol=lf *.fs diff=csharp text=auto eol=lf *.fsi diff=csharp text=auto eol=lf *.fsx diff=csharp text=auto eol=lf @@ -11,6 +12,7 @@ *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union +*.sh text eol=lf # Standard to msysgit *.doc diff=astextplain diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..02ca9be --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,31 @@ +## Description + +Please insert a description of your problem or question. + +## Error messages, screenshots + +Please add any error logs or screenshots if available. + +## Failing test, failing GitHub repo, or reproduction steps + +Please add either a failing test, a GitHub repo of the problem or detailed reproduction steps. + +## Expected Behavior + +Please define what you would expect the behavior to be like. + +## Known workarounds + +Please provide a description of any known workarounds. + +## Other information + +* Operating System: + - [ ] windows [insert version here] + - [ ] macOs [insert version] + - [ ] linux [insert flavor/version here] +* Platform + - [ ] dotnet core + - [ ] dotnet full + - [ ] mono +* Branch or release version: diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..b735373 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..066b2d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..ab249a8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +## Proposed Changes + +Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. + +## Types of changes + +What types of changes does your code introduce to FSharp.Azure.Cosmos? +_Put an `x` in the boxes that apply_ + +- [ ] Bugfix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) + + +## Checklist + +_Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ + +- [ ] Build and tests pass locally +- [ ] I have added tests that prove my fix is effective or that my feature works (if appropriate) +- [ ] I have added necessary documentation (if appropriate) + +## Further comments + +If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..8690c43 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,57 @@ +We prefer the latest F# 9 features over the old syntax + +Prefer `voption` over `option` + +Prefer `task` CE over `async` CE + +This is how you define a non-default F# class constructor: +```fsharp +type DerivedClass = + inherit BaseClass + + new (``arguments here``) as ``created object`` + = + // create any objects used in the base class constructor + let fieldValue = "" + { + inherit + BaseClass (``arguments here``) + } + then + ``created object``.otherField <- fieldValue + + [] + val mutable otherField : FieldType +``` + +Always prefer F# class initializers over property assignment! **You absolutely must use F# class initializers instead of property assignment**! + +Class declaration: +``` F# +type MyClass (someConstructorParam : string) = + member ReadOnlyProperty = someConstructorParam + + member val MutableProperty1 = "" with get, set + member val MutableProperty2 = "" with get, set +``` + +Wrong: +``` F# +let myClass = MyClass("some value") +myClass.MutableProperty1 <- "new value" +myClass.MutableProperty2 <- "new value" +``` + +Right: +``` F# +let myClass = + MyClass( + // constructor parameters go first without names + "some value", + // then mutable properties go next with names + MutableProperty1 = "new value", + MutableProperty2 = + // operations must be placed into parentheses + (5 |> string) + ) +``` diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a45ad74..31c54a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,21 +1,57 @@ -name: .NET Core +name: Build main on: push: - branches-ignore: - - master - - develop + branches: + - main + pull_request: + branches: + - main jobs: build: + strategy: + matrix: + configuration: [Debug, Release] + os: [ubuntu-latest, windows-latest, macOS-latest] + runs-on: ${{ matrix.os }} - runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup necessary dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + dotnet-version: | + 9.x + 8.x + - name: Build via Bash + if: runner.os != 'Windows' + run: | + chmod +x ./build.sh + ./build.sh + env: + CI: true + CONFIGURATION: ${{ matrix.configuration }} + ENABLE_COVERAGE: true + - name: Build via Windows + if: runner.os == 'Windows' + run: ./build.cmd + env: + CI: true + CONFIGURATION: ${{ matrix.configuration }} + ENABLE_COVERAGE: true + # Builds the project in a dev container + build-devcontainer: + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - - name: Build with dotnet - run: | - DOTNET_CLI_TELEMETRY_OPTOUT=1 - dotnet build --nologo --configuration Release + + - uses: actions/checkout@v3 + + - name: Build and run dev container task + uses: devcontainers/ci@v0.3 + with: + runCmd: | + chmod +x ./build.sh + ./build.sh diff --git a/.github/workflows/fsdocs-gh-pages.yml b/.github/workflows/fsdocs-gh-pages.yml new file mode 100644 index 0000000..0ebb9ec --- /dev/null +++ b/.github/workflows/fsdocs-gh-pages.yml @@ -0,0 +1,61 @@ +name: Deploy Docs + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Setup necessary dotnet SDKs + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + dotnet-version: | + 9.x + + - name: Build Docs + run: | + chmod +x ./build.sh + ./build.sh builddocs + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/ + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/publish_ci.yml b/.github/workflows/publish_ci.yml index b63bbb6..9a0f45b 100644 --- a/.github/workflows/publish_ci.yml +++ b/.github/workflows/publish_ci.yml @@ -1,25 +1,65 @@ -name: publish to MyGet +name: Publish to GitHub on: push: branches: - - develop + - main + +env: + CONFIGURATION: Release jobs: build: - + # Sets permissions of the GITHUB_TOKEN to allow release creating + permissions: + packages: write + environment: + name: nuget runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - - name: Build with dotnet - run: | - sed -i "s|\(.*\)|\1-ci-$GITHUB_RUN_ID|" src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj - dotnet pack --nologo --configuration Release -o nuget - - name: MyGet push - run: | - source=https://www.myget.org/F/fsharp-collections-immutable/api/v3/index.json - key=${{secrets.MyGet_Key}} - dotnet nuget push -s $source -k $key nuget/*.nupkg + - uses: actions/checkout@v3 + + - name: Setup necessary dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + dotnet-version: | + 9.x + 8.x + + - name: Add the GitHub source + run: dotnet nuget add source --name "github.com" "https://nuget.pkg.github.com/fsprojects/index.json" + + - name: Ensure NuGet package source mapping + shell: pwsh + run: | + $nugetConfigPath = "$HOME/.nuget/NuGet/NuGet.Config" + [xml]$nugetConfig = Get-Content $nugetConfigPath + + $packageSourceMapping = $nugetConfig.configuration.packageSourceMapping + if ($packageSourceMapping -ne $null) { + $packageSourceMapping.RemoveAll() + } else { + $packageSourceMapping = $nugetConfig.CreateElement("packageSourceMapping") + $nugetConfig.configuration.AppendChild($packageSourceMapping) + } + + $nugetSource = $nugetConfig.CreateElement("packageSource") + $nugetSource.SetAttribute("key", "nuget.org") + $nugetPattern = $nugetConfig.CreateElement("package") + $nugetPattern.SetAttribute("pattern", "*") + $nugetSource.AppendChild($nugetPattern) + $packageSourceMapping.AppendChild($nugetSource) + + $nugetConfig.Save($nugetConfigPath) + + - name: Publish to GitHub + env: + NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FAKE_DETAILED_ERRORS: true + ENABLE_COVERAGE: false # AltCover doesn't work with Release builds, reports lower coverage than actual + run: | + chmod +x ./build.sh + ./build.sh "PublishToGitHub" diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index acd6a0f..d92d34f 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -1,32 +1,36 @@ -name: publish to NuGet +name: Publish to NuGet on: push: - branches: - - master + tags: + - 'releases/*' +env: + CONFIGURATION: Release jobs: - publish: - + build: + # Sets permissions of the GITHUB_TOKEN to allow release creating + permissions: + contents: write + environment: + name: nuget runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - # Required for a specific dotnet version that doesn't come with ubuntu-latest / windows-latest - # Visit bit.ly/2synnZl to see the list of SDKs that are pre-installed with ubuntu-latest / windows-latest - # - name: Setup dotnet - # uses: actions/setup-dotnet@v1 - # with: - # dotnet-version: 3.1.100 - - # Publish - - name: publish on version change - uses: rohith/publish-nuget@v2 + - uses: actions/checkout@v3 + - name: Setup necessary dotnet SDKs + uses: actions/setup-dotnet@v3 with: - PROJECT_FILE_PATH: src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj # Relative to repository root - # VERSION_FILE_PATH: Directory.Build.props # Filepath with version info, relative to repository root. Defaults to project file - # VERSION_REGEX: (.*)<\/Version> # Regex pattern to extract version info in a capturing group - # TAG_COMMIT: true # Flag to enable / disalge git tagging - # TAG_FORMAT: v* # Format of the git tag, [*] gets replaced with version - NUGET_KEY: ${{secrets.NuGet_Key}} + global-json-file: global.json + dotnet-version: | + 9.x + 8.x + + - name: Publish to NuGet + env: + NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FAKE_DETAILED_ERRORS: true + ENABLE_COVERAGE: false # AltCover doesn't work with Release builds, reports lower coverage than actual + run: | + chmod +x ./build.sh + ./build.sh Publish diff --git a/.gitignore b/.gitignore index 98a4281..99fd3b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files +*.rsuser *.suo *.user *.userosscache @@ -10,6 +13,9 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +# Mono auto generated files +mono_crash.* + # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -17,39 +23,62 @@ [Rr]eleases/ x64/ x86/ -build/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ +[Ll]og/ +[Ll]ogs/ -# Visual Studio 2015 cache/options directory +# Visual Studio 2015/2017 cache/options directory .vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUNIT +# NUnit *.VisualState.xml TestResult.xml +nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c -# DNX +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core project.lock.json +project.fragment.lock.json artifacts/ +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio *_i.c *_p.c -*_i.h +*_h.h *.ilk *.meta *.obj +*.iobj *.pch *.pdb +*.ipdb *.pgc *.pgd *.rsp @@ -59,7 +88,9 @@ artifacts/ *.tlh *.tmp *.tmp_proj +*_wpftmp.csproj *.log +*.tlog *.vspscc *.vssscc .builds @@ -74,14 +105,21 @@ _Chutzpah* ipch/ *.aps *.ncb +*.opendb *.opensdf *.sdf *.cachefile +*.VC.db +*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx +*.sap + +# Visual Studio Trace Files +*.e2e # TFS 2012 Local Workspace $tf/ @@ -89,9 +127,6 @@ $tf/ # Guidance Automation Toolkit *.gpState -# CodeRush is a .NET coding add-in -.cr/ - # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper @@ -106,9 +141,23 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + # NCrunch _NCrunch_* .*crunch*.local.xml +nCrunchTemp_* # MightyMoose *.mm.* @@ -136,49 +185,72 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -## TODO: Comment the next line if you want to checkin your -## web deploy settings but do note that will include unencrypted -## passwords -#*.pubxml - +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml *.publishproj +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + # NuGet Packages *.nupkg +# NuGet Symbol Packages +*.snupkg # The packages folder can be ignored because of Package Restore -**/packages/* -# project.json output for .csproj and .fsproj projects can be ignored because of Package Restore -*.nuget.targets +**/[Pp]ackages/* # except build/, which is used as an MSBuild target. -!**/packages/build/ +!**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets -# Windows Azure Build Output +# Microsoft Azure Build Output csx/ *.build.csdef -# Windows Store app package directory +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache -!*.[Cc]ache/ +!?*.[Cc]ache/ # Others ClientBin/ -[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview +*.jfm *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + # RIA/Silverlight projects Generated_Code/ @@ -189,21 +261,32 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak # SQL Server files *.mdf *.ldf +*.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ +# GhostDoc plugin setting file +*.GhostDoc.xml + # Node.js Tools for Visual Studio .ntvs_analysis.dat +node_modules/ # Visual Studio 6 build log *.plg @@ -211,9 +294,114 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt -# LightSwitch generated files -GeneratedArtifacts/ -_Pvt_Extensions/ -ModelManifest.xml +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder .ionide/ -.idea/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +# fsdocs generated +tmp/ +temp/ +.fsdocs +docs/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..d566ef1 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "ionide.ionide-paket", + "ionide.ionide-fsharp", + "ionide.ionide-fake", + "ms-dotnettools.csharp", + "editorConfig.editorConfig" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b98926a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "FSharp.fsacRuntime":"netcore", + "FSharp.enableAnalyzers": false, + "FSharp.analyzersPath": [ + "./packages/analyzers" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e3f2456 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2020-02-23 + +First release + +### Implemented type abbreviations and modules with functions for: +* `FlatList` (`ImmutableArray`) +* `ImmutableList` +* `Stack` (`ImmutableStack`) +* `Queue` (`ImmutableQueue`) +* `HashMap` (`ImmutableDictionary`) +* `SortedMap` (`ImmutableSortedDictionary`) +* `HashSet` (`ImmutableHashSet`) +* `SortedSet` (`ImmutableSortedSet`) +* `IIndexedSeq` (`IReadOnlyList`) + +[Unreleased]: https://github.com/fsprojects/FSharp.Collections.Immutable/compare/releases/1.0.0...HEAD +[1.0.0]: https://github.com/fsprojects/FSharp.Collections.Immutable/releases/tag/releases/1.0.0 diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..d14b42f --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,49 @@ + + + + + F#;FSharp;Collections;Immutable + https://github.com/fsprojects/FSharp.Collections.Immutable + false + LICENSE + README.md + logo.png + + git + fsprojects, EventHelix;vilinski;anthony-mi;dim-37 + https://github.com/fsprojects/FSharp.Collections.Immutable + + true + + true + snupkg + true + + + + FSharp.Collections.Immutable + 9.0 + enable + true + false + true + + + + + + + + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..484ac5a --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,47 @@ + + + + + <_BuildProjBaseIntermediateOutputPath>$(MSBuildThisFileDirectory)build/obj/ + <_DotnetToolManifestFile>$(MSBuildThisFileDirectory).config/dotnet-tools.json + <_DotnetToolRestoreOutputFile>$(_BuildProjBaseIntermediateOutputPath)/dotnet-tool-restore-$(NETCoreSdkVersion)-$(OS) + <_DotnetFantomasOutputFile>$(BaseIntermediateOutputPath)dotnet-fantomas-msbuild-$(NETCoreSdkVersion)-$(OS) + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..b822078 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FSharp.Collections.Immutable.sln b/FSharp.Collections.Immutable.sln deleted file mode 100644 index a9324bb..0000000 --- a/FSharp.Collections.Immutable.sln +++ /dev/null @@ -1,34 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29021.104 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Collections.Immutable", "src\FSharp.Collections.Immutable\FSharp.Collections.Immutable.fsproj", "{9805E74C-D028-4D05-9256-5C2FDC9B6EA8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9BE8C74B-E5EF-4A0E-8E77-CDCD967B6E88}" - ProjectSection(SolutionItems) = preProject - .github\workflows\build.yml = .github\workflows\build.yml - .github\workflows\publish_ci.yml = .github\workflows\publish_ci.yml - .github\workflows\publish_release.yml = .github\workflows\publish_release.yml - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9805E74C-D028-4D05-9256-5C2FDC9B6EA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9805E74C-D028-4D05-9256-5C2FDC9B6EA8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9805E74C-D028-4D05-9256-5C2FDC9B6EA8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9805E74C-D028-4D05-9256-5C2FDC9B6EA8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {5A4E44BA-0D9F-4E81-A35E-83A38C3E43CF} - EndGlobalSection - GlobalSection(CodealikeProperties) = postSolution - SolutionGuid = f92c102d-027e-479b-995c-b971032fea8c - EndGlobalSection -EndGlobal diff --git a/FSharp.Collections.Immutable.slnf b/FSharp.Collections.Immutable.slnf new file mode 100644 index 0000000..097e0fa --- /dev/null +++ b/FSharp.Collections.Immutable.slnf @@ -0,0 +1,9 @@ +{ + "solution": { + "path": "FSharp.Collections.Immutable.slnx", + "projects": [ + "src\\FSharp.Collections.Immutable\\FSharp.Collections.Immutable.fsproj", + "tests\\FSharp.Collections.Immutable.Tests\\FSharp.Collections.Immutable.Tests.fsproj" + ] + } +} \ No newline at end of file diff --git a/FSharp.Collections.Immutable.slnx b/FSharp.Collections.Immutable.slnx new file mode 100644 index 0000000..e0ade1a --- /dev/null +++ b/FSharp.Collections.Immutable.slnx @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 1ec1d40..7819e3f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,122 @@ -# FSharp.Collections.Immutable +# FSharp.Collections.Immutable [![NuGet Status](http://img.shields.io/nuget/v/FSharp.Collections.Immutable.svg?style=flat)](https://www.nuget.org/packages/FSharp.Collections.Immutable/) [![GitHub Actions](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/Build%20main/badge.svg)](https://github.com/fsprojects/FSharp.Collections.Immutable/actions?query=branch%3Amain) F# bindings for System.Collections.Immutable -![.NET Core](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/.NET%20Core/badge.svg) +![Build status](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/.NET%20Core/badge.svg) + +**FSharp.Collections.Immutable** is a collection of F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable). + +Please contribute to this project. Don't ask for permission, just fork the repository and send pull requests. + +Please also join the [F# Open Source Group](http://fsharp.github.com) + +## Features + +* `FlatList` (`ImmutableArray`) +* `ImmutableList` +* `Stack` (`ImmutableStack`) +* `Queue` (`ImmutableQueue`) +* `HashMap` (`ImmutableDictionary`) +* `SortedMap` (`ImmutableSortedDictionary`) +* `HashSet` (`ImmutableHashSet`) +* `SortedSet` (`ImmutableSortedSet`) +* `IIndexedSeq` (`IReadOnlyList`) + +[Documentation](https://fsprojects.github.io/FSharp.Collections.Immutable/) + +--- + +## Builds + +GitHub Actions | +:---: | +[![GitHub Actions](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/Build%20main/badge.svg)](https://github.com/fsprojects/FSharp.Collections.Immutable/actions?query=branch%3Amain) | + +## NuGet + +Package | Stable | Prerelease +--- | --- | --- +FSharp.Collections.Immutable | [![NuGet Badge](https://img.shields.io/nuget/v/FSharp.Collections.Immutable.svg)](https://www.nuget.org/packages/FSharp.Collections.Immutable/) | [![NuGet Badge](https://img.shields.io/nuget/vpre/FSharp.Collections.Immutable.svg)](https://www.nuget.org/packages/FSharp.Collections.Immutable/) + +--- + +### Developing + +Make sure the following **requirements** are installed on your system: + +- [dotnet SDK](https://www.microsoft.com/net/download/core) 8.0 or higher + +or + +- [VSCode Dev Container](https://code.visualstudio.com/docs/remote/containers) + + +--- + +### Environment Variables + +- `CONFIGURATION` will set the [configuration](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x#options) of the dotnet commands. If not set, it will default to Release. + - `CONFIGURATION=Debug ./build.sh` will result in `-c` additions to commands such as in `dotnet build -c Debug` +- `ENABLE_COVERAGE` Will enable running code coverage metrics. AltCover can have [severe performance degradation](https://github.com/SteveGilham/altcover/issues/57) so code coverage evaluation are disabled by default to speed up the feedback loop. + - `ENABLE_COVERAGE=1 ./build.sh` will enable code coverage evaluation + + +--- + +### Building +> build.cmd // on windows + +> ./build.sh // on unix +--- + +### Build Targets + +- `Clean` - Cleans artifact and temp directories. +- `DotnetRestore` - Runs [dotnet restore](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-restore?tabs=netcore2x) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). +- [`DotnetBuild`](#Building) - Runs [dotnet build](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). +- `FSharpAnalyzers` - Runs [BinaryDefense.FSharp.Analyzers](https://github.com/BinaryDefense/BinaryDefense.FSharp.Analyzers). +- `DotnetTest` - Runs [dotnet test](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=netcore21) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). +- `GenerateCoverageReport` - Code coverage is run during `DotnetTest` and this generates a report via [ReportGenerator](https://github.com/danielpalme/ReportGenerator). +- `ShowCoverageReport` - Shows the report generated in `GenerateCoverageReport`. +- `WatchTests` - Runs [dotnet watch](https://docs.microsoft.com/en-us/aspnet/core/tutorials/dotnet-watch?view=aspnetcore-3.0) with the test projects. Useful for rapid feedback loops. +- `GenerateAssemblyInfo` - Generates [AssemblyInfo](https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.applicationservices.assemblyinfo?view=netframework-4.8) for libraries. +- `DotnetPack` - Runs [dotnet pack](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-pack). This includes running [Source Link](https://github.com/dotnet/sourcelink). +- `SourceLinkTest` - Runs a Source Link test tool to verify Source Links were properly generated. +- `PublishToNuGet` - Publishes the NuGet packages generated in `DotnetPack` to NuGet via [paket push](https://fsprojects.github.io/Paket/paket-push.html). Runs only from `Github Actions`. +- `GitRelease` - Creates a commit message with the [Release Notes](https://fake.build/apidocs/v5/fake-core-releasenotes.html) and a git tag via the version in the `Release Notes`. +- `GitHubRelease` - Publishes a [GitHub Release](https://help.github.com/en/articles/creating-releases) with the Release Notes and any NuGet packages. Runs only from `Github Actions`. +- `FormatCode` - Runs [Fantomas](https://github.com/fsprojects/fantomas) on the solution file. +- `CheckFormatCode` - Runs [Fantomas --check](https://fsprojects.github.io/fantomas/docs/end-users/FormattingCheck.html) on the solution file. +- `BuildDocs` - Generates [Documentation](https://fsprojects.github.io/FSharp.Formatting) from `docsSrc` and the [XML Documentation Comments](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/) from your libraries in `src`. +- `WatchDocs` - Generates documentation and starts a webserver locally. It will rebuild and hot reload if it detects any changes made to `docsSrc` files, or libraries in `src`. + +--- + + +### Releasing + +- [Start a git repo with a remote](https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/) +git init +git add . +git commit -m "Scaffold" +git branch -M main +git remote add origin https://github.com/fsprojects/FSharp.Collections.Immutable.git +git push -u origin main +- [Create an Environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#creating-an-environment) on your repository named `nuget`. +- [Create a NuGet API key](https://learn.microsoft.com/en-us/nuget/nuget-org/publish-a-package#create-an-api-key) +- Add your `NUGET_TOKEN` to the [Environment Secrets](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-secrets) of your newly created environment. +- Then update the `CHANGELOG.md` with an "Unreleased" section containing release notes for this version, in [KeepAChangelog](https://keepachangelog.com/en/1.1.0/) format. + +NOTE: Its highly recommend to add a link to the Pull Request next to the release note that it affects. The reason for this is when the `RELEASE` target is run, it will add these new notes into the body of git commit. GitHub will notice the links and will update the Pull Request with what commit referenced it saying ["added a commit that referenced this pull request"](https://github.com/TheAngryByrd/MiniScaffold/pull/179#ref-commit-837ad59). Since the build script automates the commit message, it will say "Bump Version to x.y.z". The benefit of this is when users goto a Pull Request, it will be clear when and which version those code changes released. Also when reading the `CHANGELOG`, if someone is curious about how or why those changes were made, they can easily discover the work and discussions. + +### Releasing Documentation + +- Set Source for "Build and deployment" on [GitHub Pages](https://github.com/fsprojects/FSharp.Collections.Immutable/settings/pages) to `GitHub Actions`. +- Documentation is auto-deployed via [GitHub Action](https://github.com/fsprojects/FSharp.Collections.Immutable/blob/main/.github/workflows/fsdocs-gh-pages.yml) to [Your GitHub Page](https://fsprojects.github.io/FSharp.Collections.Immutable/) + +# Maintainer(s) + +- [@xperiandri](https://github.com/xperiandri) +- [@eventhelix](https://github.com/eventhelix) + +The default maintainer account for projects under "fsprojects" is [@fsprojectsgit](https://github.com/fsprojectsgit) - F# Community Project Incubation Space (repo management) diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..b9f6df4 --- /dev/null +++ b/build.cmd @@ -0,0 +1 @@ +dotnet run --project ./build/build.fsproj -- -t %* diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..0783d63 --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -eu +set -o pipefail + +FAKE_DETAILED_ERRORS=true dotnet run --project ./build/build.fsproj -- -t "$@" diff --git a/build/Changelog.fs b/build/Changelog.fs new file mode 100644 index 0000000..27ceada --- /dev/null +++ b/build/Changelog.fs @@ -0,0 +1,233 @@ +module Changelog + +open System +open Fake.Core +open Fake.IO + +let isEmptyChange = + function + | Changelog.Change.Added s + | Changelog.Change.Changed s + | Changelog.Change.Deprecated s + | Changelog.Change.Fixed s + | Changelog.Change.Removed s + | Changelog.Change.Security s + | Changelog.Change.Custom (_, s) -> String.IsNullOrWhiteSpace s.CleanedText + +let tagFromVersionNumber versionNumber = sprintf "releases/%s" versionNumber + +let failOnEmptyChangelog (latestEntry : Changelog.ChangelogEntry) = + let isEmpty = + (latestEntry.Changes |> Seq.forall isEmptyChange) + || latestEntry.Changes |> Seq.isEmpty + + if isEmpty then + failwith "No changes in CHANGELOG. Please add your changes under a heading specified in https://keepachangelog.com/" + +let mkLinkReference (newVersion : SemVerInfo) (changelog : Changelog.Changelog) (gitHubRepoUrl : string) = + if changelog.Entries |> List.isEmpty then + // No actual changelog entries yet: link reference will just point to the Git tag + sprintf "[%s]: %s/releases/tag/%s" newVersion.AsString gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) + else + let versionTuple version = (version.Major, version.Minor, version.Patch) + // Changelog entries come already sorted, most-recent first, by the Changelog module + let prevEntry = + changelog.Entries + |> List.skipWhile (fun entry -> + entry.SemVer.PreRelease.IsSome + || versionTuple entry.SemVer = versionTuple newVersion + ) + |> List.tryHead + + let linkTarget = + match prevEntry with + | Some entry -> + sprintf + "%s/compare/%s...%s" + gitHubRepoUrl + (tagFromVersionNumber entry.SemVer.AsString) + (tagFromVersionNumber newVersion.AsString) + | None -> sprintf "%s/releases/tag/%s" gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) + + sprintf "[%s]: %s" newVersion.AsString linkTarget + +let mkReleaseNotes changelog (latestEntry : Changelog.ChangelogEntry) gitHubRepoUrl = + let linkReference = mkLinkReference latestEntry.SemVer changelog gitHubRepoUrl + + if String.isNullOrEmpty linkReference then + latestEntry.ToString () + else + // Add link reference target to description before building release notes, since in main changelog file it's at the bottom of the file + let description = + match latestEntry.Description with + | None -> linkReference + | Some desc when desc.Contains (linkReference) -> desc + | Some desc -> sprintf "%s\n\n%s" (desc.Trim ()) linkReference + + { latestEntry with Description = Some description }.ToString () + +let getVersionNumber envVarName ctx = + let args = ctx.Context.Arguments + + let verArg = + args + |> List.tryHead + |> Option.defaultWith (fun () -> Environment.environVarOrDefault envVarName "") + + if SemVer.isValid verArg then + verArg + elif verArg.StartsWith ("v") && SemVer.isValid verArg.[1..] then + let target = ctx.Context.FinalTarget + + Trace.traceImportantfn + "Please specify a version number without leading 'v' next time, e.g. \"./build.sh %s %s\" rather than \"./build.sh %s %s\"" + target + verArg.[1..] + target + verArg + + verArg.[1..] + elif String.isNullOrEmpty verArg then + let target = ctx.Context.FinalTarget + + Trace.traceErrorfn + "Please specify a version number, either at the command line (\"./build.sh %s 1.0.0\") or in the %s environment variable" + target + envVarName + + failwith "No version number found" + else + Trace.traceErrorfn "Please specify a valid version number: %A could not be recognized as a version number" verArg + + failwith "Invalid version number" + +let mutable changelogBackupFilename = "" + +let updateChangelog changelogPath (changelog : Fake.Core.Changelog.Changelog) gitHubRepoUrl ctx = + + let verStr = ctx |> getVersionNumber "RELEASE_VERSION" + + let description, unreleasedChanges = + match changelog.Unreleased with + | None -> None, [] + | Some u -> u.Description, u.Changes + + let newVersion = SemVer.parse verStr + + changelog.Entries + |> List.tryFind (fun entry -> entry.SemVer = newVersion) + |> Option.iter (fun entry -> + Trace.traceErrorfn + "Version %s already exists in %s, released on %s" + verStr + changelogPath + (if entry.Date.IsSome then + entry.Date.Value.ToString ("yyyy-MM-dd") + else + "(no date specified)") + + failwith "Can't release with a duplicate version number" + ) + + changelog.Entries + |> List.tryFind (fun entry -> entry.SemVer > newVersion) + |> Option.iter (fun entry -> + Trace.traceErrorfn + "You're trying to release version %s, but a later version %s already exists, released on %s" + verStr + entry.SemVer.AsString + (if entry.Date.IsSome then + entry.Date.Value.ToString ("yyyy-MM-dd") + else + "(no date specified)") + + failwith "Can't release with a version number older than an existing release" + ) + + let versionTuple version = (version.Major, version.Minor, version.Patch) + + let prereleaseEntries = + changelog.Entries + |> List.filter (fun entry -> + entry.SemVer.PreRelease.IsSome + && versionTuple entry.SemVer = versionTuple newVersion + ) + + let prereleaseChanges = + prereleaseEntries + |> List.collect (fun entry -> entry.Changes |> List.filter (not << isEmptyChange)) + |> List.distinct + + let assemblyVersion, nugetVersion = Changelog.parseVersions newVersion.AsString + + let newEntry = + Changelog.ChangelogEntry.New ( + assemblyVersion.Value, + nugetVersion.Value, + Some System.DateTime.Today, + description, + unreleasedChanges @ prereleaseChanges, + false + ) + + let newChangelog = + Changelog.Changelog.New (changelog.Header, changelog.Description, None, newEntry :: changelog.Entries) + + // Save changelog to temporary file before making any edits + changelogBackupFilename <- System.IO.Path.GetTempFileName () + + changelogPath |> Shell.copyFile changelogBackupFilename + + Target.activateFinal "DeleteChangelogBackupFile" + + newChangelog |> Changelog.save changelogPath + + // Now update the link references at the end of the file + let linkReferenceForLatestEntry = mkLinkReference newVersion changelog gitHubRepoUrl + + let linkReferenceForUnreleased = + sprintf "[Unreleased]: %s/compare/%s...%s" gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) "HEAD" + + let tailLines = File.read changelogPath |> List.ofSeq |> List.rev + + let isRef (line : string) = + System.Text.RegularExpressions.Regex.IsMatch (line, @"^\[.+?\]:\s?[a-z]+://.*$") + + let linkReferenceTargets = + tailLines + |> List.skipWhile String.isNullOrWhiteSpace + |> List.takeWhile isRef + |> List.rev // Now most recent entry is at the head of the list + + let newLinkReferenceTargets = + match linkReferenceTargets with + | [] -> [ linkReferenceForUnreleased; linkReferenceForLatestEntry ] + | first :: rest when first |> String.startsWith "[Unreleased]:" -> + linkReferenceForUnreleased + :: linkReferenceForLatestEntry + :: rest + | first :: rest -> + linkReferenceForUnreleased + :: linkReferenceForLatestEntry + :: first + :: rest + + let blankLineCount = + tailLines + |> Seq.takeWhile String.isNullOrWhiteSpace + |> Seq.length + + let linkRefCount = linkReferenceTargets |> List.length + + let skipCount = blankLineCount + linkRefCount + + let updatedLines = + List.rev (tailLines |> List.skip skipCount) + @ newLinkReferenceTargets + + File.write false changelogPath updatedLines + + // If build fails after this point but before we commit changes, undo our modifications + Target.activateBuildFailure "RevertChangelog" + + newEntry diff --git a/build/FsDocs.fs b/build/FsDocs.fs new file mode 100644 index 0000000..8c16481 --- /dev/null +++ b/build/FsDocs.fs @@ -0,0 +1,224 @@ +namespace Fake.DotNet + +open Fake.Core +open Fake.IO +open Fake.IO.FileSystemOperators + +/// +/// Contains tasks to interact with fsdocs tool to +/// process F# script files, markdown and for generating API documentation. +/// +[] +module Fsdocs = + + /// + /// Fsdocs build command parameters and options + /// + type BuildCommandParams = { + /// Input directory of content (default: docs) + Input : string option + + /// Project files to build API docs for outputs, defaults to all packable projects + Projects : seq option + + /// Output Directory (default output for build and tmp/watch for watch) + Output : string option + + /// Disable generation of API docs + NoApiDocs : bool option + + /// Evaluate F# fragments in scripts + Eval : bool option + + /// Save images referenced in docs + SaveImages : bool option + + /// Add line numbers + LineNumbers : bool option + + /// Additional substitution parameters for templates + Parameters : seq option + + /// Disable project cracking. + IgnoreProjects : bool option + + /// In API doc generation qualify the output by the collection name, e.g. 'reference/FSharp.Core/...' instead of 'reference/...' . + Qualify : bool option + + /// The tool will also generate documentation for non-public members + NoPublic : bool option + + /// Do not copy default content styles, javascript or use default templates + NoDefaultContent : bool option + + /// Clean the output directory + Clean : bool option + + /// Display version information + Version : bool option + + /// Provide properties to dotnet msbuild, e.g. --properties Configuration=Release Version=3.4 + Properties : string option + + /// Additional arguments passed down as otherflags to the F# compiler when the API is being generated. + /// Note that these arguments are trimmed, this is to overcome a limitation in the command line argument + /// processing. A typical use-case would be to pass an addition assembly reference. + /// Example --fscoptions " -r:MyAssembly.dll" + FscOptions : string option + + /// Fail if docs are missing or can't be generated + Strict : bool option + + /// Source folder at time of component build (<FsDocsSourceFolder>) + SourceFolder : string option + + /// Source repository for github links (<FsDocsSourceRepository>) + SourceRepository : string option + + /// Assume comments in F# code are markdown (<UsesMarkdownComments>) + MdComments : bool option + } with + + /// Parameter default values. + static member Default = { + Input = None + Projects = None + Output = None + NoApiDocs = None + Eval = None + SaveImages = None + LineNumbers = None + Parameters = None + IgnoreProjects = None + Qualify = None + NoPublic = None + NoDefaultContent = None + Clean = None + Version = None + Properties = None + FscOptions = None + Strict = None + SourceFolder = None + SourceRepository = None + MdComments = None + } + + /// + /// Fsdocs watch command parameters and options + /// + type WatchCommandParams = { + /// Do not serve content when watching. + NoServer : bool option + + /// Do not launch a browser window. + NoLaunch : bool option + + /// URL extension to launch http://localhost:/%s. + Open : string option + + /// Port to serve content for http://localhost serving. + Port : int option + + /// Build Commands + BuildCommandParams : BuildCommandParams option + } with + + /// Parameter default values. + static member Default = { + NoServer = None + NoLaunch = None + Open = None + Port = None + BuildCommandParams = None + } + + let internal buildBuildCommandParams (buildParams : BuildCommandParams) = + let buildSubstitutionParameters (subParameters : seq) = + seq { + yield "--parameters" + for (key, value) in subParameters do + yield key + yield value + } + |> String.concat " " + + System.Text.StringBuilder () + |> StringBuilder.appendIfSome buildParams.Input (sprintf "--input %s") + |> StringBuilder.appendIfSome buildParams.Projects (fun projects -> $"""--projects %s{projects |> String.concat " "}""") + |> StringBuilder.appendIfSome buildParams.Output (sprintf "--output %s") + |> StringBuilder.appendIfSome buildParams.NoApiDocs (fun _ -> "--noapidocs") + |> StringBuilder.appendIfSome buildParams.Eval (fun _ -> "--eval") + |> StringBuilder.appendIfSome buildParams.SaveImages (fun _ -> "--saveimages") + |> StringBuilder.appendIfSome buildParams.LineNumbers (fun _ -> "--linenumbers") + |> StringBuilder.appendIfSome buildParams.Parameters (fun parameters -> buildSubstitutionParameters parameters) + |> StringBuilder.appendIfSome buildParams.IgnoreProjects (fun _ -> "--ignoreprojects") + |> StringBuilder.appendIfSome buildParams.Qualify (fun _ -> "--qualify") + |> StringBuilder.appendIfSome buildParams.NoPublic (fun _ -> "--nonpublic") + |> StringBuilder.appendIfSome buildParams.NoDefaultContent (fun _ -> "--nodefaultcontent") + |> StringBuilder.appendIfSome buildParams.Clean (fun _ -> "--clean") + |> StringBuilder.appendIfSome buildParams.Version (fun _ -> "--version") + |> StringBuilder.appendIfSome buildParams.Properties (sprintf "--properties %s") + |> StringBuilder.appendIfSome buildParams.FscOptions (sprintf "--fscoptions %s") + |> StringBuilder.appendIfSome buildParams.Strict (fun _ -> "--strict") + |> StringBuilder.appendIfSome buildParams.SourceFolder (sprintf "--sourcefolder %s") + |> StringBuilder.appendIfSome buildParams.SourceRepository (sprintf "--sourcerepo %s") + |> StringBuilder.appendIfSome buildParams.MdComments (fun _ -> "--mdcomments") + |> StringBuilder.toText + |> String.trim + + let internal buildWatchCommandParams (watchParams : WatchCommandParams) = + System.Text.StringBuilder () + |> StringBuilder.appendIfSome watchParams.NoServer (fun _ -> "--noserver") + |> StringBuilder.appendIfSome watchParams.NoLaunch (fun _ -> "--nolaunch") + |> StringBuilder.appendIfSome watchParams.Open (sprintf "--open %s") + |> StringBuilder.appendIfSome watchParams.Port (sprintf "--port %i") + |> StringBuilder.appendIfSome watchParams.BuildCommandParams buildBuildCommandParams + |> StringBuilder.toText + |> String.trim + + + let cleanCache (workingDirectory) = Shell.cleanDirs [ workingDirectory ".fsdocs" ] + + /// + /// Build documentation using fsdocs build command + /// + /// + /// Function used to overwrite the dotnetOptions. + /// Function used to overwrite the build command default parameters. + /// + /// + /// + /// Fsdocs.build (fun p -> { p with Clean = Some(true); Strict = Some(true) }) + /// + /// + let build dotnetOptions setBuildParams = + let buildParams = setBuildParams BuildCommandParams.Default + let formattedParameters = buildBuildCommandParams buildParams + + // let dotnetOptions = (fun (buildOptions: DotNet.Options) -> buildOptions) + let result = DotNet.exec dotnetOptions "fsdocs build" formattedParameters + + if 0 <> result.ExitCode then + failwithf "fsdocs build failed with exit code '%d'" result.ExitCode + + /// + /// Watch documentation using fsdocs watch command + /// + /// + /// Function used to overwrite the dotnetOptions. + /// Function used to overwrite the watch command default parameters. + /// + /// + /// + /// Fsdocs.watch (fun p -> { p with Port = Some(3005) }) + /// + /// + let watch dotnetOptions setWatchParams = + let watchParams = setWatchParams WatchCommandParams.Default + let formattedParameters = buildWatchCommandParams watchParams + + // let dotnetOptions = (fun (buildOptions: DotNet.Options) -> buildOptions) + let result = DotNet.exec dotnetOptions "fsdocs watch" formattedParameters + + if 0 <> result.ExitCode then + failwithf "fsdocs watch failed with exit code '%d'" result.ExitCode diff --git a/build/Properties/launchSettings.json b/build/Properties/launchSettings.json new file mode 100644 index 0000000..f0c59e8 --- /dev/null +++ b/build/Properties/launchSettings.json @@ -0,0 +1,24 @@ +{ + "profiles": { + "BuildAndTest": { + "commandName": "Project", + "commandLineArgs": "--target DotnetTest" + }, + "Publish": { + "commandName": "Project", + "commandLineArgs": "--target Publish" + }, + "PublishToGitHub": { + "commandName": "Project", + "commandLineArgs": "--target PublishToGitHub" + }, + "BuildDocs": { + "commandName": "Project", + "commandLineArgs": "--target BuildDocs" + }, + "Release": { + "commandName": "Project", + "commandLineArgs": "--target Release 1.0.0" + } + } +} diff --git a/build/build.fs b/build/build.fs new file mode 100644 index 0000000..c28e99c --- /dev/null +++ b/build/build.fs @@ -0,0 +1,731 @@ +open System +open Fake.Core +open Fake.DotNet +open Fake.Tools +open Fake.IO +open Fake.IO.FileSystemOperators +open Fake.IO.Globbing.Operators +open Fake.Core.TargetOperators +open Fake.Api +open Fake.BuildServer +open Argu + +let environVarAsBoolOrDefault varName defaultValue = + let truthyConsts = [ "1"; "Y"; "YES"; "T"; "TRUE" ] + Environment.environVar varName + |> ValueOption.ofObj + |> ValueOption.map (fun envvar -> + truthyConsts + |> List.exists (fun ``const`` -> String.Equals (``const``, envvar, StringComparison.InvariantCultureIgnoreCase)) + ) + |> ValueOption.defaultValue defaultValue + +//----------------------------------------------------------------------------- +// Metadata and Configuration +//----------------------------------------------------------------------------- + +let rootDirectory = __SOURCE_DIRECTORY__ ".." + +let productName = "FSharp.Collections.Immutable" + +let sln = rootDirectory "FSharp.Collections.Immutable.slnf" + +let srcCodeGlob = + !!(rootDirectory "src/**/*.fs") + ++ (rootDirectory "src/**/*.fsx") + -- (rootDirectory "src/**/obj/**/*.fs") + +let testsCodeGlob = + !!(rootDirectory "tests/**/*.fs") + ++ (rootDirectory "tests/**/*.fsx") + -- (rootDirectory "tests/**/obj/**/*.fs") + +let srcGlob = rootDirectory "src/**/*.??proj" + +let testsGlob = rootDirectory "tests/**/*.??proj" + +let srcAndTest = !!srcGlob ++ testsGlob + +let distDir = rootDirectory "dist" + +let distGlob = distDir "*.nupkg" + +let testResultsDir = rootDirectory "TestResults" + +let coverageReportDir = rootDirectory "docs" "coverage" + +let docsDir = rootDirectory "docs" + +let docsSrcDir = rootDirectory "docsSrc" + +let temp = rootDirectory "temp" + +let watchDocsDir = temp "watch-docs" + +let gitOwner = "fsprojects" +let gitRepoName = "FSharp.Collections.Immutable" + +let gitHubRepoUrl = $"https://github.com/%s{gitOwner}/%s{gitRepoName}" + +let documentationRootUrl = $"https://%s{gitOwner}.github.io/%s{gitRepoName}" + +let releaseBranch = "main" +let readme = "README.md" +let changelogFile = "CHANGELOG.md" + +let READMElink = Uri (Uri (gitHubRepoUrl), $"blob/{releaseBranch}/{readme}") +let CHANGELOGlink = Uri (Uri (gitHubRepoUrl), $"blob/{releaseBranch}/{changelogFile}") + +let changelogPath = rootDirectory changelogFile + +let changelog = Fake.Core.Changelog.load changelogPath + +let mutable latestEntry = + if Seq.isEmpty changelog.Entries then + Changelog.ChangelogEntry.New ("0.0.1", "0.0.1-alpha.1", Some DateTime.Today, None, [], false) + else + changelog.LatestEntry + +let mutable changelogBackupFilename = "" + +let publishUrl = "https://www.nuget.org" + +let enableCodeCoverage = environVarAsBoolOrDefault "ENABLE_COVERAGE" false + +let githubToken = Environment.environVarOrNone "GITHUB_TOKEN" + +let nugetToken = Environment.environVarOrNone "NUGET_TOKEN" + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + + +let isRelease (targets : Target list) = + targets + |> Seq.map (fun t -> t.Name) + |> Seq.exists ((=) "PublishToNuGet") + +let invokeAsync f = async { f () } + +let configuration (targets : Target list) = + let defaultVal = if isRelease targets then "Release" else "Debug" + + match Environment.environVarOrDefault "CONFIGURATION" defaultVal with + | "Debug" -> DotNet.BuildConfiguration.Debug + | "Release" -> DotNet.BuildConfiguration.Release + | config -> DotNet.BuildConfiguration.Custom config + +let failOnBadExitAndPrint (p : ProcessResult) = + if p.ExitCode <> 0 then + p.Errors |> Seq.iter Trace.traceError + + failwithf "failed with exitcode %d" p.ExitCode + +let isPublishToGitHub ctx = ctx.Context.FinalTarget = "PublishToGitHub" + +let isCI = lazy environVarAsBoolOrDefault "CI" false + +// CI Servers can have bizarre failures that have nothing to do with your code +let rec retryIfInCI times fn = + match isCI.Value with + | true -> + if times > 1 then + try + fn () + with _ -> + retryIfInCI (times - 1) fn + else + fn () + | _ -> fn () + +let failOnWrongBranch () = + if Git.Information.getBranchName "" <> releaseBranch then + failwithf "Not on %s. If you want to release please switch to this branch." releaseBranch + + +module dotnet = + let watch cmdParam program args = DotNet.exec cmdParam (sprintf "watch %s" program) args + + let run cmdParam args = DotNet.exec cmdParam "run" args + + let tool optionConfig command args = + DotNet.exec optionConfig (sprintf "%s" command) args + |> failOnBadExitAndPrint + + let reportgenerator optionConfig args = tool optionConfig "reportgenerator" args + + let sourcelink optionConfig args = tool optionConfig "sourcelink" args + + let fcswatch optionConfig args = tool optionConfig "fcswatch" args + + let fsharpAnalyzer optionConfig args = tool optionConfig "fsharp-analyzers" args + + let fantomas args = DotNet.exec id "fantomas" args + +module FSharpAnalyzers = + type Arguments = + | Project of string + | Analyzers_Path of string + | Fail_On_Warnings of string list + | Ignore_Files of string list + | Verbose + + interface IArgParserTemplate with + member s.Usage = "" + + +module DocsTool = + let quoted s = $"\"%s{s}\"" + + let fsDocsDotnetOptions (o : DotNet.Options) = { o with WorkingDirectory = rootDirectory } + + let fsDocsBuildParams configuration (p : Fsdocs.BuildCommandParams) = { + p with + Clean = Some true + Input = Some (quoted docsSrcDir) + Output = Some (quoted docsDir) + Eval = Some true + Projects = Some (Seq.map quoted (!!srcGlob)) + Properties = Some ($"Configuration=%s{configuration}") + Parameters = + Some [ + // https://fsprojects.github.io/FSharp.Formatting/content.html#Templates-and-Substitutions + "root", quoted $"{documentationRootUrl}/" + "fsdocs-collection-name", quoted productName + "fsdocs-repository-branch", quoted releaseBranch + "fsdocs-package-version", quoted latestEntry.NuGetVersion + "fsdocs-readme-link", quoted (READMElink.ToString ()) + "fsdocs-release-notes-link", quoted (CHANGELOGlink.ToString ()) + "fsdocs-logo-src", + quoted ( + "https://raw.githubusercontent.com/fsprojects/FSharp.Collections.Immutable/refs/heads/main/docsSrc/content/logo.png" + ) + ] + Strict = Some true + } + + let cleanDocsCache () = Fsdocs.cleanCache rootDirectory + + let build (configuration) = Fsdocs.build fsDocsDotnetOptions (fsDocsBuildParams configuration) + + + let watch (configuration) = + let buildParams bp = + let bp = + Option.defaultValue Fsdocs.BuildCommandParams.Default bp + |> fsDocsBuildParams configuration + + { bp with Output = Some watchDocsDir; Strict = None } + + Fsdocs.watch fsDocsDotnetOptions (fun p -> { p with BuildCommandParams = Some (buildParams p.BuildCommandParams) }) + +let allReleaseChecks () = failOnWrongBranch () +//Changelog.failOnEmptyChangelog latestEntry + + +let failOnLocalBuild () = + if not isCI.Value then + failwith "Not on CI. If you want to publish, please use CI." + +let failOnCIBuild () = + if isCI.Value then + failwith "On CI. If you want to run this target, please use a local build." + +let allPublishChecks () = failOnLocalBuild () +//Changelog.failOnEmptyChangelog latestEntry + +//----------------------------------------------------------------------------- +// Target Implementations +//----------------------------------------------------------------------------- + +/// So we don't require always being on the latest MSBuild.StructuredLogger +let disableBinLog (p : MSBuild.CliArguments) = { p with DisableInternalBinLog = true } + +let clean _ = + [ "bin"; "temp"; distDir; coverageReportDir; testResultsDir ] + |> Shell.cleanDirs + + !!srcGlob ++ testsGlob + |> Seq.collect (fun p -> + [ "bin"; "obj" ] + |> Seq.map (fun sp -> IO.Path.GetDirectoryName p sp) + ) + |> Shell.cleanDirs + +let dotnetRestore _ = + [ sln ] + |> Seq.map (fun dir -> + fun () -> + let args = [] |> String.concat " " + + DotNet.restore + (fun c -> { + c with + MSBuildParams = disableBinLog c.MSBuildParams + Common = c.Common |> DotNet.Options.withCustomParams (Some (args)) + }) + dir + ) + |> Seq.iter (retryIfInCI 10) + +let dotnetToolRestore _ = + let result = + fun () -> DotNet.exec id "tool" "restore" + |> (retryIfInCI 10) + + + if not result.OK then + failwithf "Failed to restore .NET tools: %A" result.Errors + +let updateChangelog ctx = + latestEntry <- + if not <| isPublishToGitHub ctx then + Changelog.updateChangelog changelogPath changelog gitHubRepoUrl ctx + elif Seq.isEmpty changelog.Entries then + latestEntry + else + let latest = changelog.LatestEntry + let semVer = { + latest.SemVer with + Original = None + Patch = latest.SemVer.Patch + 1u + PreRelease = PreRelease.TryParse "ci" + } + { + latest with + SemVer = semVer + NuGetVersion = semVer.AsString + AssemblyVersion = semVer.AsString + } + +let revertChangelog _ = + if String.isNotNullOrEmpty Changelog.changelogBackupFilename then + Changelog.changelogBackupFilename + |> Shell.copyFile changelogPath + +let deleteChangelogBackupFile _ = + if String.isNotNullOrEmpty Changelog.changelogBackupFilename then + Shell.rm Changelog.changelogBackupFilename + +let getPackageVersionProperty publishToGitHub = + if publishToGitHub then + let runId = Environment.environVar "GITHUB_RUN_ID" + $"/p:PackageVersion=%s{latestEntry.NuGetVersion}-%s{runId}" + else + $"/p:PackageVersion=%s{latestEntry.NuGetVersion}" + +let dotnetBuild ctx = + + let publishToGitHub = isPublishToGitHub ctx + + let args = [ getPackageVersionProperty publishToGitHub; "--no-restore" ] + + DotNet.build + (fun c -> { + c with + Configuration = configuration (ctx.Context.AllExecutingTargets) + Common = c.Common |> DotNet.Options.withAdditionalArgs args + MSBuildParams = { + (disableBinLog c.MSBuildParams) with + Properties = [ + if publishToGitHub then + ("DebugType", "embedded") + ("EmbedAllSources", "true") + ] + } + }) + sln + +let fsharpAnalyzers _ = + let argParser = ArgumentParser.Create (programName = "fsharp-analyzers") + + !!srcGlob + |> Seq.iter (fun proj -> + let args = + [ + FSharpAnalyzers.Analyzers_Path (rootDirectory "packages/analyzers") + FSharpAnalyzers.Arguments.Project proj + FSharpAnalyzers.Arguments.Fail_On_Warnings [ "BDH0002" ] + FSharpAnalyzers.Arguments.Ignore_Files [ "*AssemblyInfo.fs" ] + FSharpAnalyzers.Verbose + ] + |> argParser.PrintCommandLineArgumentsFlat + + dotnet.fsharpAnalyzer id args + ) + +let dotnetTest ctx = + // Create test results directory if it doesn't exist + Directory.create testResultsDir + + let args = [ + "--no-build" + if enableCodeCoverage then + "--collect:\"Code Coverage\"" + "--results-directory" + testResultsDir + "--logger:trx" // Enable TRX report generation + ] + + DotNet.test + (fun c -> { + c with + MSBuildParams = disableBinLog c.MSBuildParams + Configuration = configuration (ctx.Context.AllExecutingTargets) + Common = c.Common |> DotNet.Options.withAdditionalArgs args + }) + sln + +let generateCoverageReport _ = + + let coverageFiles = !!(testResultsDir "*/coverage.cobertura.xml") + + let sourceDirs = !!srcGlob |> Seq.map Path.getDirectory |> String.concat ";" + + let independentArgs = [ + sprintf "-reports:\"%s\"" (coverageFiles |> String.concat ";") + sprintf "-targetdir:\"%s\"" coverageReportDir + // Add source dir + sprintf "-sourcedirs:\"%s\"" sourceDirs + // Ignore test assemblies + sprintf "-assemblyfilters:\"%s\"" "-*.Tests" + // Generate HTML and Cobertura reports + sprintf "-reporttypes:%s" "Html;Cobertura" + ] + + let args = independentArgs |> String.concat " " + + dotnet.reportgenerator id args + +let showCoverageReport _ = + failOnCIBuild () + + coverageReportDir "index.html" + |> Command.ShellCommand + |> CreateProcess.fromCommand + |> Proc.start + |> ignore + + +let watchTests _ = + !!testsGlob + |> Seq.map (fun proj -> + fun () -> + dotnet.watch + (fun opt -> + opt + |> DotNet.Options.withWorkingDirectory (IO.Path.GetDirectoryName proj) + ) + "test" + "" + |> ignore + ) + |> Seq.iter (invokeAsync >> Async.Catch >> Async.Ignore >> Async.Start) + + printfn "Press Ctrl+C (or Ctrl+Break) to stop..." + + let cancelEvent = + Console.CancelKeyPress + |> Async.AwaitEvent + |> Async.RunSynchronously + + cancelEvent.Cancel <- true + +let generateAssemblyInfo _ = + + let (|Fsproj|Csproj|Vbproj|) (projFileName : string) = + match projFileName with + | f when f.EndsWith ("fsproj") -> Fsproj + | f when f.EndsWith ("csproj") -> Csproj + | f when f.EndsWith ("vbproj") -> Vbproj + | _ -> failwith (sprintf "Project file %s not supported. Unknown project type." projFileName) + + let releaseChannel = + match latestEntry.SemVer.PreRelease with + | Some pr -> pr.Name + | _ -> "release" + + let getAssemblyInfoAttributes projectName = [ + AssemblyInfo.Title (projectName) + AssemblyInfo.Product productName + AssemblyInfo.Version latestEntry.AssemblyVersion + AssemblyInfo.Metadata ("ReleaseDate", latestEntry.Date.Value.ToString ("o")) + AssemblyInfo.FileVersion latestEntry.AssemblyVersion + AssemblyInfo.InformationalVersion latestEntry.AssemblyVersion + AssemblyInfo.Metadata ("ReleaseChannel", releaseChannel) + AssemblyInfo.Metadata ("GitHash", Git.Information.getCurrentSHA1 (null)) + ] + + let getProjectDetails (projectPath : string) = + let projectName = IO.Path.GetFileNameWithoutExtension (projectPath) + + (projectPath, projectName, IO.Path.GetDirectoryName (projectPath), (getAssemblyInfoAttributes projectName)) + + !!srcGlob + |> Seq.map getProjectDetails + |> Seq.iter (fun (projFileName, _, folderName, attributes) -> + match projFileName with + | Fsproj -> AssemblyInfoFile.createFSharp (folderName "AssemblyInfo.fs") attributes + | Csproj -> AssemblyInfoFile.createCSharp ((folderName "Properties") "AssemblyInfo.cs") attributes + | Vbproj -> AssemblyInfoFile.createVisualBasic ((folderName "My Project") "AssemblyInfo.vb") attributes + ) + +let dotnetPack ctx = + // Get release notes with properly-linked version number + let releaseNotes = Changelog.mkReleaseNotes changelog latestEntry gitHubRepoUrl + + let args = [ getPackageVersionProperty (isPublishToGitHub ctx); $"/p:PackageReleaseNotes=\"{releaseNotes}\"" ] + + DotNet.pack + (fun c -> { + c with + MSBuildParams = disableBinLog c.MSBuildParams + Configuration = configuration (ctx.Context.AllExecutingTargets) + OutputPath = Some distDir + Common = c.Common |> DotNet.Options.withAdditionalArgs args + }) + sln + +let sourceLinkTest _ = + !!distGlob + |> Seq.iter (fun nupkg -> dotnet.sourcelink id $"test %s{nupkg}") + +type PushSource = + | NuGet + | GitHub + +let publishTo (source : PushSource) _ = + allPublishChecks () + + distGlob + |> DotNet.nugetPush (fun o -> { + o with + Common = { + o.Common with + WorkingDirectory = "dist" + CustomParams = Some "--skip-duplicate" + } + PushParams = { + o.PushParams with + // TODO: Uncomment when migrated to F# 9 + //NoSymbols = source.IsGitHub + Source = + match source with + | NuGet -> Some "nuget.org" + | GitHub -> Some "github.com" + ApiKey = + match source with + | NuGet -> nugetToken + | GitHub -> githubToken + } + }) + +let gitRelease _ = + allReleaseChecks () + + let releaseNotesGitCommitFormat = latestEntry.ToString () + + Git.Staging.stageFile "" (rootDirectory "CHANGELOG.md") + |> ignore + + !!(rootDirectory "src/**/AssemblyInfo.fs") + ++ (rootDirectory "tests/**/AssemblyInfo.fs") + |> Seq.iter (Git.Staging.stageFile "" >> ignore) + + let msg = $"Bump version to `%s{latestEntry.NuGetVersion}`\n\n%s{releaseNotesGitCommitFormat}" + + Git.Commit.exec "" msg + + Target.deactivateBuildFailure "RevertChangelog" + + Git.Branches.push "" + + let tag = Changelog.tagFromVersionNumber latestEntry.NuGetVersion + + Git.Branches.tag "" tag + Git.Branches.pushTag "" "origin" tag + +let githubRelease _ = + allPublishChecks () + + let token = + match githubToken with + | Some s -> s + | _ -> failwith "please set the `GITHUB_TOKEN` environment variable to a github personal access token with repo access." + + let files = !!distGlob + // Get release notes with properly-linked version number + let releaseNotes = Changelog.mkReleaseNotes changelog latestEntry gitHubRepoUrl + + GitHub.createClientWithToken token + |> GitHub.draftNewRelease + gitOwner + gitRepoName + (Changelog.tagFromVersionNumber latestEntry.NuGetVersion) + (latestEntry.SemVer.PreRelease <> None) + (releaseNotes |> Seq.singleton) + |> GitHub.uploadFiles files + |> GitHub.publishDraft + |> Async.RunSynchronously + +let formatCode _ = + let result = dotnet.fantomas $"{rootDirectory}" + + if not result.OK then + printfn "Errors while formatting all files: %A" result.Messages + +let checkFormatCode ctx = + let result = dotnet.fantomas $"{rootDirectory} --check" + + if result.ExitCode = 0 then + Trace.log "No files need formatting" + elif result.ExitCode = 99 then + failwith "Some files need formatting, check output for more info" + else + Trace.logf "Errors while formatting: %A" result.Errors + + +let cleanDocsCache _ = DocsTool.cleanDocsCache () + +let buildDocs ctx = + let configuration = configuration (ctx.Context.AllExecutingTargets) + DocsTool.build (string configuration) + +let watchDocs ctx = + let configuration = configuration (ctx.Context.AllExecutingTargets) + DocsTool.watch (string configuration) + + +let initTargets (ctx : Context.FakeExecutionContext) = + BuildServer.install [ GitHubActions.Installer ] + + let isPublishToGitHub = + ctx.Arguments + |> Seq.pairwise + |> Seq.exists (fun (arg, value) -> + (String.Equals (arg, "-t", StringComparison.OrdinalIgnoreCase) + || String.Equals (arg, "--target", StringComparison.OrdinalIgnoreCase)) + && String.Equals (value, "PublishToGitHub", StringComparison.OrdinalIgnoreCase) + ) + + /// Defines a dependency - y is dependent on x. Finishes the chain. + let (==>!) x y = x ==> y |> ignore + + /// Defines a soft dependency. x must run before y, if it is present, but y does not require x to be run. Finishes the chain. + let (?=>!) x y = x ?=> y |> ignore + //----------------------------------------------------------------------------- + // Hide Secrets in Logger + //----------------------------------------------------------------------------- + Option.iter (TraceSecrets.register "") githubToken + Option.iter (TraceSecrets.register "") nugetToken + //----------------------------------------------------------------------------- + // Target Declaration + //----------------------------------------------------------------------------- + + Target.create "Clean" clean + Target.create "DotnetRestore" dotnetRestore + Target.create "DotnetToolRestore" dotnetToolRestore + Target.create "UpdateChangelog" updateChangelog + Target.createBuildFailure "RevertChangelog" revertChangelog // Do NOT put this in the dependency chain + Target.createFinal "DeleteChangelogBackupFile" deleteChangelogBackupFile // Do NOT put this in the dependency chain + Target.create "DotnetBuild" dotnetBuild + Target.create "FSharpAnalyzers" fsharpAnalyzers + Target.create "DotnetTest" dotnetTest + Target.create "GenerateCoverageReport" generateCoverageReport + Target.create "ShowCoverageReport" showCoverageReport + Target.create "WatchTests" watchTests + Target.create "GenerateAssemblyInfo" generateAssemblyInfo + Target.create "DotnetPack" dotnetPack + Target.create "SourceLinkTest" sourceLinkTest + Target.create "PublishToNuGet" (publishTo NuGet) + Target.create "PublishToGitHub" (publishTo GitHub) + Target.create "GitRelease" gitRelease + Target.create "GitHubRelease" githubRelease + Target.create "FormatCode" formatCode + Target.create "CheckFormatCode" checkFormatCode + Target.create "Release" ignore // For local + Target.create "Publish" ignore //For CI + Target.create "CleanDocsCache" cleanDocsCache + Target.create "BuildDocs" buildDocs + Target.create "WatchDocs" watchDocs + + //----------------------------------------------------------------------------- + // Target Dependencies + //----------------------------------------------------------------------------- + + + // Only call Clean if DotnetPack was in the call chain + // Ensure Clean is called before DotnetRestore + "Clean" ?=>! "DotnetRestore" + + "Clean" ==>! "DotnetPack" + + // Only call GenerateAssemblyInfo if GitRelease was in the call chain + // Ensure GenerateAssemblyInfo is called after DotnetRestore and before DotnetBuild + "DotnetRestore" ?=>! "GenerateAssemblyInfo" + + "GenerateAssemblyInfo" ?=>! "DotnetBuild" + + // Ensure UpdateChangelog is called after DotnetRestore + "DotnetRestore" ?=>! "UpdateChangelog" + + "UpdateChangelog" ?=>! "GenerateAssemblyInfo" + + "CleanDocsCache" ==>! "BuildDocs" + + "DotnetBuild" ?=>! "BuildDocs" + + "DotnetBuild" ==>! "BuildDocs" + + + "DotnetBuild" ==>! "WatchDocs" + + "DotnetTest" ==> "GenerateCoverageReport" + ==>! "ShowCoverageReport" + + "UpdateChangelog" + ==> "GenerateAssemblyInfo" + ==> "GitRelease" + ==>! "Release" + + + "DotnetRestore" =?> ("CheckFormatCode", isCI.Value) + ==> "DotnetBuild" + ==> "DotnetTest" + ==> "DotnetPack" + ==> "PublishToNuGet" + ==> "GitHubRelease" + ==>! "Publish" + + "DotnetRestore" + =?> ("CheckFormatCode", isCI.Value) + =?> ("GenerateAssemblyInfo", isPublishToGitHub) + ==> "DotnetBuild" + ==> "DotnetTest" + ==> "DotnetPack" + ==>! "PublishToGitHub" + + "DotnetRestore" ==>! "WatchTests" + + //"DotnetToolRestore" ?=>! "DotnetRestore" + "DotnetToolRestore" ==>! "BuildDocs" + "DotnetToolRestore" ?=>! "CheckFormatCode" + "DotnetToolRestore" ?=>! "FormatCode" + +//----------------------------------------------------------------------------- +// Target Start +//----------------------------------------------------------------------------- +[] +let main argv = + + let ctx = + argv + |> Array.toList + |> Context.FakeExecutionContext.Create false "build.fsx" + + Context.setExecutionContext (Context.RuntimeContext.Fake ctx) + initTargets ctx + Target.runOrDefaultWithArguments "DotnetPack" + + 0 // return an integer exit code diff --git a/build/build.fsproj b/build/build.fsproj new file mode 100644 index 0000000..a12452d --- /dev/null +++ b/build/build.fsproj @@ -0,0 +1,40 @@ + + + Exe + net8.0 + 3390;$(WarnOn) + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docsSrc/Explanations/Background.md b/docsSrc/Explanations/Background.md new file mode 100644 index 0000000..a1525b3 --- /dev/null +++ b/docsSrc/Explanations/Background.md @@ -0,0 +1,39 @@ +--- +title: Background +category: Explanations +categoryindex: 3 +index: 1 +--- + +# Background + +## System.Collections.Immutable and F# + +[System.Collections.Immutable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable) is a high-performance .NET library providing a suite of immutable collection types, such as arrays, lists, stacks, queues, dictionaries, and sets. These collections are designed for scenarios where data structures need to be shared safely across threads or require non-destructive updates, making them ideal for functional programming patterns. + +While F# has its own built-in immutable collections, System.Collections.Immutable collections are engineered for performance and scalability, especially in concurrent and multi-threaded environments. They use advanced algorithms to minimize memory allocations and maximize efficiency when creating modified copies of collections. + +**FSharp.Collections.Immutable** provides idiomatic F# bindings for these .NET collections, allowing F# developers to leverage their performance and safety benefits with a familiar F#-style API. + +## Why Use System.Collections.Immutable in F#? + +- **Performance**: Optimized for fast structural sharing and minimal memory overhead compared to standard F# collections in certain scenarios. +- **Thread Safety**: Immutable by design, making them safe for concurrent access without locks. +- **Rich API**: Feature-rich and consistent with .NET ecosystem standards. +- **Interoperability**: Seamless integration with C# and other .NET languages. + +## Available Collections + +- `FlatList` (`ImmutableArray`) +- `ImmutableList` +- `Stack` (`ImmutableStack`) +- `Queue` (`ImmutableQueue`) +- `HashMap` (`ImmutableDictionary`) +- `SortedMap` (`ImmutableSortedDictionary`) +- `HashSet` (`ImmutableHashSet`) +- `SortedSet` (`ImmutableSortedSet`) +- `IIndexedSeq` (`IReadOnlyList`) + +--- + +FSharp.Collections.Immutable enables F# developers to use these performant, thread-safe, and feature-rich immutable collections in a natural and idiomatic way. diff --git a/docsSrc/How_Tos/Doing_A_Thing.md b/docsSrc/How_Tos/Doing_A_Thing.md new file mode 100644 index 0000000..071e5c1 --- /dev/null +++ b/docsSrc/How_Tos/Doing_A_Thing.md @@ -0,0 +1,14 @@ +--- +title: How To do a first thing +category: How To Guides +categoryindex: 2 +index: 1 +--- + +# How To do a first thing + +The best way to use IObservable is to use one .Subscribe()-method which will take function parameters (which will be "injected" to the right place). + +Use Rx (or R3) when you need async events to communicate with each other, e.g.: +- Events, WebServices, Threads, Timers, AutoComplete, Drag & Drop, ... + diff --git a/docsSrc/How_Tos/Doing_Another_Thing.md b/docsSrc/How_Tos/Doing_Another_Thing.md new file mode 100644 index 0000000..03064b3 --- /dev/null +++ b/docsSrc/How_Tos/Doing_Another_Thing.md @@ -0,0 +1,9 @@ +--- +title: How To do a second thing +category: How To Guides +categoryindex: 2 +index: 2 +--- + +# How To do a second thing + diff --git a/docsSrc/Tutorials/Getting_Started.md b/docsSrc/Tutorials/Getting_Started.md new file mode 100644 index 0000000..eea423e --- /dev/null +++ b/docsSrc/Tutorials/Getting_Started.md @@ -0,0 +1,41 @@ +--- +title: Getting Started +category: Tutorials +categoryindex: 1 +index: 1 +--- + +# Getting Started + +## Installation + +First, add the NuGet package to your project: +``` +dotnet add package FSharp.Collections.Immutable +``` +## Basic Setup + +Here's a minimal example to get started: +``` F# +open FSharp.Collections.Immutable +``` + +``` F# +// Create an immutable list +let list1 = ImmutableList.ofSeq [1; 2; 3] + +// Create an immutable hash set +let set1 = HashSet.ofSeq ["a"; "b"] + +// Create an immutable dictionary +let dict1 = HashMap.ofSeq [KeyValuePair ("key1", 42); KeyValuePair ("key2", 100)] + + +printfn "List: %A" (list1 |> Seq.toList) +printfn "Set: %A" (set1 |> Seq.toList) +printfn "Dict: %A" (dict1 |> Seq.toList) +## Next Steps + +- Check out the [How-To Guides](../How_Tos/Doing_A_Thing.html) for common scenarios +- Read the [Background](../Explanations/Background.html) for deeper understanding +- Browse the [API Reference](../reference/index.html) for detailed documentation diff --git a/docsSrc/_menu-item_template.html b/docsSrc/_menu-item_template.html new file mode 100644 index 0000000..dc1b656 --- /dev/null +++ b/docsSrc/_menu-item_template.html @@ -0,0 +1 @@ +
  • {{fsdocs-menu-item-content}}
  • \ No newline at end of file diff --git a/docsSrc/_menu_template.html b/docsSrc/_menu_template.html new file mode 100644 index 0000000..066716c --- /dev/null +++ b/docsSrc/_menu_template.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/docsSrc/_template.html b/docsSrc/_template.html new file mode 100644 index 0000000..2e69546 --- /dev/null +++ b/docsSrc/_template.html @@ -0,0 +1,166 @@ + + + + + + {{fsdocs-page-title}} + + + + + + + + + + + + + + + + {{fsdocs-watch-script}} + + + + + + +
    + {{fsdocs-content}} + {{fsdocs-tooltips}} +
    + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docsSrc/content/fsdocs-custom.css b/docsSrc/content/fsdocs-custom.css new file mode 100644 index 0000000..62b2801 --- /dev/null +++ b/docsSrc/content/fsdocs-custom.css @@ -0,0 +1,15 @@ +.fsharp-icon-logo { + width: 25px; + margin-top: -2px; + -webkit-filter: grayscale(100%) brightness(1) invert(1); /* Safari 6.0 - 9.0 */ + filter: grayscale(100%) brightness(1) invert(1); +} + + +body .navbar .dropdown-menu .active .bi { + display: block !important; +} + +nav.navbar .dropdown-item img.fsharp-icon-logo { + margin-right: 0px; +} diff --git a/docsSrc/content/fsdocs-dark.css b/docsSrc/content/fsdocs-dark.css new file mode 100644 index 0000000..001b653 --- /dev/null +++ b/docsSrc/content/fsdocs-dark.css @@ -0,0 +1,50 @@ +@import url('https://raw.githubusercontent.com/tonsky/FiraCode/fixed/distr/fira_code.css'); +@import url('https://fonts.googleapis.com/css2?family=Hind+Vadodara&family=Roboto+Mono&display=swap'); +/*-------------------------------------------------------------------------- + Formatting for page & standard document content +/*--------------------------------------------------------------------------*/ + +:root { + --fsdocs-text-color:#d1d1d1; + --fsdocs-pre-border-color: #000000; + --fsdocs-pre-border-color-top: #070707; + --fsdocs-pre-background-color: #1E1E1E; + --fsdocs-pre-color: #e2e2e2; + --fsdocs-table-pre-background-color: #1d1d1d; + --fsdocs-table-pre-color: #c9c9c9; + + --fsdocs-code-strings-color: #ea9a75; + --fsdocs-code-printf-color: #E0C57F; + --fsdocs-code-escaped-color: #EA8675; + --fsdocs-code-identifiers-color: var(--fsdocs-text-color); + --fsdocs-code-module-color: #43AEC6; + --fsdocs-code-reference-color: #6a8dd8; + --fsdocs-code-value-color: #43AEC6; + --fsdocs-code-interface-color: #43AEC6; + --fsdocs-code-typearg-color: #43AEC6; + --fsdocs-code-disposable-color: #2f798a; + --fsdocs-code-property-color: #43AEC6; + --fsdocs-code-punctuation-color: #43AEC6; + --fsdocs-code-punctuation2-color: #e1e1e1; + --fsdocs-code-function-color: #e1e1e1; + --fsdocs-code-function2-color: #43AEC6; + --fsdocs-code-activepattern-color: #4ec9b0; + --fsdocs-code-unioncase-color: #4ec9b0; + --fsdocs-code-enumeration-color: #4ec9b0; + --fsdocs-code-keywords-color: #2248c4; + --fsdocs-code-comment-color: #329215; + --fsdocs-code-operators-color: #af75c1; + --fsdocs-code-numbers-color: #96C71D; + --fsdocs-code-linenumbers-color: #80b0b0; + --fsdocs-code-mutable-color: #997f0c; + --fsdocs-code-inactive-color: #808080; + --fsdocs-code-preprocessor-color: #af75c1; + --fsdocs-code-fsioutput-color: #808080; + --fsdocs-code-tooltip-color: #d1d1d1; +} + + +.fsdocs-source-link img { + -webkit-filter: grayscale(100%) brightness(1) invert(1); /* Safari 6.0 - 9.0 */ + filter: grayscale(100%) brightness(1) invert(1); +} \ No newline at end of file diff --git a/docsSrc/content/fsdocs-light.css b/docsSrc/content/fsdocs-light.css new file mode 100644 index 0000000..318ccd9 --- /dev/null +++ b/docsSrc/content/fsdocs-light.css @@ -0,0 +1,43 @@ +@import url('https://fonts.googleapis.com/css2?family=Hind+Vadodara&family=Roboto+Mono&display=swap'); +/*-------------------------------------------------------------------------- + Formatting for page & standard document content +/*--------------------------------------------------------------------------*/ + +:root { + --fsdocs-text-color:#262626; + --fsdocs-pre-border-color: #d8d8d8; + --fsdocs-pre-border-color-top: #e3e3e3; + --fsdocs-pre-background-color: #f3f4f7; + --fsdocs-pre-color: #8e0e2b; + --fsdocs-table-pre-background-color: #fff7ed; + --fsdocs-table-pre-color: #837b79; + + --fsdocs-code-strings-color: #dd1144; + --fsdocs-code-printf-color: #E0C57F; + --fsdocs-code-escaped-color: #EA8675; + --fsdocs-code-identifiers-color: var(--fsdocs-text-color); + --fsdocs-code-module-color: #009999; + --fsdocs-code-reference-color: #4974D1; + --fsdocs-code-value-color: #43AEC6; + --fsdocs-code-interface-color: #43AEC6; + --fsdocs-code-typearg-color: #43AEC6; + --fsdocs-code-disposable-color: #43AEC6; + --fsdocs-code-property-color: #43AEC6; + --fsdocs-code-punctuation-color: #43AEC6; + --fsdocs-code-punctuation2-color: #var(--fsdocs-text-color); + --fsdocs-code-function-color: #e1e1e1; + --fsdocs-code-function2-color: #990000; + --fsdocs-code-activepattern-color: #4ec9b0; + --fsdocs-code-unioncase-color: #4ec9b0; + --fsdocs-code-enumeration-color: #4ec9b0; + --fsdocs-code-keywords-color: #b68015; + --fsdocs-code-comment-color: #808080; + --fsdocs-code-operators-color: #af75c1; + --fsdocs-code-numbers-color: #009999; + --fsdocs-code-linenumbers-color: #80b0b0; + --fsdocs-code-mutable-color: #d1d1d1; + --fsdocs-code-inactive-color: #808080; + --fsdocs-code-preprocessor-color: #af75c1; + --fsdocs-code-fsioutput-color: #808080; + --fsdocs-code-tooltip-color: #d1d1d1; +} diff --git a/docsSrc/content/fsdocs-main.css b/docsSrc/content/fsdocs-main.css new file mode 100644 index 0000000..2ad1582 --- /dev/null +++ b/docsSrc/content/fsdocs-main.css @@ -0,0 +1,604 @@ +@import url('https://fonts.googleapis.com/css2?family=Hind+Vadodara&family=Roboto+Mono&display=swap'); +/*-------------------------------------------------------------------------- + Formatting for page & standard document content +/*--------------------------------------------------------------------------*/ + +body { + font-family: 'Hind Vadodara', sans-serif; + /* padding-top: 0px; + padding-bottom: 40px; +*/ +} + +blockquote { + margin: 0 1em 0 0.25em; + margin-top: 0px; + margin-right: 1em; + margin-bottom: 0px; + margin-left: 0.25em; + padding: 0 .75em 0 1em; + border-left: 1px solid #777; + border-right: 0px solid #777; +} + +/* Format the heading - nicer spacing etc. */ +.masthead { + overflow: hidden; +} + + .masthead .muted a { + text-decoration: none; + color: #999999; + } + + .masthead ul, .masthead li { + margin-bottom: 0px; + } + + .masthead .nav li { + margin-top: 15px; + font-size: 110%; + } + + .masthead h3 { + margin-top: 15px; + margin-bottom: 5px; + font-size: 170%; + } + +/*-------------------------------------------------------------------------- + Formatting fsdocs-content +/*--------------------------------------------------------------------------*/ + +/* Change font sizes for headings etc. */ +#fsdocs-content h1 { + margin: 30px 0px 15px 0px; + /* font-weight: 400; */ + font-size: 2rem; + letter-spacing: 1.78px; + line-height: 2.5rem; + font-weight: 400; +} + +#fsdocs-content h2 { + font-size: 1.6rem; + margin: 20px 0px 10px 0px; + font-weight: 400; +} + +#fsdocs-content h3 { + font-size: 1.2rem; + margin: 15px 0px 10px 0px; + font-weight: 400; +} + +#fsdocs-content hr { + margin: 0px 0px 20px 0px; +} + +#fsdocs-content li { + font-size: 1.0rem; + line-height: 1.375rem; + letter-spacing: 0.01px; + font-weight: 500; + margin: 0px 0px 15px 0px; +} + +#fsdocs-content p { + font-size: 1.0rem; + line-height: 1.375rem; + letter-spacing: 0.01px; + font-weight: 500; + color: var(--fsdocs-text-color);; +} + +#fsdocs-content a:not(.btn) { + color: #4974D1; +} +/* remove the default bootstrap bold on dt elements */ +#fsdocs-content dt { + font-weight: normal; +} + + + +/*-------------------------------------------------------------------------- + Formatting tables in fsdocs-content, using learn.microsoft.com tables +/*--------------------------------------------------------------------------*/ + +#fsdocs-content .table { + table-layout: auto; + width: 100%; + font-size: 0.875rem; +} + + #fsdocs-content .table caption { + font-size: 0.8rem; + font-weight: 600; + letter-spacing: 2px; + text-transform: uppercase; + padding: 1.125rem; + border-width: 0 0 1px; + border-style: solid; + border-color: #e3e3e3; + text-align: right; + } + + #fsdocs-content .table td, + #fsdocs-content .table th { + display: table-cell; + word-wrap: break-word; + padding: 0.75rem 1rem 0.75rem 0rem; + line-height: 1.5; + vertical-align: top; + border-top: 1px solid #e3e3e3; + border-right: 0; + border-left: 0; + border-bottom: 0; + border-style: solid; + } + + /* suppress the top line on inner lists such as tables of exceptions */ + #fsdocs-content .table .fsdocs-exception-list td, + #fsdocs-content .table .fsdocs-exception-list th { + border-top: 0 + } + + #fsdocs-content .table td p:first-child, + #fsdocs-content .table th p:first-child { + margin-top: 0; + } + + #fsdocs-content .table td.nowrap, + #fsdocs-content .table th.nowrap { + white-space: nowrap; + } + + #fsdocs-content .table td.is-narrow, + #fsdocs-content .table th.is-narrow { + width: 15%; + } + + #fsdocs-content .table th:not([scope='row']) { + border-top: 0; + border-bottom: 1px; + } + + #fsdocs-content .table > caption + thead > tr:first-child > td, + #fsdocs-content .table > colgroup + thead > tr:first-child > td, + #fsdocs-content .table > thead:first-child > tr:first-child > td { + border-top: 0; + } + + #fsdocs-content .table table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--box-shadow-light); + } + + #fsdocs-content .table.min { + width: unset; + } + + #fsdocs-content .table.is-left-aligned td:first-child, + #fsdocs-content .table.is-left-aligned th:first-child { + padding-left: 0; + } + + #fsdocs-content .table.is-left-aligned td:first-child a, + #fsdocs-content .table.is-left-aligned th:first-child a { + outline-offset: -0.125rem; + } + +@media screen and (max-width: 767px), screen and (min-resolution: 120dpi) and (max-width: 767.9px) { + #fsdocs-content .table.is-stacked-mobile td:nth-child(1) { + display: block; + width: 100%; + padding: 1rem 0; + } + + #fsdocs-content .table.is-stacked-mobile td:not(:nth-child(1)) { + display: block; + border-width: 0; + padding: 0 0 1rem; + } +} + +#fsdocs-content .table.has-inner-borders th, +#fsdocs-content .table.has-inner-borders td { + border-right: 1px solid #e3e3e3; +} + + #fsdocs-content .table.has-inner-borders th:last-child, + #fsdocs-content .table.has-inner-borders td:last-child { + border-right: none; + } + +.fsdocs-entity-list .fsdocs-entity-name { + width: 25%; + font-weight: bold; +} + +.fsdocs-member-list .fsdocs-member-usage { + width: 35%; +} + +/*-------------------------------------------------------------------------- + Formatting xmldoc sections in fsdocs-content +/*--------------------------------------------------------------------------*/ + +.fsdocs-xmldoc, .fsdocs-entity-xmldoc, .fsdocs-member-xmldoc { + font-size: 1.0rem; + line-height: 1.375rem; + letter-spacing: 0.01px; + font-weight: 500; + color: var(--fsdocs-text-color);; +} + +.fsdocs-xmldoc h1 { + font-size: 1.2rem; + margin: 10px 0px 0px 0px; +} + +.fsdocs-xmldoc h2 { + font-size: 1.2rem; + margin: 10px 0px 0px 0px; +} + +.fsdocs-xmldoc h3 { + font-size: 1.1rem; + margin: 10px 0px 0px 0px; +} + +/* #fsdocs-nav .searchbox { + margin-top: 30px; + margin-bottom: 30px; +} */ + +#fsdocs-nav img.logo{ + width:90%; + /* height:140px; */ + /* margin:10px 0px 0px 20px; */ + margin-top:40px; + border-style:none; +} + +#fsdocs-nav input{ + /* margin-left: 20px; */ + margin-right: 20px; + margin-top: 20px; + margin-bottom: 20px; + width: 93%; + -webkit-border-radius: 0; + border-radius: 0; +} + +#fsdocs-nav { + /* margin-left: -5px; */ + /* width: 90%; */ + font-size:0.95rem; +} + +#fsdocs-nav li.nav-header{ + /* margin-left: -5px; */ + /* width: 90%; */ + padding-left: 0; + color: var(--fsdocs-text-color);; + text-transform: none; + font-size:16px; + margin-top: 9px; + font-weight: bold; +} + +#fsdocs-nav a{ + padding-left: 0; + color: #6c6c6d; + /* margin-left: 5px; */ + /* width: 90%; */ +} + +/*-------------------------------------------------------------------------- + Formatting pre and code sections in fsdocs-content (code highlighting is + further below) +/*--------------------------------------------------------------------------*/ + +#fsdocs-content code { + /* font-size: 0.83rem; */ + font: 0.85rem 'Roboto Mono', monospace; + background-color: #f7f7f900; + border: 0px; + padding: 0px; + /* word-wrap: break-word; */ + /* white-space: pre; */ +} + +/* omitted */ +#fsdocs-content span.omitted { + background: #3c4e52; + border-radius: 5px; + color: #808080; + padding: 0px 0px 1px 0px; +} + +#fsdocs-content pre .fssnip code { + font: 0.86rem 'Roboto Mono', monospace; +} + +#fsdocs-content table.pre, +#fsdocs-content pre.fssnip, +#fsdocs-content pre { + line-height: 13pt; + border: 0px solid var(--fsdocs-pre-border-color); + border-top: 0px solid var(--fsdocs-pre-border-color-top); + border-collapse: separate; + white-space: pre; + font: 0.86rem 'Roboto Mono', monospace; + width: 100%; + margin: 10px 0px 20px 0px; + background-color: var(--fsdocs-pre-background-color); + padding: 10px; + border-radius: 5px; + color: var(--fsdocs-pre-color); + max-width: none; + box-sizing: border-box; +} + +#fsdocs-content pre.fssnip code { + font: 0.86rem 'Roboto Mono', monospace; + font-weight: 600; +} + +#fsdocs-content table.pre { + background-color: var(--fsdocs-table-pre-background-color);; +} + +#fsdocs-content table.pre pre { + padding: 0px; + margin: 0px; + border-radius: 0px; + width: 100%; + background-color: var(--fsdocs-table-pre-background-color); + color: var(--fsdocs-table-pre-color); +} + +#fsdocs-content table.pre td { + padding: 0px; + white-space: normal; + margin: 0px; + width: 100%; +} + +#fsdocs-content table.pre td.lines { + width: 30px; +} + + +#fsdocs-content pre { + word-wrap: inherit; +} + +.fsdocs-example-header { + font-size: 1.0rem; + line-height: 1.375rem; + letter-spacing: 0.01px; + font-weight: 700; + color: var(--fsdocs-text-color);; +} + +/*-------------------------------------------------------------------------- + Formatting github source links +/*--------------------------------------------------------------------------*/ + +.fsdocs-source-link { + float: right; + text-decoration: none; +} + + .fsdocs-source-link img { + border-style: none; + margin-left: 10px; + width: auto; + height: 1.4em; + } + + .fsdocs-source-link .hover { + display: none; + } + + .fsdocs-source-link:hover .hover { + display: block; + } + + .fsdocs-source-link .normal { + display: block; + } + + .fsdocs-source-link:hover .normal { + display: none; + } + +/*-------------------------------------------------------------------------- + Formatting logo +/*--------------------------------------------------------------------------*/ + +#fsdocs-logo { + width:40px; + height:40px; + margin:10px 0px 0px 0px; + border-style:none; +} + +/*-------------------------------------------------------------------------- + +/*--------------------------------------------------------------------------*/ + +#fsdocs-content table.pre pre { + padding: 0px; + margin: 0px; + border: none; +} + +/*-------------------------------------------------------------------------- + Remove formatting from links +/*--------------------------------------------------------------------------*/ + +#fsdocs-content h1 a, +#fsdocs-content h1 a:hover, +#fsdocs-content h1 a:focus, +#fsdocs-content h2 a, +#fsdocs-content h2 a:hover, +#fsdocs-content h2 a:focus, +#fsdocs-content h3 a, +#fsdocs-content h3 a:hover, +#fsdocs-content h3 a:focus, +#fsdocs-content h4 a, +#fsdocs-content h4 a:hover, #fsdocs-content +#fsdocs-content h4 a:focus, +#fsdocs-content h5 a, +#fsdocs-content h5 a:hover, +#fsdocs-content h5 a:focus, +#fsdocs-content h6 a, +#fsdocs-content h6 a:hover, +#fsdocs-content h6 a:focus { + color: var(--fsdocs-text-color);; + text-decoration: none; + text-decoration-style: none; + /* outline: none */ +} + +/*-------------------------------------------------------------------------- + Formatting for F# code snippets +/*--------------------------------------------------------------------------*/ + +.fsdocs-param-name, +.fsdocs-return-name, +.fsdocs-param { + font-weight: 900; + font-size: 0.85rem; + font-family: 'Roboto Mono', monospace; +} +/* strings --- and stlyes for other string related formats */ +#fsdocs-content span.s { + color: var(--fsdocs-code-strings-color); +} +/* printf formatters */ +#fsdocs-content span.pf { + color: var(--fsdocs-code-printf-color); +} +/* escaped chars */ +#fsdocs-content span.e { + color: var(--fsdocs-code-escaped-color); +} + +/* identifiers --- and styles for more specific identifier types */ +#fsdocs-content span.id { + color: var(--fsdocs-identifiers-color);; +} +/* module */ +#fsdocs-content span.m { + color:var(--fsdocs-code-module-color); +} +/* reference type */ +#fsdocs-content span.rt { + color: var(--fsdocs-code-reference-color); +} +/* value type */ +#fsdocs-content span.vt { + color: var(--fsdocs-code-value-color); +} +/* interface */ +#fsdocs-content span.if { + color: var(--fsdocs-code-interface-color); +} +/* type argument */ +#fsdocs-content span.ta { + color: var(--fsdocs-code-typearg-color); +} +/* disposable */ +#fsdocs-content span.d { + color: var(--fsdocs-code-disposable-color); +} +/* property */ +#fsdocs-content span.prop { + color: var(--fsdocs-code-property-color); +} +/* punctuation */ +#fsdocs-content span.p { + color: var(--fsdocs-code-punctuation-color); +} +#fsdocs-content span.pn { + color: var(--fsdocs-code-punctuation2-color); +} +/* function */ +#fsdocs-content span.f { + color: var(--fsdocs-code-function-color); +} +#fsdocs-content span.fn { + color: var(--fsdocs-code-function2-color); +} +/* active pattern */ +#fsdocs-content span.pat { + color: var(--fsdocs-code-activepattern-color); +} +/* union case */ +#fsdocs-content span.u { + color: var(--fsdocs-code-unioncase-color); +} +/* enumeration */ +#fsdocs-content span.e { + color: var(--fsdocs-code-enumeration-color); +} +/* keywords */ +#fsdocs-content span.k { + color: var(--fsdocs-code-keywords-color); + /* font-weight: bold; */ +} +/* comment */ +#fsdocs-content span.c { + color: var(--fsdocs-code-comment-color); + font-weight: 400; + font-style: italic; +} +/* operators */ +#fsdocs-content span.o { + color: var(--fsdocs-code-operators-color); +} +/* numbers */ +#fsdocs-content span.n { + color: var(--fsdocs-code-numbers-color); +} +/* line number */ +#fsdocs-content span.l { + color: var(--fsdocs-code-linenumbers-color); +} +/* mutable var or ref cell */ +#fsdocs-content span.v { + color: var(--fsdocs-code-mutable-color); + font-weight: bold; +} +/* inactive code */ +#fsdocs-content span.inactive { + color: var(--fsdocs-code-inactive-color); +} +/* preprocessor */ +#fsdocs-content span.prep { + color: var(--fsdocs-code-preprocessor-color); +} +/* fsi output */ +#fsdocs-content span.fsi { + color: var(--fsdocs-code-fsioutput-color); +} + +/* tool tip */ +div.fsdocs-tip { + background: #475b5f; + border-radius: 4px; + font: 0.85rem 'Roboto Mono', monospace; + padding: 6px 8px 6px 8px; + display: none; + color: var(--fsdocs-code-tooltip-color); + pointer-events: none; +} + + div.fsdocs-tip code { + color: var(--fsdocs-code-tooltip-color); + font: 0.85rem 'Roboto Mono', monospace; + } diff --git a/docsSrc/content/logo.pdn b/docsSrc/content/logo.pdn new file mode 100644 index 0000000..c1ffec0 Binary files /dev/null and b/docsSrc/content/logo.pdn differ diff --git a/docsSrc/content/logo.png b/docsSrc/content/logo.png new file mode 100644 index 0000000..1d43c1f Binary files /dev/null and b/docsSrc/content/logo.png differ diff --git a/docsSrc/content/logo.svg b/docsSrc/content/logo.svg new file mode 100644 index 0000000..0eb629c --- /dev/null +++ b/docsSrc/content/logo.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docsSrc/content/navbar-fixed-left.css b/docsSrc/content/navbar-fixed-left.css new file mode 100644 index 0000000..28e4c57 --- /dev/null +++ b/docsSrc/content/navbar-fixed-left.css @@ -0,0 +1,91 @@ +/* CSS for Bootstrap 5 Fixed Left Sidebar Navigation */ + + + +@media (min-width: 992px){ + + body { + padding-left: 300px; + padding-right: 60px; + } + + #fsdocs-logo { + width:140px; + height:140px; + margin:10px 0px 0px 0px; + border-style:none; + } + + + nav.navbar { + position: fixed; + left: 0; + width: 300px; + bottom: 0; + top: 0; + overflow-y: auto; + overflow-x: hidden; + display: block; + border-right: 1px solid #cecece; + } + + nav.navbar>.container { + flex-direction: column; + padding: 0; + } + + nav.navbar .navbar-nav { + flex-direction: column; + } + nav.navbar .navbar-collapse { + width: 100%; + } + + nav.navbar .navbar-nav { + width: 100%; + } + + nav.navbar .navbar-nav .dropdown-menu { + position: static; + display: block; + } + + nav.navbar .dropdown { + margin-bottom: 5px; + font-size: 14px; + } + + nav.navbar .dropdown-item { + white-space: normal; + font-size: 14px; + vertical-align: middle; + } + + nav.navbar .dropdown-item img { + margin-right: 5px; + } + + nav.navbar .dropdown-toggle { + cursor: default; + } + + nav.navbar .dropdown-menu { + border-radius: 0; + border-left: 0; + border-right: 0; + } + + nav.navbar .dropdown-toggle:not(#bd-theme)::after { + display: none; + } + + .dropdown-menu[data-bs-popper] { + top: auto; + left: auto; + margin-top: auto; + } + + .nav-link:focus, .nav-link:hover { + color: auto; + } +} \ No newline at end of file diff --git a/docsSrc/content/theme-toggle.js b/docsSrc/content/theme-toggle.js new file mode 100644 index 0000000..c208c08 --- /dev/null +++ b/docsSrc/content/theme-toggle.js @@ -0,0 +1,68 @@ +/*! + * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors + * Licensed under the Creative Commons Attribution 3.0 Unported License. + */ + +(() => { + 'use strict' + + const storedTheme = localStorage.getItem('theme') + + const getPreferredTheme = () => { + if (storedTheme) { + return storedTheme + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + } + + const setTheme = function (theme) { + const fsdocsTheme = document.getElementById("fsdocs-theme") + const re = /fsdocs-.*.css/ + if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) { + document.documentElement.setAttribute('data-bs-theme', 'dark') + fsdocsTheme.setAttribute("href", fsdocsTheme.getAttribute("href").replace(re,"fsdocs-dark.css")) + + } else { + document.documentElement.setAttribute('data-bs-theme', theme) + + fsdocsTheme.setAttribute("href", fsdocsTheme.getAttribute("href").replace(re,`fsdocs-${theme}.css`)) + } + } + + setTheme(getPreferredTheme()) + + const showActiveTheme = theme => { + const activeThemeIcon = document.getElementById('theme-icon-active') + const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) + const svgOfActiveBtn = btnToActive.querySelector('i').getAttribute('class') + + document.querySelectorAll('[data-bs-theme-value]').forEach(element => { + element.classList.remove('active') + }) + + btnToActive.classList.add('active') + activeThemeIcon.setAttribute('class', svgOfActiveBtn) + } + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + if (storedTheme !== 'light' || storedTheme !== 'dark') { + setTheme(getPreferredTheme()) + } + }) + + window.addEventListener('DOMContentLoaded', () => { + showActiveTheme(getPreferredTheme()) + + document.querySelectorAll('[data-bs-theme-value]') + .forEach(toggle => { + toggle.addEventListener('click', () => { + const theme = toggle.getAttribute('data-bs-theme-value') + localStorage.setItem('theme', theme) + setTheme(theme) + showActiveTheme(theme) + }) + }) + }) +})() \ No newline at end of file diff --git a/docsSrc/index.md b/docsSrc/index.md new file mode 100644 index 0000000..a58115a --- /dev/null +++ b/docsSrc/index.md @@ -0,0 +1,79 @@ +# FSharp.Collections.Immutable + +F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable). + +**FSharp.Collections.Immutable** provides idiomatic F# wrappers for the .NET immutable collections, making it easy to use persistent data structures in F# code. + +## Key Features + +- `FlatList` (`ImmutableArray`) +- `ImmutableList` +- `Stack` (`ImmutableStack`) +- `Queue` (`ImmutableQueue`) +- `HashMap` (`ImmutableDictionary`) +- `SortedMap` (`ImmutableSortedDictionary`) +- `HashSet` (`ImmutableHashSet`) +- `SortedSet` (`ImmutableSortedSet`) +- `IIndexedSeq` (`IReadOnlyList`) + +--- + +
    +
    +
    +
    + The FSharp.Collections.Immutable library can be installed from NuGet: +
    PM> Install-Package FSharp.Collections.Immutable
    +
    +
    +
    +
    + +--- + +
    +
    +
    +
    +
    Tutorials
    +

    Step-by-step guide to get started with FSharp.Collections.Immutable.

    +
    + +
    +
    +
    +
    +
    +
    How-To Guides
    +

    Guides you through the steps involved in addressing key problems and use-cases.

    +
    + +
    +
    +
    +
    +
    +
    Explanations
    +

    Discusses key topics and concepts at a fairly high level and provide useful background information and explanation.

    +
    + +
    +
    +
    +
    +
    +
    Api Reference
    +

    Contain technical reference for APIs.

    +
    + +
    +
    +
    diff --git a/global.json b/global.json new file mode 100644 index 0000000..ccbe73c --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "9.0.300", + "rollForward": "latestMinor" + } +} diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index f19c4a9..9f5941f 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -1,39 +1,43 @@  - netstandard1.6;netstandard2.0 - F# bindings for System.Collections.Immutable - Copyright © XperiAndri 2016 - FSharp.Collections.Immutable - XperiAndri - FSharp.Collections.Immutable - 1.0.0 - XperiAndri;EventHelix;vilinski + net8.0 + $(AssemblyBaseName) true + true + True + + + FSharp.Collections.Immutable - System;Immutable;Collections;FSharp;F# - git - https://github.com/fsprojects/FSharp.Collections.Immutable/ + FSharp.Collections.Immutable + F# API for using Microsoft Azure Cosmos DB service via NoSQL API + Provides extension methods for the FeedIterator and computation expressions to build operations - - - - - - - - - - + + true + true + - - - + + + + + + + + + + - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/FSharp.Collections.Immutable/flat-list.fs b/src/FSharp.Collections.Immutable/FlatList.fs similarity index 51% rename from src/FSharp.Collections.Immutable/flat-list.fs rename to src/FSharp.Collections.Immutable/FlatList.fs index 634fb87..82bcce7 100644 --- a/src/FSharp.Collections.Immutable/flat-list.fs +++ b/src/FSharp.Collections.Immutable/FlatList.fs @@ -1,4 +1,4 @@ -#if INTERACTIVE +#if INTERACTIVE namespace global #else namespace FSharp.Collections.Immutable @@ -8,60 +8,89 @@ namespace FSharp.Collections.Immutable type FlatList<'T> = System.Collections.Immutable.ImmutableArray<'T> // based on the F# Array module source -[] +[] module FlatList = type internal FlatListFactory = System.Collections.Immutable.ImmutableArray - let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T>() + let inline internal checkNotDefault argName (list : FlatList<'T>) = + if list.IsDefault then + invalidArg argName "Uninstantiated ImmutableArray/FlatList" + + let inline internal check (list : FlatList<'T>) = checkNotDefault (nameof list) list + ////////// Creating ////////// + + let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T> () let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T> (item) - let inline ofSeq source: FlatList<'T> = FlatListFactory.CreateRange source + let inline ofSeq source = FlatListFactory.CreateRange source + let inline ofArray (source : _ array) = FlatListFactory.CreateRange source + + let inline toSeq (flatList : FlatList<_>) = flatList :> seq<_> + + let inline toArray (list : FlatList<_>) = + check list + Seq.toArray list ////////// Building ////////// let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = - checkNotNull "builder" builder - builder.MoveToImmutable() + checkNotNull (nameof builder) builder + builder.MoveToImmutable () + let ofBuilder (builder : FlatList<_>.Builder) : FlatList<_> = - checkNotNull "builder" builder - builder.ToImmutable() + checkNotNull (nameof builder) builder + builder.ToImmutable () - let builder () : FlatList<'T>.Builder = FlatListFactory.CreateBuilder() + let inline builder () : FlatList<'T>.Builder = FlatListFactory.CreateBuilder () + let inline builderWith capacity : FlatList<'T>.Builder = FlatListFactory.CreateBuilder (capacity) - let builderWith capacity : FlatList<'T>.Builder = FlatListFactory.CreateBuilder(capacity) + let toBuilder list : FlatList<_>.Builder = + check list + list.ToBuilder () - let inline internal checkNotDefault argName (list : FlatList<'T>) = - if list.IsDefault then invalidArg argName "Uninstantiated ImmutableArray/FlatList" + module Builder = + let inline private check (builder : FlatList<'T>.Builder) = checkNotNull (nameof builder) builder + + let add item builder = + check builder + builder.Add (item) - let inline internal check (list : FlatList<'T>) = checkNotDefault "list" list + let inline internal indexNotFound () = raise <| System.Collections.Generic.KeyNotFoundException () - let inline internal indexNotFound() = raise (System.Collections.Generic.KeyNotFoundException()) + let isEmpty (list : FlatList<_>) = list.IsEmpty + let isDefault (list : FlatList<_>) = list.IsDefault + let isDefaultOrEmpty (list : FlatList<_>) = list.IsDefaultOrEmpty - let length list = check list; list.Length + ////////// IReadOnly* ////////// - let item index list = check list; list.[index] + let length list = + check list + list.Length + + let item index list = + check list + list.[index] let append list1 list2 : FlatList<'T> = - checkNotDefault "list1" list1 - checkNotDefault "list2" list2 - list1.AddRange(list2 : FlatList<_>) + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + list1.AddRange (list2 : FlatList<_>) /// Searches for the specified object and returns the zero-based index of the first occurrence within the range /// of elements in the list that starts at the specified index and /// contains the specified number of elements. let indexRangeWith comparer index count item list = check list - list.IndexOf(item, index, count, comparer) - let indexRange index count item list = - indexRangeWith HashIdentity.Structural index count item list - let indexFromWith comparer index item list = - indexRangeWith comparer index (length list - index) item - let indexFrom index item list = - indexFromWith HashIdentity.Structural index item list - let indexWith comparer item list = - indexFromWith comparer 0 item list + list.IndexOf (item, index, count, comparer) + + let indexRange index count item list = indexRangeWith HashIdentity.Structural index count item list + let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item + let indexFrom index item list = indexFromWith HashIdentity.Structural index item list + let indexWith comparer item list = indexFromWith comparer 0 item list let index item list = indexWith HashIdentity.Structural item list /// Searches for the specified object and returns the zero-based index of the last occurrence within the @@ -69,143 +98,159 @@ module FlatList = /// of elements and ends at the specified index. let lastIndexRangeWith comparer index count item list = check list - list.LastIndexOf(item, index, count, comparer) - let lastIndexRange index count item list = - lastIndexRangeWith HashIdentity.Structural index count item list - let lastIndexFromWith comparer index item list = - lastIndexRangeWith comparer index (index + 1) item list - let lastIndexFrom index item list = - lastIndexFromWith HashIdentity.Structural index item list - let lastIndexWith comparer item list = - lastIndexFromWith comparer (length list - 1) item list - let lastIndex item list = lastIndexWith HashIdentity.Structural item list - - let isEmpty (list: FlatList<_>) = list.IsEmpty - - let isDefault (list: FlatList<_>) = list.IsDefault + list.LastIndexOf (item, index, count, comparer) - let isDefaultOrEmpty (list: FlatList<_>) = list.IsDefaultOrEmpty + let lastIndexRange index count item list = lastIndexRangeWith HashIdentity.Structural index count item list + let lastIndexFromWith comparer index item list = lastIndexRangeWith comparer index (index + 1) item list + let lastIndexFrom index item list = lastIndexFromWith HashIdentity.Structural index item list + let lastIndexWith comparer item list = lastIndexFromWith comparer (length list - 1) item list + let lastIndex item list = lastIndexWith HashIdentity.Structural item list /// Removes the specified objects from the list with the given comparer. - let removeAllWith (comparer: System.Collections.Generic.IEqualityComparer<_>) items list: FlatList<_> = + let removeAllWith (comparer : System.Collections.Generic.IEqualityComparer<'T>) (items : 'T seq) list : FlatList<_> = check list - list.RemoveRange(items, comparer) + list.RemoveRange (items, comparer) /// Removes the specified objects from the list. let removeAll items list = removeAllWith HashIdentity.Structural items list /// Removes all the elements that do not match the conditions defined by the specified predicate. - let filter predicate list: FlatList<_> = + let filter predicate list : FlatList<_> = check list - System.Predicate(not << predicate) - |> list.RemoveAll + System.Predicate (not << predicate) |> list.RemoveAll /// Removes all the elements that do not match the conditions defined by the specified predicate. let where predicate list = filter predicate list /// Removes a range of elements from the list. - let removeRange index (count: int) list: FlatList<_> = check list; list.RemoveRange(index, count) + let removeRange index (count : int) list : FlatList<_> = + check list + list.RemoveRange (index, count) + + let blit source sourceIndex (destination : 'T[]) destinationIndex count = + checkNotDefault (nameof source) source - let blit source sourceIndex (destination: 'T[]) destinationIndex count = - checkNotDefault "source" source try - source.CopyTo(sourceIndex, destination, destinationIndex, count) - with - |exn -> raise exn // throw same exception with the correct stack trace. Update exception code + source.CopyTo (sourceIndex, destination, destinationIndex, count) + with exn -> + raise exn // throw same exception with the correct stack trace. Update exception code let sortRangeWithComparer comparer index count list = check list - list.Sort(index, count, comparer) + list.Sort (index, count, comparer) + let sortRangeWith comparer index count list = sortRangeWithComparer (ComparisonIdentity.FromFunction comparer) index count list - let sortRange index count list = sortRangeWithComparer ComparisonIdentity.Structural index count list - let sortWithComparer (comparer : System.Collections.Generic.IComparer<_>) list = check list; list.Sort(comparer) - let sortWith comparer list = sortWithComparer (ComparisonIdentity.FromFunction comparer) list - let sort list = check list; list.Sort() - ////////// Building ////////// - - let toBuilder list: FlatList<_>.Builder = check list; list.ToBuilder() + let sortRange index count list = sortRangeWithComparer ComparisonIdentity.Structural index count list - let inline private builderWithLengthOf list = builderWith <| length list + let sortWithComparer (comparer : System.Collections.Generic.IComparer<_>) list = + check list + list.Sort (comparer) - module Builder = - let inline private check (builder: FlatList<'T>.Builder) = checkNotNull "builder" builder + let sortWith comparer list = sortWithComparer (ComparisonIdentity.FromFunction comparer) list - let add item builder = check builder; builder.Add(item) + let sort list = + check list + list.Sort () ////////// Loop-based ////////// + let inline private builderWithLengthOf list = builderWith <| length list + let init count initializer = - if count < 0 then invalidArg "count" ErrorStrings.InputMustBeNonNegative + if count < 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + let builder = builderWith count + for i = 0 to count - 1 do builder.Add <| initializer i + moveFromBuilder builder - let rec private concatAddLengths (arrs: FlatList>) i acc = - if i >= length arrs then acc - else concatAddLengths arrs (i+1) (acc + arrs.[i].Length) + let rec private concatAddLengths (arrs : FlatList>) i acc = + if i >= length arrs then + acc + else + concatAddLengths arrs (i + 1) (acc + arrs.[i].Length) let concat (arrs : FlatList>) = // consider generalizing - let result: FlatList<'T>.Builder = builderWith <| concatAddLengths arrs 0 0 + let result : FlatList<'T>.Builder = builderWith <| concatAddLengths arrs 0 0 + for i = 0 to length arrs - 1 do - result.AddRange(arrs.[i]: FlatList<'T>) + result.AddRange (arrs.[i] : FlatList<'T>) + moveFromBuilder result let inline map mapping list = check list let builder = builderWithLengthOf list + for i = 0 to length list - 1 do - builder.Add(mapping list.[i]) + builder.Add (mapping list.[i]) + moveFromBuilder builder let countBy projection list = check list // need struct box optimization - let dict = new System.Collections.Generic.Dictionary<'Key, int>(HashIdentity.Structural) + let dict = new System.Collections.Generic.Dictionary<'Key, int> (HashIdentity.Structural) // Build the groupings for v in list do let key = projection v let mutable prev = Unchecked.defaultof<_> - if dict.TryGetValue(key, &prev) then dict.[key] <- prev + 1 else dict.[key] <- 1 + + if dict.TryGetValue (key, &prev) then + dict.[key] <- prev + 1 + else + dict.[key] <- 1 let res = builderWith dict.Count let mutable i = 0 + for group in dict do - res.Add(group.Key, group.Value) + res.Add (group.Key, group.Value) i <- i + 1 + moveFromBuilder res let indexed list = check list let builder = builderWithLengthOf list + for i = 0 to length list - 1 do - builder.Add(i, list.[i]) + builder.Add (i, list.[i]) + moveFromBuilder builder let inline iter action list = check list + for i = 0 to length list - 1 do action list.[i] let iter2 action list1 list2 = - checkNotDefault "list1" list1 - checkNotDefault "list2" list2 - let f = OptimizedClosures.FSharpFunc<'T,'U, unit>.Adapt(action) + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + let f = OptimizedClosures.FSharpFunc<'T, 'U, unit>.Adapt (action) let len = length list1 - if len <> length list2 then invalidArg "list2" ErrorStrings.ListsHaveDifferentLengths + + if len <> length list2 then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + for i = 0 to len - 1 do - f.Invoke(list1.[i], list2.[i]) + f.Invoke (list1.[i], list2.[i]) - let distinctBy projection (list: FlatList<'T>) = - let builder: FlatList<'T>.Builder = builderWith <| length list - let set = System.Collections.Generic.HashSet<'Key>(HashIdentity.Structural) + let distinctBy projection (list : FlatList<'T>) = + let builder : FlatList<'T>.Builder = builderWith <| length list + let set = System.Collections.Generic.HashSet<'Key> (HashIdentity.Structural) let mutable outputIndex = 0 for i = 0 to length list - 1 do let item = list.[i] + if set.Add <| projection item then outputIndex <- outputIndex + 1 Builder.add item builder @@ -213,203 +258,274 @@ module FlatList = ofBuilder builder let map2 mapping list1 list2 = - checkNotDefault "list1" list1 - checkNotDefault "list2" list2 - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(mapping) + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) let len1 = list1.Length - if len1 <> list2.Length then invalidArg "list2" ErrorStrings.ListsHaveDifferentLengths + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let res = builderWith len1 + for i = 0 to len1 - 1 do - res.Add <| f.Invoke(list1.[i], list2.[i]) + res.Add <| f.Invoke (list1.[i], list2.[i]) + moveFromBuilder res let map3 mapping list1 list2 list3 = - checkNotDefault "list1" list1 - checkNotDefault "list2" list2 - checkNotDefault "list3" list3 - let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt(mapping) + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + checkNotDefault (nameof list3) list3 + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) let len1 = list1.Length - if not (len1 = list2.Length && len1 = list3.Length) then invalidArg "" ErrorStrings.ListsHaveDifferentLengths + + if not (len1 = list2.Length) then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + if not (len1 = list3.Length) then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths let res = builderWith len1 + for i = 0 to len1 - 1 do - res.Add <| f.Invoke(list1.[i], list2.[i], list3.[i]) + res.Add <| f.Invoke (list1.[i], list2.[i], list3.[i]) + moveFromBuilder res + let mapi2 mapping list1 list2 = - checkNotDefault "list1" list1 - checkNotDefault "list2" list2 - let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt(mapping) + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) let len1 = list1.Length - if len1 <> list2.Length then invalidArg "list2" ErrorStrings.ListsHaveDifferentLengths + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let res = builderWith len1 + for i = 0 to len1 - 1 do - res.Add <| f.Invoke(i,list1.[i], list2.[i]) + res.Add <| f.Invoke (i, list1.[i], list2.[i]) + moveFromBuilder res let iteri action list = check list - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(action) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (action) let len = list.Length + for i = 0 to len - 1 do - f.Invoke(i, list.[i]) + f.Invoke (i, list.[i]) let iteri2 action list1 list2 = - checkNotDefault "list1" list1 - checkNotDefault "list2" list2 - let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt(action) + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (action) let len1 = list1.Length - if len1 <> list2.Length then invalidArg "list2" ErrorStrings.ListsHaveDifferentLengths + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + for i = 0 to len1 - 1 do - f.Invoke(i,list1.[i], list2.[i]) + f.Invoke (i, list1.[i], list2.[i]) let mapi mapping list = check list - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(mapping) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) let len = list.Length let res = builderWithLengthOf list + for i = 0 to len - 1 do - res.Add <| f.Invoke(i,list.[i]) + res.Add <| f.Invoke (i, list.[i]) + moveFromBuilder res let exists predicate list = check list let len = list.Length - let rec loop i = i < len && (predicate list.[i] || loop (i+1)) + let rec loop i = i < len && (predicate list.[i] || loop (i + 1)) loop 0 let inline contains e list = check list let mutable state = false let mutable i = 0 + while (not state && i < list.Length) do state <- e = list.[i] i <- i + 1 + state let exists2 predicate list1 list2 = - checkNotDefault "list1" list1 - checkNotDefault "list2" list2 - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(predicate) + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) let len1 = list1.Length - if len1 <> list2.Length then invalidArg "list2" ErrorStrings.ListsHaveDifferentLengths - let rec loop i = i < len1 && (f.Invoke(list1.[i], list2.[i]) || loop (i+1)) + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + let rec loop i = + i < len1 + && (f.Invoke (list1.[i], list2.[i]) || loop (i + 1)) + loop 0 let forall predicate list = check list let len = list.Length - let rec loop i = i >= len || (predicate list.[i] && loop (i+1)) + let rec loop i = i >= len || (predicate list.[i] && loop (i + 1)) loop 0 let forall2 predicate list1 list2 = - checkNotDefault "list1" list1 - checkNotDefault "list2" list2 - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(predicate) + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) let len1 = list1.Length - if len1 <> list2.Length then invalidArg "list2" ErrorStrings.ListsHaveDifferentLengths - let rec loop i = i >= len1 || (f.Invoke(list1.[i], list2.[i]) && loop (i+1)) + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + let rec loop i = + i >= len1 + || (f.Invoke (list1.[i], list2.[i]) && loop (i + 1)) + loop 0 let groupBy projection list = check list - let dict = new System.Collections.Generic.Dictionary<'Key,ResizeArray<'T>>(HashIdentity.Structural) + let dict = new System.Collections.Generic.Dictionary<'Key, ResizeArray<'T>> (HashIdentity.Structural) // Build the groupings for i = 0 to (list.Length - 1) do let v = list.[i] let key = projection v - let ok, prev = dict.TryGetValue(key) + let ok, prev = dict.TryGetValue (key) + if ok then - prev.Add(v) + prev.Add (v) else - let prev = new ResizeArray<'T>(1) + let prev = new ResizeArray<'T> (1) dict.[key] <- prev - prev.Add(v) + prev.Add (v) // Return the list-of-lists. let result = builderWith dict.Count let mutable i = 0 + for group in dict do - result.Add(group.Key, ofSeq group.Value) + result.Add (group.Key, ofSeq group.Value) i <- i + 1 moveFromBuilder result let pick chooser list = check list + let rec loop i = if i >= list.Length then - indexNotFound() + indexNotFound () else match chooser list.[i] with - | None -> loop(i+1) + | None -> loop (i + 1) | Some res -> res + loop 0 let tryPick chooser list = check list + let rec loop i = - if i >= list.Length then None else - match chooser list.[i] with - | None -> loop(i+1) - | res -> res + if i >= list.Length then + None + else + match chooser list.[i] with + | None -> loop (i + 1) + | res -> res + loop 0 let choose chooser list = check list let res = builderWith list.Length + for i = 0 to list.Length - 1 do match chooser list.[i] with | None -> () - | Some b -> res.Add(b) + | Some b -> res.Add (b) + ofBuilder res let partition predicate list = check list let res1 = builderWith list.Length let res2 = builderWith list.Length + for i = 0 to list.Length - 1 do let x = list.[i] - if predicate x then res1.Add(x) else res2.Add(x) + if predicate x then res1.Add (x) else res2.Add (x) + ofBuilder res1, ofBuilder res2 let find predicate list = check list + let rec loop i = - if i >= list.Length then indexNotFound() else - if predicate list.[i] then list.[i] else loop (i+1) + if i >= list.Length then indexNotFound () + else if predicate list.[i] then list.[i] + else loop (i + 1) + loop 0 + let tryFind predicate list = check list + let rec loop i = - if i >= list.Length then None else - if predicate list.[i] then Some list.[i] else loop (i+1) + if i >= list.Length then None + else if predicate list.[i] then Some list.[i] + else loop (i + 1) + loop 0 + let findBack predicate list = check list + let rec loop i = - if i < 0 then indexNotFound() else - if predicate list.[i] then list.[i] else loop (i - 1) + if i < 0 then indexNotFound () + else if predicate list.[i] then list.[i] + else loop (i - 1) + loop <| length list - 1 + let tryFindBack predicate list = check list + let rec loop i = - if i < 0 then None else - if predicate list.[i] then Some list.[i] else loop (i+1) + if i < 0 then None + else if predicate list.[i] then Some list.[i] + else loop (i + 1) + loop <| length list - 1 let findIndexBack predicate list = check list + let rec loop i = - if i < 0 then indexNotFound() else - if predicate list.[i] then i else loop (i - 1) + if i < 0 then indexNotFound () + else if predicate list.[i] then i + else loop (i - 1) + loop <| length list - 1 let tryFindIndexBack predicate list = check list + let rec loop i = - if i < 0 then None else - if predicate list.[i] then Some i else loop (i - 1) + if i < 0 then None + else if predicate list.[i] then Some i + else loop (i - 1) + loop <| length list - 1 // TODO: windowed @@ -420,9 +536,12 @@ module FlatList = let inline private lengthWhile predicate list = check list let mutable count = 0 + while count < list.Length && predicate list.[count] do count <- count + 1 + count + let takeWhile predicate list = take (lengthWhile predicate list) list let skip index list = removeRange 0 index list @@ -438,8 +557,10 @@ module FlatList = let head list = item 0 list let tryItem index list = - if index >= length list || index < 0 then None - else Some(list.[index]) + if index >= length list || index < 0 then + None + else + Some (list.[index]) let tryHead list = tryItem 0 list @@ -458,7 +579,7 @@ module FlatList = let collect mapping list = concat <| map mapping list let inline build f = - let builder = builder() + let builder = builder () f builder moveFromBuilder builder @@ -467,6 +588,6 @@ module FlatList = f builder moveFromBuilder builder - ////////// +////////// module ImmutableArray = FlatList diff --git a/src/FSharp.Collections.Immutable/immutable-collection-util.fs b/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs similarity index 73% rename from src/FSharp.Collections.Immutable/immutable-collection-util.fs rename to src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs index af62e58..608fe4f 100644 --- a/src/FSharp.Collections.Immutable/immutable-collection-util.fs +++ b/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs @@ -1,4 +1,4 @@ -#if INTERACTIVE +#if INTERACTIVE namespace global #else namespace FSharp.Collections.Immutable @@ -6,14 +6,14 @@ namespace FSharp.Collections.Immutable [] module internal ImmutableCollectionUtil = - let inline checkNotNull name arg = + let inline checkNotNull name (arg : _ | null) = match arg with - |null -> nullArg name - |_ -> () + | null -> nullArg name + | _ -> () module internal ErrorStrings = [] let InputMustBeNonNegative = "The input must be non-negative." [] - let ListsHaveDifferentLengths = "The lists have different lengths." \ No newline at end of file + let ListsHaveDifferentLengths = "The lists have different lengths." diff --git a/src/FSharp.Collections.Immutable/ImmutableList.fs b/src/FSharp.Collections.Immutable/ImmutableList.fs new file mode 100644 index 0000000..48ced7c --- /dev/null +++ b/src/FSharp.Collections.Immutable/ImmutableList.fs @@ -0,0 +1,353 @@ +#if INTERACTIVE +namespace global +#else +namespace FSharp.Collections.Immutable + +open FSharp.Collections.Immutable.ImmutableCollectionUtil +#endif +open System.Collections.Immutable + +[] +module ImmutableList = + + ////////// Factory ////////// + + let inline internal check (list : IImmutableList<_>) = checkNotNull (nameof list) list + + let inline empty<'T> = ImmutableList.Create<'T> () + let inline singleton<'T> (item : 'T) : ImmutableList<'T> = ImmutableList.Create<'T> (item) + + let inline ofSeq source = + checkNotNull (nameof source) source + ImmutableList.CreateRange source + + let inline ofArray (source : _ array) = + checkNotNull (nameof source) source + ImmutableList.CreateRange source + + let inline ofList (list : _ list) = ofSeq list + + let inline toSeq (list : ImmutableList<_>) = list :> seq<_> + + let inline toArray (list : ImmutableList<_>) = + check list + Seq.toArray list + + ////////// Building ////////// + + let inline ofBuilder (builder : ImmutableList<_>.Builder) = builder.ToImmutable () + + let inline builder () = ImmutableList.CreateBuilder () + + let toBuilder (list : ImmutableList<_>) = + check list + list.ToBuilder () + + let inline build f = + let builder = builder () + f builder + builder.ToImmutable () + + let inline update f list = + let builder = toBuilder list + f builder + builder.ToImmutable () + + + open System.Collections.Generic + open System + + ////////// IReadOnly* ////////// + + let length list = + check list + list.Count + + let item index list = + check list + list.[index] + + ////////// ImmutableList ////////// + + let contains item (list : ImmutableList<_>) = list.Contains (item) + + let reverse (list : ImmutableList<_>) = list.Reverse () + + let reverseRange (index, count) (list : ImmutableList<_>) = list.Reverse (index, count) + + ////////// IImmutableList ////////// + + /// Replaces an element in the list at a given position with the specified element. + let withItem index value list = + check list + list.SetItem (index, value) + + /// Returns a new list with the first matching element in the list replaced with the specified element with + /// the given comparer. + let replaceWith comparer oldValue value list = + check list + list.Replace (oldValue, value, comparer) + + /// Returns a new list with the first matching element in the list replaced with the specified element. + let replace oldValue value list = replaceWith HashIdentity.Structural oldValue value list + + + /// Creates a list with all the items removed, but with the same sorting and ordering semantics as + /// this list. + let clear list = + check list + list.Clear () + + /// Makes a copy of the list, and adds the specified object to the end of the copied list. + let add item list = + check list + list.Add item + + + /// Makes a copy of the list and adds the specified objects to the end of the copied list. + let append list items = + check list + list.AddRange items + + /// Inserts the specified element at the specified index in a immutable list. + let insert index item list = + check list + list.Insert (index, item) + + /// Inserts the specified elements at the specified index in the immutable list. + let insertRange index items list = + check list + list.InsertRange (index, items) // TODO: rename + + + /// Removes the first occurrence of a specified object from this immutable list using the given comparer. + let removeWith comparer item list = + check list + list.Remove (item, comparer) + + /// Removes the first occurrence of a specified object from this immutable list. + let remove item list = removeWith HashIdentity.Structural item list + + + /// Removes the specified objects from the list with the given comparer. + let exceptWith (comparer : IEqualityComparer<_>) items list = + check list + list.RemoveRange (items, comparer) + + /// Removes the specified objects from the list. + let except items list = exceptWith HashIdentity.Structural items list + + + /// Removes all the elements that do not match the conditions defined by the specified predicate. + let filter predicate list = + check list + Predicate (not << predicate) |> list.RemoveAll + + /// Removes a range of elements from the System.Collections.Immutable.IImmutableList`1. + let removeRange index (count : int) list = + check list + list.RemoveRange (index, count) + + /// Removes the element at the specified index of the immutable list. + let removeAt index list = + check list + list.RemoveAt index + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the list that starts at the specified index and + /// contains the specified number of elements. + let indexRangeWith comparer index count item list = + check list + list.IndexOf (item, index, count, comparer) + + let indexRange index count item list = indexRangeWith HashIdentity.Structural index count item list + let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item + let indexFrom index item list = indexFromWith HashIdentity.Structural index item list + let indexWith comparer item list = indexFromWith comparer 0 item list + let index item list = indexWith HashIdentity.Structural item list + + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the list that contains the specified number + /// of elements and ends at the specified index. + let lastIndexRangeWith comparer index count item list = + check list + list.LastIndexOf (item, index, count, comparer) + + let lastIndexRange index count item list = lastIndexRangeWith HashIdentity.Structural index count item list + let lastIndexFromWith comparer index item list = lastIndexRangeWith comparer index (index + 1) item list + let lastIndexFrom index item list = lastIndexFromWith HashIdentity.Structural index item list + let lastIndexWith comparer item list = lastIndexFromWith comparer (length list - 1) item list + let lastIndex item list = lastIndexWith HashIdentity.Structural item list + + ////////// Filter-based ////////// + + let filterFold (predicate : 'State -> 'T -> bool * 'State) initial list = + let state = ref initial + + filter + (fun item -> + let condition, state' = predicate !state item + state := state' + condition + ) + list, + !state + + let skipWhile predicate list = + let condition = ref true + + filter + (fun item -> + if !condition then + condition := !condition && predicate item + !condition + else + false + ) + list + + let skipUntil predicate list = skipWhile (not << predicate) list + + let takeWhile predicate list = + let condition = ref true + + filter + (fun item -> + if !condition then + condition := !condition && predicate item + not !condition + else + true + ) + list + + let takeUntil predicate list = takeWhile (not << predicate) list + + ////////// Loop-based ////////// + + let concat lists = + checkNotNull (nameof lists) lists + + build + <| fun result -> + for list in lists do + result.AddRange list + + let map mapping list = + check list + + build + <| fun builder -> + for item in list do + builder.Add (mapping item) + + let choose chooser list = + check list + + build + <| fun builder -> + for item in list do + match chooser item with + | Some item -> builder.Add item + | None -> () + + + ////////// Based on other operations ////////// + + let isEmpty list = length list = 0 + + let take count list = removeRange count (length list - count) list + + let skip index list = removeRange 0 index list + + let truncate count list = if count < length list then take count list else list + + let splitAt index list = take index list, skip index list + + let head list = item 0 list + + let last (list : IImmutableList<_>) = list.[length list - 1] + + let tail list = removeAt 0 list + + let tryItem index list = + if index >= length list || index < 0 then + None + else + Some (list.[index]) + + let tryHead list = tryItem 0 list + + let tryLast list = tryItem (length list - 1) list + + let tryTail list = if isEmpty list then None else Some <| tail list + + let collect mapping list = concat <| map mapping list + + let cons head list = insert 0 head list + + //let ofArray (array: 'T array) = ImmutableList.Create<'T>(items = [||]) + + let init count initializer = + if count < 0 then + // throw the same exception + try + Seq.init count initializer |> ignore + with exn -> + raise exn // get the right stack trace + + build + <| fun builder -> + for i = 0 to count - 1 do + builder.Add <| initializer i + + let unfold generator state = + let rec unfoldLoop state (builder : ImmutableList<_>.Builder) = + match generator state with + | Some (state, item) -> + builder.Add (item) + unfoldLoop state builder + | None -> () + + build <| unfoldLoop state + + + ////////// Seq-based ////////// + + let find predicate list = + check list + Seq.find predicate list + + let tryFind predicate list = + check list + Seq.tryFind predicate list + + let findIndex predicate list = + check list + Seq.findIndex predicate list + + let tryFindIndex predicate list = + check list + Seq.tryFindIndex predicate list + + let pick chooser list = + check list + Seq.pick chooser list + + let fold folder state list = + check list + Seq.fold folder state list + + let forall predicate list = + check list + Seq.forall predicate list + + let forall2 predicate (list1 : IImmutableList<_>) (list2 : IImmutableList<_>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + Seq.forall2 predicate list1 list2 + + let iter action list = + check list + Seq.iter action list diff --git a/src/FSharp.Collections.Immutable/IndexedSeq.fs b/src/FSharp.Collections.Immutable/IndexedSeq.fs new file mode 100644 index 0000000..7d388b4 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedSeq.fs @@ -0,0 +1,17 @@ +namespace FSharp.Collections.Immutable + +type IIndexedSeq<'T> = System.Collections.Generic.IReadOnlyList<'T> + +module IndexedSeq = + + let check (seq : IIndexedSeq<_>) = checkNotNull (nameof seq) seq + + let item index seq = + check seq + seq.[index] + + let length seq = + check seq + seq.Count + +module ReadOnlyList = IndexedSeq diff --git a/src/FSharp.Collections.Immutable/Maps.fs b/src/FSharp.Collections.Immutable/Maps.fs new file mode 100644 index 0000000..12f5d63 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Maps.fs @@ -0,0 +1,339 @@ +namespace FSharp.Collections.Immutable + +open System.Collections.Generic + +type IMap<'Key, 'Value> = System.Collections.Immutable.IImmutableDictionary<'Key, 'Value> + +type HashMap<'Key, 'Value when 'Key : not null> = System.Collections.Immutable.ImmutableDictionary<'Key, 'Value> + +type HashMapBuilder<'Key, 'Value when 'Key : not null> = HashMap<'Key, 'Value>.Builder + +[] +module HashMap = + + type internal HashMapFactory = System.Collections.Immutable.ImmutableDictionary + + let inline check (map : HashMap<_, _>) = checkNotNull (nameof map) map + + ////////// Creating ////////// + + let inline empty<'Key, 'Value when 'Key : not null> = HashMapFactory.Create<'Key, 'Value> () + let inline singleton item = empty.Add (item) + + let inline ofSeq source = HashMapFactory.CreateRange (source) + + let inline ofSeqWith getKey source = + source + |> Seq.map (fun i -> KeyValuePair (getKey i, i)) + |> HashMapFactory.CreateRange + + let inline ofSeqGroupBy getKey source = + source + |> Seq.groupBy getKey + |> Seq.map (fun (key, value) -> KeyValuePair (key, value)) + |> HashMapFactory.CreateRange + + let inline ofArray (source : _ array) = HashMapFactory.CreateRange (source) + + let inline toSeq (map : HashMap<_, _>) = map :> seq<_> + + let inline toArray (map : HashMap<_, _>) = + check map + Seq.toArray map + + ////////// Building ////////// + + let inline builder () = HashMapFactory.CreateBuilder () + let inline builderWithKeyComparer comparer = HashMapFactory.CreateBuilder (comparer) + let inline builderWithComparers keyComparer valueComparer = HashMapFactory.CreateBuilder (keyComparer, valueComparer) + + let inline ofBuilder (mapBuilder : HashMapBuilder<_, _>) = + checkNotNull (nameof mapBuilder) mapBuilder + mapBuilder.ToImmutable () + + let inline toBuilder map : HashMapBuilder<_, _> = + check map + map.ToBuilder () + + let inline ofKeyComparer<'Key, 'Value when 'Key : not null> comparer = HashMapFactory.Create<'Key, 'Value> (comparer) + let inline ofComparers<'Key, 'Value when 'Key : not null> keyComparer valueComparer = + HashMapFactory.Create<'Key, 'Value> (keyComparer, valueComparer) + + + let inline isEmpty map = + check map + map.IsEmpty + + let inline length map = + check map + map.Count + + let inline keyComparer map = + check map + map.KeyComparer + + let inline valueComparer map = + check map + map.ValueComparer + + let inline containsKey key map = + check map + map.ContainsKey key + + let inline find key map = + check map + map.[key] + + let inline tryFind key map = + check map + + match map.TryGetValue (key) with + | true, value -> Some value + | false, _ -> None + + let inline vTryFind key map = + check map + + match map.TryGetValue (key) with + | true, value -> ValueSome value + | false, _ -> ValueNone + + let inline pick chooser map = + check map + map |> Seq.pick (fun kvp -> chooser kvp.Key kvp.Value) + + let inline tryPick chooser map = + check map + map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) + + let inline vTryPick chooser map = + check map + + match map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) with + | Some value -> ValueSome value + | None -> ValueNone + + let inline iter action (map : HashMap<_, _>) = + check map + map |> Seq.iter (fun kvp -> action kvp.Key kvp.Value) + + let inline exists predicate map = + check map + map |> Seq.exists (fun kvp -> predicate kvp.Key kvp.Value) + + let inline add key value map : HashMap<_, _> = + check map + map.Add (key, value) + + let inline append map pairs : HashMap<_, _> = + check map + checkNotNull (nameof pairs) pairs + map.AddRange pairs + + let inline remove key map : HashMap<_, _> = + check map + map.Remove key + + let inline except keys map : HashMap<_, _> = + check map + map.RemoveRange keys + + let inline clear map : HashMap<_, _> = + check map + map.Clear () + + let inline filter predicate map = + map + |> Seq.filter (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + + let inline forall predicate map = + map + |> Seq.forall (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + + let inline map mapping map' = + map' + |> Seq.map (fun (kvp : KeyValuePair<_, _>) -> mapping kvp.Key kvp.Value) + |> ofSeq + + let inline where predicate map = + map + |> Seq.where (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + |> empty.AddRange + + +type SortedMap<'Key, 'Value when 'Key : not null> = System.Collections.Immutable.ImmutableSortedDictionary<'Key, 'Value> + +type SortedMapBuilder<'Key, 'Value when 'Key : not null> = SortedMap<'Key, 'Value>.Builder + +[] +module SortedMap = + + type internal SortedMapFactory = System.Collections.Immutable.ImmutableSortedDictionary + + let inline check (sortedMap : SortedMap<_, _>) = checkNotNull (nameof sortedMap) sortedMap + + ////////// Creating ////////// + + let inline empty<'Key, 'Value when 'Key : not null> = SortedMapFactory.Create<'Key, 'Value> () + let inline singleton item = SortedMapFactory.Create (item) + + let inline ofSeq source = SortedMapFactory.CreateRange (source) + + let inline ofSeqWith getKey source = + source + |> Seq.map (fun i -> KeyValuePair (getKey i, i)) + |> SortedMapFactory.CreateRange + + let inline ofSeqGroupBy getKey source = + source + |> Seq.groupBy getKey + |> Seq.map (fun (key, value) -> KeyValuePair (key, value)) + + let inline ofArray (source : _ array) = SortedMapFactory.CreateRange (source) + + let inline toSeq (map : SortedMap<_, _>) = map :> seq<_> + + let inline toArray (map : SortedMap<_, _>) = + check map + Seq.toArray map + + ////////// Building ////////// + + let inline builder () = SortedMapFactory.CreateBuilder () + let inline builderWithKeyComparer comparer = SortedMapFactory.CreateBuilder (comparer) + let inline builderWithComparers keyComparer valueComparer = SortedMapFactory.CreateBuilder (keyComparer, valueComparer) + + let inline ofBuilder (sortedMapBuilder : SortedMapBuilder<_, _>) = + checkNotNull (nameof sortedMapBuilder) sortedMapBuilder + sortedMapBuilder.ToImmutable () + + let inline toBuilder map : SortedMapBuilder<_, _> = + check map + map.ToBuilder () + + let inline ofKeyComparer<'Key, 'Value when 'Key : not null> comparer = SortedMapFactory.Create<'Key, 'Value> (comparer) + let inline ofComparers<'Key, 'Value when 'Key : not null> keyComparer valueComparer = + SortedMapFactory.Create<'Key, 'Value> (keyComparer, valueComparer) + + + let inline isEmpty map = + check map + map.IsEmpty + + let inline length map = + check map + map.Count + + let inline keyComparer map = + check map + map.KeyComparer + + let inline valueComparer map = + check map + map.ValueComparer + + let inline containsKey key map = + check map + map.ContainsKey key + + let inline find key map = + check map + map.[key] + + let inline tryFind key map = + check map + + match map.TryGetValue (key) with + | true, value -> Some value + | false, _ -> None + + let inline vTryFind key map = + check map + + match map.TryGetValue (key) with + | true, value -> ValueSome value + | false, _ -> ValueNone + + let inline pick chooser map = + check map + map |> Seq.pick (fun kvp -> chooser kvp.Key kvp.Value) + + let inline tryPick chooser map = + check map + map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) + + let inline vTryPick chooser map = + check map + + match map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) with + | Some value -> ValueSome value + | None -> ValueNone + + let inline iter action (map : SortedMap<_, _>) = + check map + map |> Seq.iter (fun kvp -> action kvp.Key kvp.Value) + + let inline exists predicate map = + check map + map |> Seq.exists (fun kvp -> predicate kvp.Key kvp.Value) + + let inline add key value map : SortedMap<_, _> = + check map + map.Add (key, value) + + let inline append map pairs : SortedMap<_, _> = + check map + checkNotNull (nameof pairs) pairs + map.AddRange pairs + + let inline remove key map : SortedMap<_, _> = + check map + map.Remove key + + let inline except keys map : SortedMap<_, _> = + check map + map.RemoveRange keys + + let inline clear map : SortedMap<_, _> = + check map + map.Clear () + + let inline findKey predicate map = + check map + + match (map |> Seq.tryFind (fun kvp -> predicate kvp.Key kvp.Value)) with + | Some value -> value.Key + | None -> raise (new KeyNotFoundException ()) + + let inline tryFindKey predicate map = + check map + map |> Seq.tryFind (fun kvp -> predicate kvp.Key kvp.Value) + + let inline vTryFindKey predicate map = + check map + + match (map |> Seq.tryFind (fun kvp -> predicate kvp.Key kvp.Value)) with + | Some value -> ValueSome value.Key + | None -> ValueNone + + let inline filter predicate map = + map + |> Seq.filter (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + + let inline forall predicate map = + map + |> Seq.forall (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + + let inline map mapping (map' : SortedMap<_, _>) = + map' + |> Seq.map (fun (kvp : KeyValuePair<_, _>) -> mapping kvp.Key kvp.Value) + |> ofSeq + + let inline where predicate map = + map + |> Seq.where (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + |> empty.AddRange diff --git a/src/FSharp.Collections.Immutable/Queue.fs b/src/FSharp.Collections.Immutable/Queue.fs new file mode 100644 index 0000000..dd8d64f --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue.fs @@ -0,0 +1,132 @@ +namespace FSharp.Collections.Immutable + +type IQueue<'T> = System.Collections.Immutable.IImmutableQueue<'T> + +type Queue<'T> = System.Collections.Immutable.ImmutableQueue<'T> + +[] +module Queue = + + type internal QueueFactory = System.Collections.Immutable.ImmutableQueue + + let inline private check (queue : IQueue<_>) = checkNotNull (nameof queue) queue + + let inline empty<'T> : Queue<'T> = QueueFactory.Create<'T> () + + let inline singleton<'T> (item : 'T) : Queue<'T> = QueueFactory.Create<'T> (item) + + let inline ofSeq (source : 'T seq) : Queue<'T> = QueueFactory.CreateRange source + + let inline toSeq (queue : Queue<_>) = queue :> seq<_> + + let isEmpty queue = + check queue + queue.IsEmpty + + let clear queue : IQueue<_> = + check queue + queue.Clear () + + let enqueue item queue : IQueue<_> = + check queue + queue.Enqueue item + + let head queue = + check queue + queue.Peek () + + let tail queue : IQueue<_> = + check queue + queue.Dequeue () + + ////////// + + let (|Cons|Nil|) queue = // consider renaming + if isEmpty queue then Nil else Cons (head queue, tail queue) + + ////////// Predicate based ////////// + + let filter predicate queue = + let rec loop queue result = + match queue with + | Cons (head, tail) -> + loop tail + <| if predicate head then enqueue head result else result + | Nil -> result + + loop queue <| clear queue + + ////////// Seq-based ////////// + + let find predicate queue = + check queue + Seq.find predicate queue + + let tryFind predicate queue = + check queue + Seq.tryFind predicate queue + + let findIndex predicate queue = + check queue + Seq.findIndex predicate queue + + let tryFindIndex predicate queue = + check queue + Seq.tryFindIndex predicate queue + + let pick chooser queue = + check queue + Seq.pick chooser queue + + let tryPick chooser queue = + check queue + Seq.tryPick chooser queue + + let iter action queue = + check queue + Seq.iter action queue + + let iteri action queue = + check queue + Seq.iteri action queue + + let iter2 action (queue1 : IQueue<_>) (queue2 : IQueue<_>) = + checkNotNull (nameof queue1) queue1 + checkNotNull (nameof queue2) queue2 + Seq.iter2 action queue1 queue2 + + let fold folder state queue = + check queue + Seq.fold folder state queue + + let forall predicate queue = + check queue + Seq.forall predicate + + let exists predicate queue = + check queue + Seq.exists predicate queue + + let reduce reduction queue = + check queue + Seq.reduce reduction queue + + let inline sum queue = + check queue + Seq.sum queue + + let inline sumBy projection queue = + check queue + Seq.sumBy projection queue + + let inline average queue = + check queue + Seq.average queue + + let inline averageBy projection queue = + check queue + Seq.averageBy projection + +module ImmutableQueue = Queue diff --git a/src/FSharp.Collections.Immutable/Seq.fs b/src/FSharp.Collections.Immutable/Seq.fs new file mode 100644 index 0000000..d9826b1 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Seq.fs @@ -0,0 +1,32 @@ +#if INTERACTIVE +namespace global +#else +namespace FSharp.Collections.Immutable +#endif + +[] +module Seq = + + let inline ofFlatList flatList = FlatList.toSeq flatList + let inline toFlatList seq = FlatList.ofSeq seq + + let inline ofStack stack = Stack.toSeq stack + let inline toStack seq = Stack.ofSeq seq + + let inline ofImmutableList immutableList = ImmutableList.toSeq immutableList + let inline toImmutableList seq = ImmutableList.ofSeq seq + + let inline ofQueue queue = Queue.toSeq queue + let inline toQueue queue = Queue.ofSeq queue + + let inline ofHashMap hashMap = HashMap.toSeq hashMap + let inline toHashMap hashMap = HashMap.ofSeq hashMap + + let inline ofSortedMap sortedHashMap = SortedMap.toSeq sortedHashMap + let inline toSortedMap sortedHashMap = SortedMap.ofSeq sortedHashMap + + let inline ofHashSet hashSet = HashSet.toSeq hashSet + let inline toHashSet hashSet = HashSet.ofSeq hashSet + + let inline ofSortedSet sortedSet = SortedSet.toSeq sortedSet + let inline toSortedSet sortedSet = SortedSet.ofSeq sortedSet diff --git a/src/FSharp.Collections.Immutable/Sets.fs b/src/FSharp.Collections.Immutable/Sets.fs new file mode 100644 index 0000000..cbdba22 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Sets.fs @@ -0,0 +1,266 @@ +namespace FSharp.Collections.Immutable + +type ISet<'T> = System.Collections.Immutable.IImmutableSet<'T> + +type HashSet<'T> = System.Collections.Immutable.ImmutableHashSet<'T> +type HashSetBuilder<'T> = HashSet<'T>.Builder + +[] +module HashSet = + + type internal HashSetFactory = System.Collections.Immutable.ImmutableHashSet + + let inline check (set : HashSet<_>) = checkNotNull (nameof set) set + + ////////// Creating ////////// + + let inline empty<'T> = HashSetFactory.Create<'T> () + let inline singleton<'T> (item : 'T) = HashSetFactory.Create<'T> (item) + let inline ofSeq source = HashSetFactory.CreateRange (source) + let inline ofSeqWithComparer comparer source = HashSetFactory.Create (comparer, items = (source |> Array.ofSeq)) + let inline ofArray (source : _ array) = HashSetFactory.CreateRange (source) + + let inline ofBuilder (hashSetBuilder : HashSetBuilder<_>) = + checkNotNull (nameof hashSetBuilder) hashSetBuilder + hashSetBuilder.ToImmutable () + + let inline ofComparer<'T> comparer = HashSetFactory.Create<'T> (equalityComparer = comparer) + + let inline toSeq (set : HashSet<_>) = set :> seq<_> + + let inline toArray (set : HashSet<_>) = + check set + Seq.toArray set + + ////////// Building ////////// + + let inline builder () = HashSetFactory.CreateBuilder () + let inline builderWith capacity : HashSet<'T>.Builder = HashSetFactory.CreateBuilder (capacity) + let inline builderWithComparer comparer = HashSetFactory.CreateBuilder (comparer) + + let inline toBuilder set : HashSetBuilder<_> = + check set + set.ToBuilder () + + let inline keyComparer set = + check set + set.KeyComparer + + let inline length set = + check set + set.Count + + let inline isEmpty set = + check set + set.IsEmpty + + let inline contains value set = + check set + set.Contains value + + let inline exists predicate map = + check map + map |> Seq.exists predicate + + let inline isSubset (set1 : HashSet<_>) set2 = + check set1 + set1.IsSubsetOf set2 + + let inline isProperSubset (set1 : HashSet<_>) set2 = + check set1 + set1.IsProperSubsetOf set2 + + let inline isSuperset (set1 : HashSet<_>) set2 = + check set1 + set1.IsSupersetOf set2 + + let inline isProperSuperset (set1 : HashSet<_>) set2 = + check set1 + set1.IsProperSupersetOf set2 + + let inline add value set : HashSet<_> = + check set + set.Add (value) + + let inline union set values : HashSet<_> = + check set + values |> set.Union + + let inline unionMany (sets : HashSet<_> seq) = Seq.reduce union sets + + let inline intersect (set1 : HashSet<_>) set2 = + check set1 + set1.Intersect set2 + + let inline intersectMany (sets : HashSet<_> seq) = Seq.reduce intersect sets + + let inline remove value set : HashSet<_> = + check set + set.Remove value + + let inline difference values set : HashSet<_> = + check set + values |> set.Except + + let inline clear set : HashSet<_> = + check set + set.Clear () + + let inline filter predicate set = set |> Seq.filter predicate |> empty.Union + + let inline where predicate set = set |> Seq.where predicate |> empty.Union + + let inline pick chooser set = + check set + set |> Seq.pick chooser + + let inline tryPick chooser set = + check set + set |> Seq.tryPick chooser + + let inline vTryPick chooser set = + check set + + match set |> Seq.tryPick chooser with + | Some value -> ValueSome value + | None -> ValueNone + + let inline map mapping (set : HashSet<_>) = set |> Seq.map mapping |> ofSeq + + let inline forall predicate set = set |> Seq.forall predicate + + let inline iter action (set : HashSet<_>) = + check set + set |> Seq.iter action + +type SortedSet<'T> = System.Collections.Immutable.ImmutableSortedSet<'T> +type SortedSetBuilder<'T> = SortedSet<'T>.Builder + +[] +module SortedSet = + + type internal SortedSetFactory = System.Collections.Immutable.ImmutableSortedSet + + let inline check (sortedSet : SortedSet<_>) = checkNotNull (nameof sortedSet) sortedSet + + ////////// Creating ////////// + + let inline empty<'T> = SortedSetFactory.Create<'T> () + let inline singleton<'T> (item : 'T) = SortedSetFactory.Create<'T> (item) + let inline ofSeq source = SortedSetFactory.CreateRange (source) + let inline ofSeqWithComparer comparer source = SortedSetFactory.Create (comparer, items = (source |> Array.ofSeq)) + let inline ofArray (source : _ array) = SortedSetFactory.CreateRange (source) + + let inline ofBuilder (sortedSetBuilder : SortedSetBuilder<_>) = + checkNotNull (nameof sortedSetBuilder) sortedSetBuilder + sortedSetBuilder.ToImmutable () + + let inline ofComparer<'T> comparer = SortedSetFactory.Create<'T> (comparer = comparer) + + let inline toSeq (set : SortedSet<_>) = set :> seq<_> + + let inline toArray (set : SortedSet<_>) = + check set + Seq.toArray set + + ////////// Building ////////// + + let inline builder () = SortedSetFactory.CreateBuilder () + let inline builderWith capacity : SortedSet<'T>.Builder = SortedSetFactory.CreateBuilder (capacity) + let inline builderWithComparer comparer = SortedSetFactory.CreateBuilder (comparer) + + let inline toBuilder set : SortedSetBuilder<_> = + check set + set.ToBuilder () + + let inline keyComparer set = + check set + set.KeyComparer + + + let inline length set = + check set + set.Count + + let inline contains value set = + check set + set.Contains value + + let inline isEmpty set = + check set + set.IsEmpty + + let inline exists predicate map = + check map + map |> Seq.exists predicate + + let inline isSubset (set1 : SortedSet<_>) set2 = + check set1 + set1.IsSubsetOf set2 + + let inline isProperSubset (set1 : SortedSet<_>) set2 = + check set1 + set1.IsProperSubsetOf set2 + + let inline isSuperset (set1 : SortedSet<_>) set2 = + check set1 + set1.IsSupersetOf set2 + + let inline isProperSuperset (set1 : SortedSet<_>) set2 = + check set1 + set1.IsProperSupersetOf set2 + + let inline add value set : SortedSet<_> = + check set + set.Add (value) + + let inline union set values : SortedSet<_> = + check set + values |> set.Union + + let inline unionMany (sets : SortedSet<_> seq) = Seq.reduce union sets + let inline intersect (set1 : SortedSet<_>) set2 = set1.Intersect set2 + let inline intersectMany (sets : SortedSet<_> seq) = Seq.reduce intersect sets + + let inline remove value set : SortedSet<_> = + check set + set.Remove value + + let inline difference values set : SortedSet<_> = + check set + values |> set.Except + + let inline clear set : SortedSet<_> = + check set + set.Clear () + + let inline filter predicate set = set |> Seq.filter predicate |> empty.Union + + let inline where predicate set = set |> Seq.where predicate |> empty.Union + + let inline pick chooser set = + check set + set |> Seq.pick chooser + + let inline tryPick chooser set = + check set + set |> Seq.tryPick chooser + + let inline vTryPick chooser set = + check set + + match set |> Seq.tryPick chooser with + | Some value -> ValueSome value + | None -> ValueNone + + let inline map mapping (set : SortedSet<_>) = set |> Seq.map mapping |> ofSeq + + let inline forall predicate set = set |> Seq.forall predicate + + let inline iter action (set : SortedSet<_>) = + check set + set |> Seq.iter action diff --git a/src/FSharp.Collections.Immutable/stack.fs b/src/FSharp.Collections.Immutable/Stack.fs similarity index 61% rename from src/FSharp.Collections.Immutable/stack.fs rename to src/FSharp.Collections.Immutable/Stack.fs index 107362f..a9502e5 100644 --- a/src/FSharp.Collections.Immutable/stack.fs +++ b/src/FSharp.Collections.Immutable/Stack.fs @@ -4,17 +4,21 @@ type IStack<'T> = System.Collections.Immutable.IImmutableStack<'T> type Stack<'T> = System.Collections.Immutable.ImmutableStack<'T> -[] +[] module Stack = type internal StackFactory = System.Collections.Immutable.ImmutableStack - let inline internal check (stack : IStack<_>) = checkNotNull "stack" stack + let inline internal check (stack : IStack<_>) = checkNotNull (nameof stack) stack - let inline empty<'T> = StackFactory.Create<'T>() + let inline empty<'T> = StackFactory.Create<'T> () let inline ofSeq source = StackFactory.CreateRange source - let inline ofArray (array : 'T []) : Stack<'T> = ofSeq array + let inline ofArray (array : 'T[]) : Stack<'T> = ofSeq array + + let inline toSeq (stack : IStack<_>) = stack :> seq<_> let push head stack : IStack<'T> = check stack @@ -24,22 +28,25 @@ module Stack = let peek stack = check stack - stack.Peek() + stack.Peek () let head stack = peek stack let tail stack : IStack<_> = check stack - stack.Pop() + stack.Pop () let pop stack = check stack - stack.Peek(), tail stack + stack.Peek (), tail stack let (|Cons|Nil|) stack = check stack - if stack.IsEmpty then Nil - else Cons(stack.Peek(), stack.Pop()) + + if stack.IsEmpty then + Nil + else + Cons (stack.Peek (), stack.Pop ()) ///////////// module ImmutableStack = Stack diff --git a/src/FSharp.Collections.Immutable/immutable-list.fs b/src/FSharp.Collections.Immutable/immutable-list.fs deleted file mode 100644 index a4429a2..0000000 --- a/src/FSharp.Collections.Immutable/immutable-list.fs +++ /dev/null @@ -1,291 +0,0 @@ -#if INTERACTIVE -namespace global -#else -namespace FSharp.Collections.Immutable -open FSharp.Collections.Immutable.ImmutableCollectionUtil -#endif -open System.Collections.Immutable - -[] -module ImmutableList = - - ////////// Factory ////////// - - let inline internal check (list: IImmutableList<_>) = checkNotNull "list" list - - let inline empty<'T> = ImmutableList.Create<'T>() - - let inline singleton<'T> (item : 'T) : ImmutableList<'T> = ImmutableList.Create<'T> (item) - - let inline ofSeq (seq : 'T seq) = checkNotNull "seq" seq; ImmutableList.CreateRange seq - - let inline ofBuilder (builder : ImmutableList<_>.Builder) = builder.ToImmutable() - - let inline builder() = ImmutableList.CreateBuilder() - - open System.Collections.Generic - open System - - ////////// IReadOnly* ////////// - - let length list = check list; list.Count - - let item index list = check list; list.[index] - - ////////// ImmutableList ////////// - - let contains item (list : ImmutableList<_>) = list.Contains(item) - - let reverse (list : ImmutableList<_>) = list.Reverse() - - let reverseRange (index, count) (list : ImmutableList<_>) = list.Reverse(index, count) - - ////////// IImmutableList ////////// - - /// Replaces an element in the list at a given position with the specified element. - let withItem index value list = check list; list.SetItem(index, value) - - /// Returns a new list with the first matching element in the list replaced with the specified element with - /// the given comparer. - let replaceWith comparer oldValue value list = - check list - list.Replace(oldValue, value, comparer) - - /// Returns a new list with the first matching element in the list replaced with the specified element. - let replace oldValue value list = - replaceWith HashIdentity.Structural oldValue value list - - - /// Creates a list with all the items removed, but with the same sorting and ordering semantics as - /// this list. - let clear list = check list; list.Clear() - - /// Makes a copy of the list, and adds the specified object to the end of the copied list. - let add item list = check list; list.Add item - - - /// Makes a copy of the list and adds the specified objects to the end of the copied list. - let append list items = check list; list.AddRange items - - /// Inserts the specified element at the specified index in a immutable list. - let insert index item list = check list; list.Insert(index, item) - - /// Inserts the specified elements at the specified index in the immutable list. - let insertRange index items list = check list; list.InsertRange(index, items) // TODO: rename - - - /// Removes the first occurrence of a specified object from this immutable list using the given comparer. - let removeWith comparer item list = check list; list.Remove(item, comparer) - - /// Removes the first occurrence of a specified object from this immutable list. - let remove item list = removeWith HashIdentity.Structural item list - - - - /// Removes the specified objects from the list with the given comparer. - let exceptWith (comparer: IEqualityComparer<_>) items list = - check list - list.RemoveRange(items, comparer) - - /// Removes the specified objects from the list. - let except items list = exceptWith HashIdentity.Structural items list - - - /// Removes all the elements that do not match the conditions defined by the specified predicate. - let filter predicate list = - check list - Predicate(not << predicate) - |> list.RemoveAll - - /// Removes a range of elements from the System.Collections.Immutable.IImmutableList`1. - let removeRange index (count: int) list = check list; list.RemoveRange(index, count) - - /// Removes the element at the specified index of the immutable list. - let removeAt index list = check list; list.RemoveAt index - - /// Searches for the specified object and returns the zero-based index of the first occurrence within the range - /// of elements in the list that starts at the specified index and - /// contains the specified number of elements. - let indexRangeWith comparer index count item list = - check list; - list.IndexOf(item, index, count, comparer) - let indexRange index count item list = - indexRangeWith HashIdentity.Structural index count item list - let indexFromWith comparer index item list = - indexRangeWith comparer index (length list - index) item - let indexFrom index item list = - indexFromWith HashIdentity.Structural index item list - let indexWith comparer item list = - indexFromWith comparer 0 item list - let index item list = indexWith HashIdentity.Structural item list - - - /// Searches for the specified object and returns the zero-based index of the last occurrence within the - /// range of elements in the list that contains the specified number - /// of elements and ends at the specified index. - let lastIndexRangeWith comparer index count item list = - check list - list.LastIndexOf(item, index, count, comparer) - let lastIndexRange index count item list = - lastIndexRangeWith HashIdentity.Structural index count item list - let lastIndexFromWith comparer index item list = - lastIndexRangeWith comparer index (index + 1) item list - let lastIndexFrom index item list = - lastIndexFromWith HashIdentity.Structural index item list - let lastIndexWith comparer item list = - lastIndexFromWith comparer (length list - 1) item list - let lastIndex item list = lastIndexWith HashIdentity.Structural item list - - - ////////// Filter-based ////////// - - let filterFold (predicate: 'State -> 'T -> bool * 'State) initial list = - let state = ref initial - filter (fun item -> - let condition, state' = predicate !state item - state := state' - condition) list, !state - - let skipWhile predicate list = - let condition = ref true - filter (fun item -> - if !condition then - condition := !condition && predicate item - !condition - else false) list - - let skipUntil predicate list = skipWhile (not << predicate) list - - let takeWhile predicate list = - let condition = ref true - filter (fun item -> - if !condition then - condition := !condition && predicate item - not !condition - else true) list - let takeUntil predicate list = takeWhile (not << predicate) list - - ////////// Building ////////// - - let inline build f = - let builder = builder() - f builder - builder.ToImmutable() - - let toBuilder (list: ImmutableList<_>) = check list; list.ToBuilder() - - - let inline update f list = - let builder = toBuilder list - f builder - builder.ToImmutable() - - ////////// Loop-based ////////// - - let concat lists = - checkNotNull "lists" lists - build <| fun result -> - for list in lists do - result.AddRange list - - let map mapping list = - check list - build <| fun builder -> - for item in list do - builder.Add(mapping item) - - let choose chooser list = - check list - build <| fun builder -> - for item in list do - match chooser item with - |Some item -> builder.Add item - |None -> () - - - ////////// Based on other operations ////////// - - let isEmpty list = length list = 0 - - let take count list = - removeRange count (length list - count) list - - let skip index list = removeRange 0 index list - - let truncate count list = if count < length list then take count list else list - - let splitAt index list = take index list, skip index list - - let head list = item 0 list - - let last (list : IImmutableList<_>) = list.[length list - 1] - - let tail list = removeAt 0 list - - let tryItem index list = - if index >= length list || index < 0 then None - else Some(list.[index]) - - let tryHead list = tryItem 0 list - - let tryLast list = tryItem (length list - 1) list - - let tryTail list = if isEmpty list then None else Some <| tail list - - - let collect mapping list = concat <| map mapping list - - let cons head list = insert 0 head list - - - - - - let ofList list = ofSeq list - - //let ofArray (array: 'T array) = ImmutableList.Create<'T>(items = [||]) - - - - let init count initializer = - if count < 0 then - // throw the same exception - try - Seq.init count initializer |> ignore - with - |exn -> raise exn // get the right stack trace - build <| fun builder -> - for i = 0 to count - 1 do - builder.Add <| initializer i - - let unfold generator state = - let rec unfoldLoop state (builder: ImmutableList<_>.Builder) = - match generator state with - |Some(state, item) -> builder.Add(item); unfoldLoop state builder - |None -> () - build <| unfoldLoop state - - - ////////// Seq-based ////////// - - let find predicate list = check list; Seq.find predicate list - - let tryFind predicate list = check list; Seq.tryFind predicate list - - let findIndex predicate list = check list; Seq.findIndex predicate list - - let tryFindIndex predicate list = check list; Seq.tryFindIndex predicate list - - let pick chooser list = check list; Seq.pick chooser list - - let fold folder state list = check list; Seq.fold folder state list - - let forall predicate list = check list; Seq.forall predicate list - - let forall2 predicate (list1: IImmutableList<_>) (list2: IImmutableList<_>) = - checkNotNull "list1" list1; checkNotNull "list2" list2 - Seq.forall2 predicate list1 list2 - - let iter action list = check list; Seq.iter action list - - let toArray list = check list; Seq.toArray list diff --git a/src/FSharp.Collections.Immutable/indexed-seq.fs b/src/FSharp.Collections.Immutable/indexed-seq.fs deleted file mode 100644 index 5b1731f..0000000 --- a/src/FSharp.Collections.Immutable/indexed-seq.fs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FSharp.Collections.Immutable - -type IIndexedSeq<'T> = System.Collections.Generic.IReadOnlyList<'T> - -module IndexedSeq = - let check (seq: IIndexedSeq<_>) = checkNotNull "seq" seq - let item index seq = check seq; seq.[index] - let length seq = check seq; seq.Count - -module ReadOnlyList = IndexedSeq diff --git a/src/FSharp.Collections.Immutable/maps.fs b/src/FSharp.Collections.Immutable/maps.fs deleted file mode 100644 index 1c53dbf..0000000 --- a/src/FSharp.Collections.Immutable/maps.fs +++ /dev/null @@ -1,73 +0,0 @@ -namespace FSharp.Collections.Immutable - -type IMap<'Key, 'Value> = System.Collections.Immutable.IImmutableDictionary<'Key, 'Value> - -type HashMap<'Key, 'Value> = - System.Collections.Immutable.ImmutableDictionary<'Key, 'Value> - -type HashMapBuilder<'Key, 'Value> = HashMap<'Key, 'Value>.Builder - -type internal HashMapFactory = System.Collections.Immutable.ImmutableDictionary - -module HashMap = - let inline empty<'Key, 'Value> = HashMapFactory.Create<'Key, 'Value>() - - let inline ofSeq items = - checkNotNull "items" items - HashMapFactory.CreateRange(items) - - let inline builder() = HashMapFactory.CreateBuilder() - - let inline ofBuilder (mapBuilder: HashMapBuilder<_,_>) = - checkNotNull "mapBuilder" mapBuilder - mapBuilder.ToImmutable() - - ///////// - - let inline check (map: HashMap<_, _>) = checkNotNull "map" map - - let inline isEmpty map = check map; map.IsEmpty - let inline length map = check map; map.Count - - let inline keyComparer map = check map; map.KeyComparer - - let inline containsKey key map = check map; map.ContainsKey key - - let inline find key map = check map; map.[key] - let inline tryFind key map = - check map - let mutable value = Unchecked.defaultof<_> - if map.TryGetValue(key, &value) then Some value else None - - let inline add key value map : HashMap<_,_> = check map; map.Add(key, value) - let inline append map pairs : HashMap<_,_> = - check map - checkNotNull "pairs" pairs - map.AddRange pairs - - let inline remove key map : HashMap<_,_> = check map; map.Remove key - let inline except keys map : HashMap<_,_> = check map; map.RemoveRange keys - - let inline clear map: HashMap<_,_> = check map; map.Clear() - - let inline toBuilder map : HashMapBuilder<_,_> = check map; map.ToBuilder() - - - // consider alternate implementation using range functions - let inline filter predicate map = - let builder = toBuilder map - for kvp in map do - if predicate kvp.Key kvp.Value then - builder.Add kvp - builder.ToImmutable() - - -type SortedMap<'Key, 'Value> = - System.Collections.Immutable.ImmutableSortedDictionary<'Key, 'Value> - -type SortedMapBuilder<'Key, 'Value> = SortedMap<'Key, 'Value>.Builder - -type internal SortedMapFactory = - System.Collections.Immutable.ImmutableSortedDictionary -module SortedMap = - let inline empty<'Key, 'Value> = SortedMapFactory.Create<'Key, 'Value>() diff --git a/src/FSharp.Collections.Immutable/queue.fs b/src/FSharp.Collections.Immutable/queue.fs deleted file mode 100644 index d44ad51..0000000 --- a/src/FSharp.Collections.Immutable/queue.fs +++ /dev/null @@ -1,78 +0,0 @@ -namespace FSharp.Collections.Immutable - -type IQueue<'T> = System.Collections.Immutable.IImmutableQueue<'T> - -type Queue<'T> = System.Collections.Immutable.ImmutableQueue<'T> - -[] -module Queue = - - type internal QueueFactory = System.Collections.Immutable.ImmutableQueue - - let inline private check (queue: IQueue<_>) = checkNotNull "queue" queue - - let inline empty<'T> : Queue<'T> = QueueFactory.Create<'T>() - - let inline singleton<'T> (item : 'T) : Queue<'T> = QueueFactory.Create<'T> (item) - - let inline ofSeq(source : 'T seq) : Queue<'T> = QueueFactory.CreateRange source - - let isEmpty queue = check queue; queue.IsEmpty - - let clear queue : IQueue<_> = check queue; queue.Clear() - - let enqueue item queue : IQueue<_> = check queue; queue.Enqueue item - - let head queue = check queue; queue.Peek() - - let tail queue : IQueue<_> = check queue; queue.Dequeue() - - ////////// - - let (|Cons|Nil|) queue = // consider renaming - if isEmpty queue then Nil else Cons(head queue, tail queue) - - ////////// Predicate based ////////// - - let filter predicate queue = - let rec loop queue result = - match queue with - |Cons(head, tail) -> - loop tail <| if predicate head then enqueue head result else result - |Nil -> result - - loop queue <| clear queue - - ////////// Seq-based ////////// - - let find predicate queue = check queue; Seq.find predicate queue - let tryFind predicate queue = check queue; Seq.tryFind predicate queue - - let findIndex predicate queue = check queue; Seq.findIndex predicate queue - let tryFindIndex predicate queue = check queue; Seq.tryFindIndex predicate queue - - let pick chooser queue = check queue; Seq.pick chooser queue - let tryPick chooser queue = check queue; Seq.tryPick chooser queue - - let iter action queue = check queue; Seq.iter action queue - let iteri action queue = check queue; Seq.iteri action queue - let iter2 action (queue1: IQueue<_>) (queue2: IQueue<_>) = - checkNotNull "queue1" queue1 - checkNotNull "queue2" queue2 - Seq.iter2 action queue1 queue2 - - let fold folder state queue = check queue; Seq.fold folder state queue - - let forall predicate queue = check queue; Seq.forall predicate - - let exists predicate queue = check queue; Seq.exists predicate queue - - let reduce reduction queue = check queue; Seq.reduce reduction queue - - let inline sum queue = check queue; Seq.sum queue - let inline sumBy projection queue = check queue; Seq.sumBy projection queue - - let inline average queue = check queue; Seq.average queue - let inline averageBy projection queue = check queue; Seq.averageBy projection - -module ImmutableQueue = Queue diff --git a/src/FSharp.Collections.Immutable/sets.fs b/src/FSharp.Collections.Immutable/sets.fs deleted file mode 100644 index b2f86e5..0000000 --- a/src/FSharp.Collections.Immutable/sets.fs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FSharp.Collections.Immutable - -type ISet<'T> = System.Collections.Immutable.IImmutableSet<'T> - -type SortedSet<'T> = System.Collections.Immutable.ImmutableSortedSet<'T> - -type HashSet<'T> = System.Collections.Immutable.ImmutableHashSet<'T> diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000..e7a7266 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,7 @@ + + + + false + true + + diff --git a/tests/FSharp.Collections.Immutable.Tests/Attributes.fs b/tests/FSharp.Collections.Immutable.Tests/Attributes.fs new file mode 100644 index 0000000..923f313 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Attributes.fs @@ -0,0 +1,7 @@ +namespace FSharp.Collections.Immutable + +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] + +do () diff --git a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj new file mode 100644 index 0000000..407a068 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -0,0 +1,32 @@ + + + + net8.0 + $(AssemblyBaseName).Tests + + Exe + true + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList.fs new file mode 100644 index 0000000..351bac9 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type FlatListTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/ImmutableList.fs b/tests/FSharp.Collections.Immutable.Tests/ImmutableList.fs new file mode 100644 index 0000000..452167f --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/ImmutableList.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type ImmutableListTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedSeq.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedSeq.fs new file mode 100644 index 0000000..17294a9 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedSeq.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type IndexedSeqTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/Maps.fs b/tests/FSharp.Collections.Immutable.Tests/Maps.fs new file mode 100644 index 0000000..84f2fde --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Maps.fs @@ -0,0 +1,16 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type HashMapTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) + +[] +type SortedMapTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue.fs b/tests/FSharp.Collections.Immutable.Tests/Queue.fs new file mode 100644 index 0000000..826a1cb --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type QueueTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/Sets.fs b/tests/FSharp.Collections.Immutable.Tests/Sets.fs new file mode 100644 index 0000000..b170420 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Sets.fs @@ -0,0 +1,16 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type HashSetTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) + +[] +type SortedSetTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack.fs b/tests/FSharp.Collections.Immutable.Tests/Stack.fs new file mode 100644 index 0000000..2674f68 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type StackTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true)