From 0dad74819ef88444ab2bc2d912e6fddc18994ce1 Mon Sep 17 00:00:00 2001
From: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Date: Wed, 2 Oct 2024 23:25:20 +0100
Subject: [PATCH] Initial MVP
---
.config/dotnet-tools.json | 18 +
.editorconfig | 40 ++
.github/workflows/dotnet.yaml | 187 ++++++
.gitignore | 14 +
CONTRIBUTING.md | 55 ++
Directory.Build.props | 19 +
LICENSE | 21 +
README.md | 13 +
WoofWare.Whippet.Core/Domain.fs | 28 +
WoofWare.Whippet.Core/README.md | 19 +
WoofWare.Whippet.Core/SurfaceBaseline.txt | 11 +
.../WoofWare.Whippet.Core.fsproj | 33 +
WoofWare.Whippet.Core/version.json | 11 +
WoofWare.Whippet.Test/TestSurface.fs | 28 +
.../WoofWare.Whippet.Test.fsproj | 26 +
WoofWare.Whippet.sln | 28 +
WoofWare.Whippet/Program.fs | 175 ++++++
WoofWare.Whippet/WoofWare.Whippet.fsproj | 39 ++
WoofWare.Whippet/version.json | 12 +
analyzers/analyzers.fsproj | 16 +
flake.lock | 61 ++
flake.nix | 68 +++
nix/deps.nix | 569 ++++++++++++++++++
23 files changed, 1491 insertions(+)
create mode 100644 .config/dotnet-tools.json
create mode 100644 .editorconfig
create mode 100644 .github/workflows/dotnet.yaml
create mode 100644 .gitignore
create mode 100644 CONTRIBUTING.md
create mode 100644 Directory.Build.props
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 WoofWare.Whippet.Core/Domain.fs
create mode 100644 WoofWare.Whippet.Core/README.md
create mode 100644 WoofWare.Whippet.Core/SurfaceBaseline.txt
create mode 100644 WoofWare.Whippet.Core/WoofWare.Whippet.Core.fsproj
create mode 100644 WoofWare.Whippet.Core/version.json
create mode 100644 WoofWare.Whippet.Test/TestSurface.fs
create mode 100644 WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj
create mode 100644 WoofWare.Whippet.sln
create mode 100644 WoofWare.Whippet/Program.fs
create mode 100644 WoofWare.Whippet/WoofWare.Whippet.fsproj
create mode 100644 WoofWare.Whippet/version.json
create mode 100644 analyzers/analyzers.fsproj
create mode 100644 flake.lock
create mode 100644 flake.nix
create mode 100644 nix/deps.nix
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..383189c
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,18 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "fantomas": {
+ "version": "6.3.15",
+ "commands": [
+ "fantomas"
+ ]
+ },
+ "fsharp-analyzers": {
+ "version": "0.27.0",
+ "commands": [
+ "fsharp-analyzers"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e207c42
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,40 @@
+root=true
+
+[*]
+charset=utf-8
+trim_trailing_whitespace=true
+insert_final_newline=true
+indent_style=space
+indent_size=4
+
+# ReSharper properties
+resharper_xml_indent_size=2
+resharper_xml_max_line_length=100
+resharper_xml_tab_width=2
+
+[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
+indent_style=space
+indent_size=2
+
+[*.{fs,fsi}]
+fsharp_bar_before_discriminated_union_declaration=true
+fsharp_space_before_uppercase_invocation=true
+fsharp_space_before_class_constructor=true
+fsharp_space_before_member=true
+fsharp_space_before_colon=true
+fsharp_space_before_semicolon=true
+fsharp_multiline_bracket_style=aligned
+fsharp_newline_between_type_definition_and_members=true
+fsharp_align_function_signature_to_indentation=true
+fsharp_alternative_long_member_definitions=true
+fsharp_multi_line_lambda_closing_newline=true
+fsharp_experimental_keep_indent_in_branch=true
+fsharp_max_value_binding_width=80
+fsharp_max_record_width=0
+max_line_length=120
+end_of_line=lf
+
+[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
+indent_style=space
+indent_size=2
+tab_width=2
diff --git a/.github/workflows/dotnet.yaml b/.github/workflows/dotnet.yaml
new file mode 100644
index 0000000..d61f4d6
--- /dev/null
+++ b/.github/workflows/dotnet.yaml
@@ -0,0 +1,187 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/github-workflow.json
+name: .NET
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ NUGET_XMLDOC_MODE: ''
+ DOTNET_MULTILEVEL_LOOKUP: 0
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ config:
+ - Release
+ - Debug
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
+ - name: Install Nix
+ uses: cachix/install-nix-action@v29
+ with:
+ extra_nix_config: |
+ access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
+ - name: Restore dependencies
+ run: nix develop --command dotnet restore
+ - name: Build
+ run: nix develop --command dotnet build --no-restore --configuration ${{matrix.config}}
+ - name: Test
+ run: nix develop --command dotnet test --no-build --verbosity normal --configuration ${{matrix.config}}
+
+ analyzers:
+ runs-on: ubuntu-latest
+ permissions:
+ security-events: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
+ - name: Install Nix
+ uses: cachix/install-nix-action@v29
+ with:
+ extra_nix_config: |
+ access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
+ - name: Prepare analyzers
+ run: nix develop --command dotnet restore analyzers/analyzers.fsproj
+ - name: Build project
+ run: nix develop --command dotnet build ./WoofWare.Whippet/WoofWare.Whippet.fsproj
+ - name: Run analyzers
+ run: nix run .#fsharp-analyzers -- --project ./WoofWare.Whippet/WoofWare.Whippet.fsproj --analyzers-path ./.analyzerpackages/g-research.fsharp.analyzers/*/ --verbosity detailed --report ./analysis.sarif --treat-as-error GRA-STRING-001 GRA-STRING-002 GRA-STRING-003 GRA-UNIONCASE-001 GRA-INTERPOLATED-001 GRA-TYPE-ANNOTATE-001 GRA-VIRTUALCALL-001 GRA-IMMUTABLECOLLECTIONEQUALITY-001 GRA-JSONOPTS-001 GRA-LOGARGFUNCFULLAPP-001 GRA-DISPBEFOREASYNC-001 --exclude-analyzers PartialAppAnalyzer
+
+ build-nix:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Install Nix
+ uses: cachix/install-nix-action@v29
+ with:
+ extra_nix_config: |
+ access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
+ - name: Build
+ run: nix build
+ - name: Reproducibility check
+ run: nix build --rebuild
+
+ check-dotnet-format:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Install Nix
+ uses: cachix/install-nix-action@v29
+ with:
+ extra_nix_config: |
+ access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
+ - name: Run Fantomas
+ run: nix run .#fantomas -- --check .
+
+ check-nix-format:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Install Nix
+ uses: cachix/install-nix-action@v29
+ with:
+ extra_nix_config: |
+ access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
+ - name: Run Alejandra
+ run: nix develop --command alejandra --check .
+
+ linkcheck:
+ name: Check links
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - name: Install Nix
+ uses: cachix/install-nix-action@v29
+ with:
+ extra_nix_config: |
+ access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
+ - name: Run link checker
+ run: nix develop --command markdown-link-check README.md CONTRIBUTING.md
+
+ flake-check:
+ name: Check flake
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - name: Install Nix
+ uses: cachix/install-nix-action@v29
+ with:
+ extra_nix_config: |
+ access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
+ - name: Flake check
+ run: nix flake check
+
+ nuget-pack:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
+ - name: Install Nix
+ uses: cachix/install-nix-action@v29
+ with:
+ extra_nix_config: |
+ access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
+ - name: Restore dependencies
+ run: nix develop --command dotnet restore
+ - name: Build
+ run: nix develop --command dotnet build --no-restore --configuration Release
+ - name: Pack
+ run: nix develop --command dotnet pack --configuration Release
+ - name: Upload NuGet artifact (runner)
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-package-runner
+ path: WoofWare.Whippet/bin/Release/WoofWare.Whippet.*.nupkg
+ - name: Upload NuGet artifact (core)
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-package-core
+ path: WoofWare.Whippet.Core/bin/Release/WoofWare.Whippet.Core.*.nupkg
+
+ expected-pack:
+ needs: [nuget-pack]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download NuGet artifact (runner)
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-runner
+ path: packed-runner
+ - name: Check NuGet contents
+ # Verify that there is exactly one nupkg in the artifact that would be NuGet published
+ run: if [[ $(find packed-runner -maxdepth 1 -name 'WoofWare.Whippet.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
+ - name: Download NuGet artifact (core)
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-core
+ path: packed-core
+ - name: Check NuGet contents
+ # Verify that there is exactly one nupkg in the artifact that would be NuGet published
+ run: if [[ $(find packed-core -maxdepth 1 -name 'WoofWare.Whippet.Core.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
+
+ all-required-checks-complete:
+ needs: [check-dotnet-format, check-nix-format, build, build-nix, linkcheck, flake-check, nuget-pack, expected-pack, analyzers]
+ if: ${{ always() }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: G-Research/common-actions/check-required-lite@2b7dc49cb14f3344fbe6019c14a31165e258c059
+ with:
+ needs-context: ${{ toJSON(needs) }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e904e54
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+bin/
+obj/
+/packages/
+riderModule.iml
+/_ReSharper.Caches/
+.idea/
+*.sln.DotSettings.user
+.DS_Store
+result
+.analyzerpackages/
+analysis.sarif
+.direnv/
+.venv/
+.vs/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..b8f3b16
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,55 @@
+# Contributing
+
+The main project fork lives [on GitHub](https://github.com/Smaug123/WoofWare.Whippet).
+
+Contributions are welcome, but I am generally very opinionated about both style and content.
+I also can't commit to looking at anything in a particularly timely manner (or at all, though I expect I will try).
+
+In general my aesthetics lead me to accept correctness fixes much more readily than other changes.
+
+## Issues
+
+Please raise bug reports and feature requests as Issues on [the main GitHub project](https://github.com/Smaug123/WoofWare.Whippet/issues).
+
+## Pull requests
+
+Before embarking on a large change, I strongly recommend checking via a GitHub Issue first that I'm likely to accept it.
+
+You may find that the following guidelines will help you produce a change that I accept:
+
+* Keep your change as small and focused as is practical.
+* Ensure that your change is thoroughly tested.
+* Document any choices you make which are not immediately obvious.
+* Explain why your change is necessary or desirable.
+
+## On your first checkout
+
+There are pull request checks on this repo, enforcing [Fantomas](https://github.com/fsprojects/fantomas/)-compliant formatting according to the [G-Research style guidelines](https://github.com/G-Research/fsharp-formatting-conventions/).
+After checking out the repo, you may wish to add a pre-push hook to ensure locally that formatting is complete, rather than having to wait for the CI checks to tell you that you haven't formatted your code.
+Consider performing the following command to set this up in the repo:
+```bash
+git config core.hooksPath hooks/
+```
+Before your first push (but only once), you will need to install the [.NET local tools](https://docs.microsoft.com/en-us/dotnet/core/tools/local-tools-how-to-use) which form part of the pre-push hook:
+```bash
+dotnet tool restore
+```
+
+In future, some commits (such as big-bang formatting commits) may be recorded for convenience in `.git-blame-ignore-revs`.
+Consider performing the following command to have `git blame` ignore these commits, when we ever create any:
+```bash
+git config blame.ignoreRevsFile .git-blame-ignore-revs
+```
+
+## Dependencies
+
+I try to keep this repository's dependencies as few as possible, because (for example) any consumer of the source generator will also consume this project via the attributes.
+When adding dependencies, you will need to `nix run .#fetchDeps` to obtain a new copy of [the dependency lockfile](./nix/deps.nix).
+
+## Branch strategy
+
+Releases are made from the `main` branch.
+
+## License
+
+This project is licensed with the MIT license, a copy of which you can find at the repository root.
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..4ea13b0
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,19 @@
+
+
+ embedded
+ true
+ [UNDEFINED]
+ true
+ true
+ true
+ embedded
+ FS3388,FS3559
+
+
+
+
+
+
+ true
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..605abb0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Patrick Stevens
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..89c2554
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# WoofWare.Whippet
+
+Whippet is a source generator for F#, inspired by [Myriad](https://github.com/MoiraeSoftware/myriad).
+
+It is currently vapourware; please do not use it.
+With some modest changes to [WoofWare.Myriad.Plugins](https://github.com/Smaug123/WoofWare.Myriad/) I was able to use Whippet to generate source files in that repo.
+However, it currently lacks any of Myriad's ease of invocation, and indeed any of the future features intended to distinguish Whippet from Myriad.
+
+Differentiating features:
+
+* Whippet expands the range of information available to a source-generating plugin. Eventually we intend for it to supply type-checking information.
+* Whippet will eventually support the Fantomas [Oak](https://fsprojects.github.io/fantomas/docs/end-users/GeneratingCode.html) format, rather than just a plain AST.
+* Whippet is intended to be more modular, providing various different helper assemblies a plugin author can optionally use depending on what features they want.
diff --git a/WoofWare.Whippet.Core/Domain.fs b/WoofWare.Whippet.Core/Domain.fs
new file mode 100644
index 0000000..90da65d
--- /dev/null
+++ b/WoofWare.Whippet.Core/Domain.fs
@@ -0,0 +1,28 @@
+namespace WoofWare.Whippet.Core
+
+(*
+These types should take no dependencies and should only change additively; otherwise consumers will break!
+*)
+
+/// When decorating a type, indicates that the type contains Whippet generators.
+///
+/// If you don't want to take a dependency on WoofWare.Whippet.Core, you can define your own attribute with this name,
+/// and we'll detect it happily when running the plugin.
+type WhippetGeneratorAttribute () =
+ inherit System.Attribute ()
+
+/// The arguments we'll give you (a plugin) when we call you to generate some code from raw file input.
+type RawSourceGenerationArgs =
+ {
+ /// Full path to the file, on disk, which you're taking as input.
+ FilePath : string
+ /// Contents of the file; you might want to `System.Text.Encoding.UTF8.GetString` this.
+ FileContents : byte[]
+ }
+
+/// We provide this interface as a helper to give you compile-time safety, but you don't have to use it.
+/// At runtime, we'll find any member with the right name and signature.
+/// You must use `RawSourceGenerationArgs`, though!
+type IGenerateRawFromRaw =
+ /// Return `null` to indicate "I don't want to do any updates".
+ abstract member GenerateRawFromRaw : RawSourceGenerationArgs -> string
diff --git a/WoofWare.Whippet.Core/README.md b/WoofWare.Whippet.Core/README.md
new file mode 100644
index 0000000..1d834e1
--- /dev/null
+++ b/WoofWare.Whippet.Core/README.md
@@ -0,0 +1,19 @@
+# WoofWare.Whippet.Core
+
+This library defines the types you will need to create a plugin that works with the WoofWare.Whippet source generator,
+as well as some types which you may find convenient for this purpose.
+
+To the greatest extent possible, WoofWare.Whippet is structured so that if you wish, you do not need to use this library.
+However, there are some types you *must* use.
+
+## Mandatory types
+
+When defining any method which performs source generation, you must use the appropriate input type
+(such as `RawSourceGenerationArgs`).
+We try *very hard* to ensure we never break backward compatibility, so you should safely be able to use old versions of
+WoofWare.Whippet.Core even with new versions of the WoofWare.Whippet generator.
+
+## Optional types
+
+* You must decorate your plugin types with an attribute named `[]`. We supply one you can use.
+* We supply interfaces from which you can inherit, to ensure that you are providing members of the right type signature.
diff --git a/WoofWare.Whippet.Core/SurfaceBaseline.txt b/WoofWare.Whippet.Core/SurfaceBaseline.txt
new file mode 100644
index 0000000..e3ee1b6
--- /dev/null
+++ b/WoofWare.Whippet.Core/SurfaceBaseline.txt
@@ -0,0 +1,11 @@
+WoofWare.Whippet.Core.IGenerateRawFromRaw - interface with 1 member(s)
+WoofWare.Whippet.Core.IGenerateRawFromRaw.GenerateRawFromRaw [method]: WoofWare.Whippet.Core.RawSourceGenerationArgs -> string
+WoofWare.Whippet.Core.RawSourceGenerationArgs inherit obj, implements WoofWare.Whippet.Core.RawSourceGenerationArgs System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Whippet.Core.RawSourceGenerationArgs System.IComparable, System.IComparable, System.Collections.IStructuralComparable
+WoofWare.Whippet.Core.RawSourceGenerationArgs..ctor [constructor]: (string, System.Byte [])
+WoofWare.Whippet.Core.RawSourceGenerationArgs.Equals [method]: (WoofWare.Whippet.Core.RawSourceGenerationArgs, System.Collections.IEqualityComparer) -> bool
+WoofWare.Whippet.Core.RawSourceGenerationArgs.FileContents [property]: [read-only] System.Byte []
+WoofWare.Whippet.Core.RawSourceGenerationArgs.FilePath [property]: [read-only] string
+WoofWare.Whippet.Core.RawSourceGenerationArgs.get_FileContents [method]: unit -> System.Byte []
+WoofWare.Whippet.Core.RawSourceGenerationArgs.get_FilePath [method]: unit -> string
+WoofWare.Whippet.Core.WhippetGeneratorAttribute inherit System.Attribute
+WoofWare.Whippet.Core.WhippetGeneratorAttribute..ctor [constructor]: unit
\ No newline at end of file
diff --git a/WoofWare.Whippet.Core/WoofWare.Whippet.Core.fsproj b/WoofWare.Whippet.Core/WoofWare.Whippet.Core.fsproj
new file mode 100644
index 0000000..68cc50b
--- /dev/null
+++ b/WoofWare.Whippet.Core/WoofWare.Whippet.Core.fsproj
@@ -0,0 +1,33 @@
+
+
+
+ netstandard2.0
+ true
+ Patrick Stevens
+ Copyright (c) Patrick Stevens 2024
+ Core library types to allow you to use the WoofWare.Whippet source generator.
+ git
+ https://github.com/Smaug123/WoofWare.Whippet
+ MIT
+ README.md
+ fsharp;source-generator;source-gen
+ true
+ FS3559
+ WoofWare.Whippet.Core
+
+
+
+
+
+
+
+ True
+ \
+
+
+
+
+
+
+
+
diff --git a/WoofWare.Whippet.Core/version.json b/WoofWare.Whippet.Core/version.json
new file mode 100644
index 0000000..790e212
--- /dev/null
+++ b/WoofWare.Whippet.Core/version.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.1",
+ "publicReleaseRefSpec": [
+ "^refs/heads/main$"
+ ],
+ "pathFilters": [
+ "./",
+ ":/global.json",
+ ":/Directory.Build.props"
+ ]
+}
diff --git a/WoofWare.Whippet.Test/TestSurface.fs b/WoofWare.Whippet.Test/TestSurface.fs
new file mode 100644
index 0000000..163aadf
--- /dev/null
+++ b/WoofWare.Whippet.Test/TestSurface.fs
@@ -0,0 +1,28 @@
+namespace WoofWare.Whippet.Test
+
+open NUnit.Framework
+open WoofWare.Whippet.Core
+open ApiSurface
+
+[]
+module TestSurface =
+
+ let coreAssembly = typeof.Assembly
+
+ []
+ let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical coreAssembly
+
+ (*
+ []
+ // https://github.com/nunit/nunit3-vs-adapter/issues/876
+ let CheckVersionAgainstRemote () =
+ MonotonicVersion.validate assembly "WoofWare.Myriad.Core"
+ *)
+
+ []
+ let ``Update API surface: core`` () =
+ ApiSurface.writeAssemblyBaseline coreAssembly
+
+ []
+ let ``Ensure public API is fully documented: core`` () =
+ DocCoverage.assertFullyDocumented coreAssembly
diff --git a/WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj b/WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj
new file mode 100644
index 0000000..751b43a
--- /dev/null
+++ b/WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj
@@ -0,0 +1,26 @@
+
+
+
+ net8.0
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WoofWare.Whippet.sln b/WoofWare.Whippet.sln
new file mode 100644
index 0000000..00c7d31
--- /dev/null
+++ b/WoofWare.Whippet.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet", "WoofWare.Whippet\WoofWare.Whippet.fsproj", "{85B34488-847A-4784-8001-E4D1715F5ADD}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Core", "WoofWare.Whippet.Core\WoofWare.Whippet.Core.fsproj", "{BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Test", "WoofWare.Whippet.Test\WoofWare.Whippet.Test.fsproj", "{A2E2A639-D17D-426D-B424-0139B4DBCF8F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {85B34488-847A-4784-8001-E4D1715F5ADD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {85B34488-847A-4784-8001-E4D1715F5ADD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {85B34488-847A-4784-8001-E4D1715F5ADD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {85B34488-847A-4784-8001-E4D1715F5ADD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A2E2A639-D17D-426D-B424-0139B4DBCF8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A2E2A639-D17D-426D-B424-0139B4DBCF8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A2E2A639-D17D-426D-B424-0139B4DBCF8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A2E2A639-D17D-426D-B424-0139B4DBCF8F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/WoofWare.Whippet/Program.fs b/WoofWare.Whippet/Program.fs
new file mode 100644
index 0000000..acdec5a
--- /dev/null
+++ b/WoofWare.Whippet/Program.fs
@@ -0,0 +1,175 @@
+namespace WoofWare.Whippet
+
+open System
+open System.IO
+open System.Reflection
+open Ionide.ProjInfo
+open Ionide.ProjInfo.Types
+
+open WoofWare.Whippet.Core
+
+type Args =
+ {
+ PluginDll : FileInfo
+ InputFile : FileInfo
+ }
+
+type WhippetTarget =
+ {
+ InputSource : FileInfo
+ GeneratedDest : FileInfo
+ }
+
+module Program =
+ let parseArgs (argv : string array) =
+ let inputFile = argv.[0] |> FileInfo
+ let pluginDll = argv.[1] |> FileInfo
+
+ {
+ InputFile = inputFile
+ PluginDll = pluginDll
+ }
+
+ let getGenerateRawFromRaw (host : obj) : (RawSourceGenerationArgs -> string) option =
+ let pluginType = host.GetType ()
+
+ let generateRawFromRaw =
+ match
+ pluginType.GetMethod (
+ "GenerateRawFromRaw",
+ BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.FlattenHierarchy
+ )
+ |> Option.ofObj
+ with
+ | None ->
+ pluginType.GetInterfaces ()
+ |> Array.tryPick (fun interf ->
+ interf.GetMethod (
+ "GenerateRawFromRaw",
+ BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.FlattenHierarchy
+ )
+ |> Option.ofObj
+ )
+ | Some generateRawFromRaw -> Some generateRawFromRaw
+
+ match generateRawFromRaw with
+ | None -> None
+ | Some generateRawFromRaw ->
+ let pars = generateRawFromRaw.GetParameters ()
+
+ if pars.Length <> 1 then
+ failwith
+ $"Expected GenerateRawFromRaw to take exactly one parameter: a RawSourceGenerationArgs. Got %i{pars.Length}"
+
+ if pars.[0].ParameterType <> typeof then
+ failwith
+ $"Expected GenerateRawFromRaw to take exactly one parameter: a RawSourceGenerationArgs. Got %s{pars.[0].ParameterType.FullName}"
+
+ let retType = generateRawFromRaw.ReturnType
+
+ if retType <> typeof then
+ failwith
+ $"Expected GenerateRawFromRaw method to have return type `string`, but was: %s{retType.FullName}"
+
+ fun args -> generateRawFromRaw.Invoke (host, [| args |]) |> unbox
+ |> Some
+
+ []
+ let main argv =
+ let args = parseArgs argv
+
+ let projectDirectory = args.InputFile.Directory
+ let toolsPath = Init.init projectDirectory None
+ let defaultLoader = WorkspaceLoader.Create (toolsPath, [])
+
+ use subscription =
+ defaultLoader.Notifications.Subscribe (fun msg ->
+ match msg with
+ | WorkspaceProjectState.Loading projectFilePath ->
+ Console.Error.WriteLine $"Loading: %s{projectFilePath}"
+ | WorkspaceProjectState.Loaded (loadedProject, _knownProjects, fromCache) ->
+ let fromCache = if fromCache then " (from cache)" else ""
+ Console.Error.WriteLine $"Loaded %s{loadedProject.ProjectFileName}%s{fromCache}"
+ | WorkspaceProjectState.Failed (projectFilePath, errors) ->
+ let errors = errors.ToString ()
+ failwith $"Failed to load project %s{projectFilePath}: %s{errors}"
+ )
+
+ let projectOptions =
+ defaultLoader.LoadProjects ([ args.InputFile.FullName ]) |> Seq.toArray
+
+ let desiredProject =
+ projectOptions
+ |> Array.find (fun po -> po.ProjectFileName = args.InputFile.FullName)
+
+ let toGenerate =
+ desiredProject.Items
+ |> List.choose (fun (ProjectItem.Compile (name, fullPath, metadata)) ->
+ match metadata with
+ | None -> None
+ | Some metadata ->
+
+ match Map.tryFind "WhippetFile" metadata with
+ | None -> None
+ | Some myriadFile ->
+
+ {
+ GeneratedDest = FileInfo fullPath
+ InputSource =
+ FileInfo (Path.Combine (Path.GetDirectoryName desiredProject.ProjectFileName, myriadFile))
+ }
+ |> Some
+ )
+
+ Console.Error.WriteLine $"Loading plugin: %s{args.PluginDll.FullName}"
+ let pluginAssembly = Assembly.LoadFrom args.PluginDll.FullName
+ // We will look up any member called GenerateRawFromRaw and/or GenerateFromRaw.
+ // It's your responsibility to decide whether to do anything with this call; you return null if you don't want
+ // to do anything.
+ // Alternatively, return the text you want to output.
+ // We provide you with the input file contents.
+ // GenerateRawFromRaw should return plain text.
+ // GenerateFromRaw should return a Fantomas AST.
+ let applicablePlugins =
+ pluginAssembly.ExportedTypes
+ |> Seq.choose (fun ty ->
+ if
+ ty.CustomAttributes
+ |> Seq.exists (fun attr -> attr.AttributeType.Name = typeof.Name)
+ then
+ Some (ty, Activator.CreateInstance ty)
+ else
+ None
+ )
+ |> Seq.toList
+
+ for item in toGenerate do
+ use output = item.GeneratedDest.Open (FileMode.Create, FileAccess.Write)
+ use outputWriter = new StreamWriter (output, leaveOpen = true)
+
+ for plugin, hostClass in applicablePlugins do
+ match getGenerateRawFromRaw hostClass with
+ | None -> ()
+ | Some generateRawFromRaw ->
+ let fileContents = File.ReadAllBytes item.InputSource.FullName
+
+ let args =
+ {
+ RawSourceGenerationArgs.FilePath = item.InputSource.FullName
+ FileContents = fileContents
+ }
+
+ let result = generateRawFromRaw args
+
+ match result with
+ | null -> ()
+ | result ->
+ Console.Error.WriteLine
+ $"Writing output for generator %s{plugin.Name} to file %s{item.GeneratedDest.FullName}"
+
+ outputWriter.Write result
+ outputWriter.Write "\n"
+
+ ()
+
+ 0
diff --git a/WoofWare.Whippet/WoofWare.Whippet.fsproj b/WoofWare.Whippet/WoofWare.Whippet.fsproj
new file mode 100644
index 0000000..4f1a25e
--- /dev/null
+++ b/WoofWare.Whippet/WoofWare.Whippet.fsproj
@@ -0,0 +1,39 @@
+
+
+
+ Exe
+ net8.0
+ Patrick Stevens
+ Copyright (c) Patrick Stevens 2024
+ A source generator for F#.
+ git
+ https://github.com/Smaug123/WoofWare.Whippet
+ MIT
+ README.md
+ fsharp;source-generator;source-gen
+ true
+ FS3559
+ WoofWare.Whippet
+
+
+
+
+
+ True
+ \
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WoofWare.Whippet/version.json b/WoofWare.Whippet/version.json
new file mode 100644
index 0000000..ad147c0
--- /dev/null
+++ b/WoofWare.Whippet/version.json
@@ -0,0 +1,12 @@
+{
+ "version": "0.1",
+ "publicReleaseRefSpec": [
+ "^refs/heads/main$"
+ ],
+ "pathFilters": [
+ "./",
+ ":/WoofWare.Whippet.Core/",
+ ":/global.json",
+ ":/Directory.Build.props"
+ ]
+}
diff --git a/analyzers/analyzers.fsproj b/analyzers/analyzers.fsproj
new file mode 100644
index 0000000..9d5d0c3
--- /dev/null
+++ b/analyzers/analyzers.fsproj
@@ -0,0 +1,16 @@
+
+
+
+ false
+ false
+ ../.analyzerpackages/
+ net6.0
+ true
+ false
+
+
+
+
+
+
+
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..1f3d98c
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1726560853,
+ "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1727811607,
+ "narHash": "sha256-2ByOBflaIUJKeF9q6efVcYHljZXGZ7MnCWtseRvmpm8=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "1839883cd0068572aed75fb9442b508bbd9ef09c",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..e8f7b6b
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,68 @@
+{
+ description = "Source generators for F#";
+
+ inputs = {
+ flake-utils.url = "github:numtide/flake-utils";
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+ };
+
+ outputs = {
+ nixpkgs,
+ flake-utils,
+ ...
+ }:
+ flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = nixpkgs.legacyPackages.${system};
+ pname = "WoofWare.Whippet";
+ dotnet-sdk = pkgs.dotnet-sdk_8;
+ dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0;
+ version = "0.1";
+ dotnetTool = dllOverride: toolName: toolVersion: hash:
+ pkgs.stdenvNoCC.mkDerivation rec {
+ name = toolName;
+ version = toolVersion;
+ nativeBuildInputs = [pkgs.makeWrapper];
+ src = pkgs.fetchNuGet {
+ pname = name;
+ version = version;
+ hash = hash;
+ installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin'';
+ };
+ installPhase = let
+ dll =
+ if isNull dllOverride
+ then name
+ else dllOverride;
+ in ''
+ runHook preInstall
+ mkdir -p "$out/lib"
+ cp -r ./bin/* "$out/lib"
+ makeWrapper "${dotnet-runtime}/bin/dotnet" "$out/bin/${name}" --add-flags "$out/lib/${dll}.dll"
+ runHook postInstall
+ '';
+ };
+ in {
+ packages = {
+ fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).hash;
+ fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version (builtins.head (builtins.filter (elem: elem.pname == "fsharp-analyzers") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).hash;
+ default = pkgs.buildDotnetModule {
+ inherit pname version dotnet-sdk dotnet-runtime;
+ name = "WoofWare.Whippet";
+ src = ./.;
+ projectFile = "./WoofWare.Whippet/WoofWare.Whippet.fsproj";
+ testProjectFile = "./WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj";
+ disabledTests = ["WoofWare.Whippet.Test.TestSurface.CheckVersionAgainstRemote"];
+ nugetDeps = ./nix/deps.nix; # `nix build .#default.passthru.fetch-deps && ./result nix/deps.nix`
+ doCheck = true;
+ };
+ };
+ devShell = pkgs.mkShell {
+ buildInputs = [dotnet-sdk];
+ packages = [
+ pkgs.alejandra
+ pkgs.nodePackages.markdown-link-check
+ pkgs.shellcheck
+ ];
+ };
+ });
+}
diff --git a/nix/deps.nix b/nix/deps.nix
new file mode 100644
index 0000000..fa38e65
--- /dev/null
+++ b/nix/deps.nix
@@ -0,0 +1,569 @@
+# This file was automatically generated by passthru.fetch-deps.
+# Please dont edit it manually, your changes might get overwritten!
+{fetchNuGet}: [
+ (fetchNuGet {
+ pname = "ApiSurface";
+ version = "4.1.5";
+ hash = "sha256-Kbt18XLk1gvZfzGca885HaXZB119APay85KzI546PYM=";
+ })
+ (fetchNuGet {
+ pname = "fantomas";
+ version = "6.3.15";
+ hash = "sha256-Gjw7MxjUNckMWSfnOye4UTe5fZWnor6RHCls3PNsuG8=";
+ })
+ (fetchNuGet {
+ pname = "fsharp-analyzers";
+ version = "0.27.0";
+ hash = "sha256-QhLi2veTY1wZlQKJLTyVPgx/ImkaZugQNjSN5VJCNEA=";
+ })
+ (fetchNuGet {
+ pname = "FSharp.Core";
+ version = "4.3.4";
+ hash = "sha256-styyo+6mJy+yxE0NZG/b1hxkAjPOnJfMgd9zWzCJ5uk=";
+ })
+ (fetchNuGet {
+ pname = "FSharp.Core";
+ version = "8.0.400";
+ hash = "sha256-wlrcAjjvI5YtnHR7kFH8uRUA4GomJYmqr41K5LYjCGs=";
+ })
+ (fetchNuGet {
+ pname = "FsUnit";
+ version = "6.0.1";
+ hash = "sha256-vka/aAgWhDCl5tu+kgO7GtSaHOOvlSaWxG+tExwGXpI=";
+ })
+ (fetchNuGet {
+ pname = "Ionide.ProjInfo";
+ version = "0.67.0";
+ hash = "sha256-brgOnch4WuVe5TLx0RCa0wLDs3/Vu4HZP04YZyuAFYM=";
+ })
+ (fetchNuGet {
+ pname = "Ionide.ProjInfo.Sln";
+ version = "0.67.0";
+ hash = "sha256-PyDq0Efv/vussW2Bgy+xl05SuyELH5NcYUMfSks4bT0=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.Build";
+ version = "17.2.0";
+ hash = "sha256-JzPqbxFyotNhSr5tokVevdqB9+nJKx4YH2hPkC05GiY=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.Build.Framework";
+ version = "17.2.0";
+ hash = "sha256-jG+p2tlyX5nWT4pcmgIC4M8LNruKLSZ2+I29S/ZI/yE=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.CodeCoverage";
+ version = "17.11.1";
+ hash = "sha256-1dLlK3NGh88PuFYZiYpT+izA96etxhU3BSgixDgdtGA=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.NET.StringTools";
+ version = "1.0.0";
+ hash = "sha256-smmwm1XbKsk0SPW74rd2uDubWzfd7RhfSkPr932cyhs=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.NET.Test.Sdk";
+ version = "17.11.1";
+ hash = "sha256-0JUEucQ2lzaPgkrjm/NFLBTbqU1dfhvhN3Tl3moE6mI=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.NETCore.Platforms";
+ version = "1.0.1";
+ hash = "sha256-mZotlGZqtrqDSoBrZhsxFe6fuOv5/BIo0w2Z2x0zVAU=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.NETCore.Platforms";
+ version = "1.1.0";
+ hash = "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.NETCore.Platforms";
+ version = "2.0.0";
+ hash = "sha256-IEvBk6wUXSdyCnkj6tHahOJv290tVVT8tyemYcR0Yro=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.NETCore.Platforms";
+ version = "3.1.0";
+ hash = "sha256-cnygditsEaU86bnYtIthNMymAHqaT/sf9Gjykhzqgb0=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.NETCore.Targets";
+ version = "1.0.1";
+ hash = "sha256-lxxw/Gy32xHi0fLgFWNj4YTFBSBkjx5l6ucmbTyf7V4=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.NETCore.Targets";
+ version = "1.1.0";
+ hash = "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.TestPlatform.ObjectModel";
+ version = "17.11.1";
+ hash = "sha256-5vX+vCzFY3S7xfMVIv8OlMMFtdedW9UIJzc0WEc+vm4=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.TestPlatform.TestHost";
+ version = "17.11.1";
+ hash = "sha256-wSkY0H1fQAq0H3LcKT4u7Y5RzhAAPa6yueVN84g8HxU=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.Win32.Registry";
+ version = "4.3.0";
+ hash = "sha256-50XwFbyRfZkTD/bBn76WV/NIpOy/mzXD3MMEVFX/vr8=";
+ })
+ (fetchNuGet {
+ pname = "Microsoft.Win32.SystemEvents";
+ version = "4.7.0";
+ hash = "sha256-GHxnD1Plb32GJWVWSv0Y51Kgtlb+cdKgOYVBYZSgVF4=";
+ })
+ (fetchNuGet {
+ pname = "Nerdbank.GitVersioning";
+ version = "3.6.143";
+ hash = "sha256-OhOtMzP+2obDIR+npR7SsoXo0KrmcsL+VCE8Z3t5gzQ=";
+ })
+ (fetchNuGet {
+ pname = "NETStandard.Library";
+ version = "2.0.3";
+ hash = "sha256-Prh2RPebz/s8AzHb2sPHg3Jl8s31inv9k+Qxd293ybo=";
+ })
+ (fetchNuGet {
+ pname = "Newtonsoft.Json";
+ version = "13.0.1";
+ hash = "sha256-K2tSVW4n4beRPzPu3rlVaBEMdGvWSv/3Q1fxaDh4Mjo=";
+ })
+ (fetchNuGet {
+ pname = "Newtonsoft.Json";
+ version = "13.0.3";
+ hash = "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc=";
+ })
+ (fetchNuGet {
+ pname = "NuGet.Common";
+ version = "6.11.0";
+ hash = "sha256-eb7G07RyZv4AQT6ItRqdBuUf9e9BXcQygsy5RNEXfNE=";
+ })
+ (fetchNuGet {
+ pname = "NuGet.Configuration";
+ version = "6.11.0";
+ hash = "sha256-2SNZkX64SB15glzQx3k+vI7btr8Yqg4CayaaaK1B0AQ=";
+ })
+ (fetchNuGet {
+ pname = "NuGet.Frameworks";
+ version = "6.11.0";
+ hash = "sha256-8DC7V2IlCjiMDQ9yWbl7QQHia6OpBrbWh5rL0qa0Opw=";
+ })
+ (fetchNuGet {
+ pname = "NuGet.Frameworks";
+ version = "6.11.1";
+ hash = "sha256-p25Oa7wJjwF+2puIhBkZZkKSuk4jGq7xikYSCdfp9Vc=";
+ })
+ (fetchNuGet {
+ pname = "NuGet.Packaging";
+ version = "6.11.0";
+ hash = "sha256-LVLvxcB6SMdayxAsrc5bCuLLt25fqPr6KfYcYoWWIQk=";
+ })
+ (fetchNuGet {
+ pname = "NuGet.Protocol";
+ version = "6.11.0";
+ hash = "sha256-3vdB/8IiJ2LMHhFXLWOzf0H59Ow/zcoq6W4uCHbihCQ=";
+ })
+ (fetchNuGet {
+ pname = "NuGet.Versioning";
+ version = "6.11.0";
+ hash = "sha256-03edgWvbqUtbzpBBTIxTwsSRoj1T2muGVL+vTuIHXag=";
+ })
+ (fetchNuGet {
+ pname = "NUnit";
+ version = "4.2.2";
+ hash = "sha256-+0OS67ITalmG9arYCgQF/+YbmPRnB3pIIykew0kvoCc=";
+ })
+ (fetchNuGet {
+ pname = "NUnit3TestAdapter";
+ version = "4.6.0";
+ hash = "sha256-9Yav2fYhC4w0OgsyUwU4/5rDy4FVDTpKnWHuwl/uKJQ=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Collections";
+ version = "4.3.0";
+ hash = "sha256-4PGZqyWhZ6/HCTF2KddDsbmTTjxs2oW79YfkberDZS8=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Globalization";
+ version = "4.3.0";
+ hash = "sha256-PaiITTFI2FfPylTEk7DwzfKeiA/g/aooSU1pDcdwWLU=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.IO";
+ version = "4.3.0";
+ hash = "sha256-vej7ySRhyvM3pYh/ITMdC25ivSd0WLZAaIQbYj/6HVE=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Reflection";
+ version = "4.3.0";
+ hash = "sha256-ns6f++lSA+bi1xXgmW1JkWFb2NaMD+w+YNTfMvyAiQk=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Reflection.Primitives";
+ version = "4.3.0";
+ hash = "sha256-LkPXtiDQM3BcdYkAm5uSNOiz3uF4J45qpxn5aBiqNXQ=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Resources.ResourceManager";
+ version = "4.3.0";
+ hash = "sha256-9EvnmZslLgLLhJ00o5MWaPuJQlbUFcUF8itGQNVkcQ4=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Runtime";
+ version = "4.3.0";
+ hash = "sha256-qwhNXBaJ1DtDkuRacgHwnZmOZ1u9q7N8j0cWOLYOELM=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Runtime.Handles";
+ version = "4.3.0";
+ hash = "sha256-PQRACwnSUuxgVySO1840KvqCC9F8iI9iTzxNW0RcBS4=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Runtime.InteropServices";
+ version = "4.3.0";
+ hash = "sha256-Kaw5PnLYIiqWbsoF3VKJhy7pkpoGsUwn4ZDCKscbbzA=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Text.Encoding";
+ version = "4.3.0";
+ hash = "sha256-Q18B9q26MkWZx68exUfQT30+0PGmpFlDgaF0TnaIGCs=";
+ })
+ (fetchNuGet {
+ pname = "runtime.any.System.Threading.Tasks";
+ version = "4.3.0";
+ hash = "sha256-agdOM0NXupfHbKAQzQT8XgbI9B8hVEh+a/2vqeHctg4=";
+ })
+ (fetchNuGet {
+ pname = "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-LXUPLX3DJxsU1Pd3UwjO1PO9NM2elNEDXeu2Mu/vNps=";
+ })
+ (fetchNuGet {
+ pname = "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-qeSqaUI80+lqw5MK4vMpmO0CZaqrmYktwp6L+vQAb0I=";
+ })
+ (fetchNuGet {
+ pname = "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-SrHqT9wrCBsxILWtaJgGKd6Odmxm8/Mh7Kh0CUkZVzA=";
+ })
+ (fetchNuGet {
+ pname = "runtime.native.System";
+ version = "4.3.0";
+ hash = "sha256-ZBZaodnjvLXATWpXXakFgcy6P+gjhshFXmglrL5xD5Y=";
+ })
+ (fetchNuGet {
+ pname = "runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-Jy01KhtcCl2wjMpZWH+X3fhHcVn+SyllWFY8zWlz/6I=";
+ })
+ (fetchNuGet {
+ pname = "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-wyv00gdlqf8ckxEdV7E+Ql9hJIoPcmYEuyeWb5Oz3mM=";
+ })
+ (fetchNuGet {
+ pname = "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-zi+b4sCFrA9QBiSGDD7xPV27r3iHGlV99gpyVUjRmc4=";
+ })
+ (fetchNuGet {
+ pname = "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-gybQU6mPgaWV3rBG2dbH6tT3tBq8mgze3PROdsuWnX0=";
+ })
+ (fetchNuGet {
+ pname = "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-VsP72GVveWnGUvS/vjOQLv1U80H2K8nZ4fDAmI61Hm4=";
+ })
+ (fetchNuGet {
+ pname = "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-4yKGa/IrNCKuQ3zaDzILdNPD32bNdy6xr5gdJigyF5g=";
+ })
+ (fetchNuGet {
+ pname = "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-HmdJhhRsiVoOOCcUvAwdjpMRiyuSwdcgEv2j9hxi+Zc=";
+ })
+ (fetchNuGet {
+ pname = "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl";
+ version = "4.3.0";
+ hash = "sha256-pVFUKuPPIx0edQKjzRon3zKq8zhzHEzko/lc01V/jdw=";
+ })
+ (fetchNuGet {
+ pname = "runtime.unix.System.Private.Uri";
+ version = "4.3.0";
+ hash = "sha256-c5tXWhE/fYbJVl9rXs0uHh3pTsg44YD1dJvyOA0WoMs=";
+ })
+ (fetchNuGet {
+ pname = "runtime.unix.System.Runtime.Extensions";
+ version = "4.3.0";
+ hash = "sha256-l8S9gt6dk3qYG6HYonHtdlYtBKyPb29uQ6NDjmrt3V4=";
+ })
+ (fetchNuGet {
+ pname = "SemanticVersioning";
+ version = "2.0.2";
+ hash = "sha256-d5tUJshDHk/rhNqt7Rl9S/Fg526el1faeanNHKcqtAg=";
+ })
+ (fetchNuGet {
+ pname = "System.Collections";
+ version = "4.0.11";
+ hash = "sha256-puoFMkx4Z55C1XPxNw3np8nzNGjH+G24j43yTIsDRL0=";
+ })
+ (fetchNuGet {
+ pname = "System.Collections";
+ version = "4.3.0";
+ hash = "sha256-afY7VUtD6w/5mYqrce8kQrvDIfS2GXDINDh73IjxJKc=";
+ })
+ (fetchNuGet {
+ pname = "System.Collections.Immutable";
+ version = "5.0.0";
+ hash = "sha256-GdwSIjLMM0uVfE56VUSLVNgpW0B//oCeSFj8/hSlbM8=";
+ })
+ (fetchNuGet {
+ pname = "System.Configuration.ConfigurationManager";
+ version = "4.7.0";
+ hash = "sha256-rYjp/UmagI4ZULU1ocia/AiXxLNL8uhMV8LBF4QFW10=";
+ })
+ (fetchNuGet {
+ pname = "System.Drawing.Common";
+ version = "4.7.0";
+ hash = "sha256-D3qG+xAe78lZHvlco9gHK2TEAM370k09c6+SQi873Hk=";
+ })
+ (fetchNuGet {
+ pname = "System.Formats.Asn1";
+ version = "6.0.0";
+ hash = "sha256-KaMHgIRBF7Nf3VwOo+gJS1DcD+41cJDPWFh+TDQ8ee8=";
+ })
+ (fetchNuGet {
+ pname = "System.Globalization";
+ version = "4.0.11";
+ hash = "sha256-rbSgc2PIEc2c2rN6LK3qCREAX3DqA2Nq1WcLrZYsDBw=";
+ })
+ (fetchNuGet {
+ pname = "System.Globalization";
+ version = "4.3.0";
+ hash = "sha256-caL0pRmFSEsaoeZeWN5BTQtGrAtaQPwFi8YOZPZG5rI=";
+ })
+ (fetchNuGet {
+ pname = "System.IO";
+ version = "4.1.0";
+ hash = "sha256-V6oyQFwWb8NvGxAwvzWnhPxy9dKOfj/XBM3tEC5aHrw=";
+ })
+ (fetchNuGet {
+ pname = "System.IO";
+ version = "4.3.0";
+ hash = "sha256-ruynQHekFP5wPrDiVyhNiRIXeZ/I9NpjK5pU+HPDiRY=";
+ })
+ (fetchNuGet {
+ pname = "System.IO.Abstractions";
+ version = "4.2.13";
+ hash = "sha256-nkC/PiqE6+c1HJ2yTwg3x+qdBh844Z8n3ERWDW8k6Gg=";
+ })
+ (fetchNuGet {
+ pname = "System.IO.FileSystem.AccessControl";
+ version = "4.5.0";
+ hash = "sha256-ck44YBQ0M+2Im5dw0VjBgFD1s0XuY54cujrodjjSBL8=";
+ })
+ (fetchNuGet {
+ pname = "System.Memory";
+ version = "4.5.4";
+ hash = "sha256-3sCEfzO4gj5CYGctl9ZXQRRhwAraMQfse7yzKoRe65E=";
+ })
+ (fetchNuGet {
+ pname = "System.Private.Uri";
+ version = "4.3.0";
+ hash = "sha256-fVfgcoP4AVN1E5wHZbKBIOPYZ/xBeSIdsNF+bdukIRM=";
+ })
+ (fetchNuGet {
+ pname = "System.Reflection";
+ version = "4.1.0";
+ hash = "sha256-idZHGH2Yl/hha1CM4VzLhsaR8Ljo/rV7TYe7mwRJSMs=";
+ })
+ (fetchNuGet {
+ pname = "System.Reflection";
+ version = "4.3.0";
+ hash = "sha256-NQSZRpZLvtPWDlvmMIdGxcVuyUnw92ZURo0hXsEshXY=";
+ })
+ (fetchNuGet {
+ pname = "System.Reflection.Metadata";
+ version = "1.6.0";
+ hash = "sha256-JJfgaPav7UfEh4yRAQdGhLZF1brr0tUWPl6qmfNWq/E=";
+ })
+ (fetchNuGet {
+ pname = "System.Reflection.Primitives";
+ version = "4.0.1";
+ hash = "sha256-SFSfpWEyCBMAOerrMCOiKnpT+UAWTvRcmoRquJR6Vq0=";
+ })
+ (fetchNuGet {
+ pname = "System.Reflection.Primitives";
+ version = "4.3.0";
+ hash = "sha256-5ogwWB4vlQTl3jjk1xjniG2ozbFIjZTL9ug0usZQuBM=";
+ })
+ (fetchNuGet {
+ pname = "System.Resources.ResourceManager";
+ version = "4.0.1";
+ hash = "sha256-cZ2/3/fczLjEpn6j3xkgQV9ouOVjy4Kisgw5xWw9kSw=";
+ })
+ (fetchNuGet {
+ pname = "System.Resources.ResourceManager";
+ version = "4.3.0";
+ hash = "sha256-idiOD93xbbrbwwSnD4mORA9RYi/D/U48eRUsn/WnWGo=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime";
+ version = "4.1.0";
+ hash = "sha256-FViNGM/4oWtlP6w0JC0vJU+k9efLKZ+yaXrnEeabDQo=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime";
+ version = "4.3.0";
+ hash = "sha256-51813WXpBIsuA6fUtE5XaRQjcWdQ2/lmEokJt97u0Rg=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime.CompilerServices.Unsafe";
+ version = "5.0.0";
+ hash = "sha256-neARSpLPUzPxEKhJRwoBzhPxK+cKIitLx7WBYncsYgo=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime.CompilerServices.Unsafe";
+ version = "6.0.0";
+ hash = "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime.Extensions";
+ version = "4.1.0";
+ hash = "sha256-X7DZ5CbPY7jHs20YZ7bmcXs9B5Mxptu/HnBUvUnNhGc=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime.Extensions";
+ version = "4.3.0";
+ hash = "sha256-wLDHmozr84v1W2zYCWYxxj0FR0JDYHSVRaRuDm0bd/o=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime.Handles";
+ version = "4.0.1";
+ hash = "sha256-j2QgVO9ZOjv7D1het98CoFpjoYgxjupuIhuBUmLLH7w=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime.Handles";
+ version = "4.3.0";
+ hash = "sha256-KJ5aXoGpB56Y6+iepBkdpx/AfaJDAitx4vrkLqR7gms=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime.InteropServices";
+ version = "4.1.0";
+ hash = "sha256-QceAYlJvkPRJc/+5jR+wQpNNI3aqGySWWSO30e/FfQY=";
+ })
+ (fetchNuGet {
+ pname = "System.Runtime.InteropServices";
+ version = "4.3.0";
+ hash = "sha256-8sDH+WUJfCR+7e4nfpftj/+lstEiZixWUBueR2zmHgI=";
+ })
+ (fetchNuGet {
+ pname = "System.Security.AccessControl";
+ version = "4.5.0";
+ hash = "sha256-AFsKPb/nTk2/mqH/PYpaoI8PLsiKKimaXf+7Mb5VfPM=";
+ })
+ (fetchNuGet {
+ pname = "System.Security.AccessControl";
+ version = "4.7.0";
+ hash = "sha256-/9ZCPIHLdhzq7OW4UKqTsR0O93jjHd6BRG1SRwgHE1g=";
+ })
+ (fetchNuGet {
+ pname = "System.Security.Cryptography.Pkcs";
+ version = "6.0.4";
+ hash = "sha256-2e0aRybote+OR66bHaNiYpF//4fCiaO3zbR2e9GABUI=";
+ })
+ (fetchNuGet {
+ pname = "System.Security.Cryptography.ProtectedData";
+ version = "4.4.0";
+ hash = "sha256-Ri53QmFX8I8UH0x4PikQ1ZA07ZSnBUXStd5rBfGWFOE=";
+ })
+ (fetchNuGet {
+ pname = "System.Security.Cryptography.ProtectedData";
+ version = "4.7.0";
+ hash = "sha256-dZfs5q3Ij1W1eJCfYjxI2o+41aSiFpaAugpoECaCOug=";
+ })
+ (fetchNuGet {
+ pname = "System.Security.Permissions";
+ version = "4.7.0";
+ hash = "sha256-BGgXMLUi5rxVmmChjIhcXUxisJjvlNToXlyaIbUxw40=";
+ })
+ (fetchNuGet {
+ pname = "System.Security.Principal.Windows";
+ version = "4.5.0";
+ hash = "sha256-BkUYNguz0e4NJp1kkW7aJBn3dyH9STwB5N8XqnlCsmY=";
+ })
+ (fetchNuGet {
+ pname = "System.Security.Principal.Windows";
+ version = "4.7.0";
+ hash = "sha256-rWBM2U8Kq3rEdaa1MPZSYOOkbtMGgWyB8iPrpIqmpqg=";
+ })
+ (fetchNuGet {
+ pname = "System.Text.Encoding";
+ version = "4.0.11";
+ hash = "sha256-PEailOvG05CVgPTyKLtpAgRydlSHmtd5K0Y8GSHY2Lc=";
+ })
+ (fetchNuGet {
+ pname = "System.Text.Encoding";
+ version = "4.3.0";
+ hash = "sha256-GctHVGLZAa/rqkBNhsBGnsiWdKyv6VDubYpGkuOkBLg=";
+ })
+ (fetchNuGet {
+ pname = "System.Text.Encoding.CodePages";
+ version = "4.0.1";
+ hash = "sha256-wxtwWQSTv5tuFP79KhUAhaL6bL4d8lSzSWkCn9aolwM=";
+ })
+ (fetchNuGet {
+ pname = "System.Text.Encodings.Web";
+ version = "6.0.0";
+ hash = "sha256-UemDHGFoQIG7ObQwRluhVf6AgtQikfHEoPLC6gbFyRo=";
+ })
+ (fetchNuGet {
+ pname = "System.Text.Encodings.Web";
+ version = "7.0.0";
+ hash = "sha256-tF8qt9GZh/nPy0mEnj6nKLG4Lldpoi/D8xM5lv2CoYQ=";
+ })
+ (fetchNuGet {
+ pname = "System.Text.Json";
+ version = "6.0.0";
+ hash = "sha256-9AE/5ds4DqEfb0l+27fCBTSeYCdRWhxh2Bhg8IKvIuo=";
+ })
+ (fetchNuGet {
+ pname = "System.Text.Json";
+ version = "7.0.3";
+ hash = "sha256-aSJZ17MjqaZNQkprfxm/09LaCoFtpdWmqU9BTROzWX4=";
+ })
+ (fetchNuGet {
+ pname = "System.Threading";
+ version = "4.0.11";
+ hash = "sha256-mob1Zv3qLQhQ1/xOLXZmYqpniNUMCfn02n8ZkaAhqac=";
+ })
+ (fetchNuGet {
+ pname = "System.Threading.Tasks";
+ version = "4.0.11";
+ hash = "sha256-5SLxzFg1df6bTm2t09xeI01wa5qQglqUwwJNlQPJIVs=";
+ })
+ (fetchNuGet {
+ pname = "System.Threading.Tasks";
+ version = "4.3.0";
+ hash = "sha256-Z5rXfJ1EXp3G32IKZGiZ6koMjRu0n8C1NGrwpdIen4w=";
+ })
+ (fetchNuGet {
+ pname = "System.Threading.Tasks.Dataflow";
+ version = "6.0.0";
+ hash = "sha256-bEx7t8jvo7HEkqexhyYyrgFojiMVYyd2nG2mHJv0m6w=";
+ })
+ (fetchNuGet {
+ pname = "System.Windows.Extensions";
+ version = "4.7.0";
+ hash = "sha256-yW+GvQranReaqPw5ZFv+mSjByQ5y1pRLl05JIEf3tYU=";
+ })
+]