From da609db2ce7cc01fbd6549f714ab9ca115ab6e30 Mon Sep 17 00:00:00 2001
From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com>
Date: Mon, 7 Oct 2024 13:35:43 +0100
Subject: [PATCH] First release (#10)
---
.github/workflows/dotnet.yaml | 287 +-
NuGet.config | 18 +
.../Attributes.fs | 96 +
.../README.md | 5 +
.../SurfaceBaseline.txt | 24 +
...Whippet.Plugin.ArgParser.Attributes.fsproj | 33 +
.../version.json | 11 +
.../Args.fs | 237 +
.../GeneratedArgs.fs | 4403 +++++++++++++++++
...e.Whippet.Plugin.ArgParser.Consumer.fsproj | 26 +
.../ArgParserGenerator.fs | 1807 +++++++
.../README.md | 97 +
.../TestArgParser.fs | 706 +++
.../TestSurface.fs | 26 +
...fWare.Whippet.Plugin.ArgParser.Test.fsproj | 27 +
.../WoofWare.Whippet.Plugin.ArgParser.fsproj | 40 +
.../version.json | 14 +
.../Attributes.fs | 37 +
.../README.md | 6 +
.../SurfaceBaseline.txt | 10 +
...Ware.Whippet.Plugin.Json.Attributes.fsproj | 33 +
.../version.json | 11 +
.../GeneratedJson.fs | 483 ++
.../GeneratedPureGymDto.fs | 1116 +++++
...eneratedSerializationAndDeserialization.fs | 977 ++++
.../JsonRecord.fs | 83 +
.../PureGymDto.fs | 190 +
.../SerializationAndDeserialization.fs | 94 +
...ofWare.Whippet.Plugin.Json.Consumer.fsproj | 31 +
.../DesiredGenerator.fs | 15 +
.../JsonParseGenerator.fs | 783 +++
.../JsonSerializeGenerator.fs | 602 +++
.../WoofWare.Whippet.Plugin.Json/README.md | 223 +
.../PureGymDtos.fs | 264 +
.../TestExtensionMethod.fs | 74 +
.../TestJsonParse.fs | 63 +
.../TestJsonSerde.fs | 474 ++
.../TestPureGymJson.fs | 71 +
.../TestSurface.fs | 26 +
.../WoofWare.Whippet.Plugin.Json.Test.fsproj | 31 +
.../WoofWare.Whippet.Plugin.Json.fsproj | 40 +
.../WoofWare.Whippet.Plugin.Json/version.json | 14 +
README.md | 79 +-
WoofWare.Whippet.App/AppContext.fs | 33 +
WoofWare.Whippet.App/Context.fs | 26 +
.../Program.fs | 66 +-
WoofWare.Whippet.App/RuntimeConfig.fs | 47 +
WoofWare.Whippet.App/RuntimeConfigGen.fs | 103 +
WoofWare.Whippet.App/RuntimeLocator.fs | 104 +
.../WoofWare.Whippet.App.fsproj | 27 +
WoofWare.Whippet.Core/Domain.fs | 4 +
WoofWare.Whippet.Core/SurfaceBaseline.txt | 6 +-
WoofWare.Whippet.Fantomas/Ast.fs | 3 +-
WoofWare.Whippet.Fantomas/Measure.fs | 26 +
WoofWare.Whippet.Fantomas/SurfaceBaseline.txt | 2 +
.../WoofWare.Whippet.Fantomas.fsproj | 1 +
WoofWare.Whippet.Fantomas/version.json | 2 +-
WoofWare.Whippet.sln | 54 +
WoofWare.Whippet/WoofWare.Whippet.fsproj | 52 +-
.../build/WoofWare.Whippet.targets | 49 +
60 files changed, 14225 insertions(+), 67 deletions(-)
create mode 100644 NuGet.config
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/Attributes.fs
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/README.md
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/SurfaceBaseline.txt
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/WoofWare.Whippet.Plugin.ArgParser.Attributes.fsproj
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/version.json
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/Args.fs
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/GeneratedArgs.fs
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/WoofWare.Whippet.Plugin.ArgParser.Consumer.fsproj
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/ArgParserGenerator.fs
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/README.md
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/WoofWare.Whippet.Plugin.ArgParser.Test/TestArgParser.fs
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/WoofWare.Whippet.Plugin.ArgParser.Test/TestSurface.fs
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/WoofWare.Whippet.Plugin.ArgParser.Test/WoofWare.Whippet.Plugin.ArgParser.Test.fsproj
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/WoofWare.Whippet.Plugin.ArgParser.fsproj
create mode 100644 Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/version.json
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Attributes/Attributes.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Attributes/README.md
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Attributes/SurfaceBaseline.txt
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Attributes/WoofWare.Whippet.Plugin.Json.Attributes.fsproj
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Attributes/version.json
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Consumer/GeneratedJson.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Consumer/GeneratedPureGymDto.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Consumer/GeneratedSerializationAndDeserialization.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Consumer/JsonRecord.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Consumer/PureGymDto.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Consumer/SerializationAndDeserialization.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json.Consumer/WoofWare.Whippet.Plugin.Json.Consumer.fsproj
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/DesiredGenerator.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonParseGenerator.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonSerializeGenerator.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/README.md
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/WoofWare.Whippet.Plugin.Json.Test/PureGymDtos.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/WoofWare.Whippet.Plugin.Json.Test/TestExtensionMethod.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/WoofWare.Whippet.Plugin.Json.Test/TestJsonParse.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/WoofWare.Whippet.Plugin.Json.Test/TestJsonSerde.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/WoofWare.Whippet.Plugin.Json.Test/TestPureGymJson.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/WoofWare.Whippet.Plugin.Json.Test/TestSurface.fs
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/WoofWare.Whippet.Plugin.Json.Test/WoofWare.Whippet.Plugin.Json.Test.fsproj
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/WoofWare.Whippet.Plugin.Json.fsproj
create mode 100644 Plugins/Json/WoofWare.Whippet.Plugin.Json/version.json
create mode 100644 WoofWare.Whippet.App/AppContext.fs
create mode 100644 WoofWare.Whippet.App/Context.fs
rename {WoofWare.Whippet => WoofWare.Whippet.App}/Program.fs (71%)
create mode 100644 WoofWare.Whippet.App/RuntimeConfig.fs
create mode 100644 WoofWare.Whippet.App/RuntimeConfigGen.fs
create mode 100644 WoofWare.Whippet.App/RuntimeLocator.fs
create mode 100644 WoofWare.Whippet.App/WoofWare.Whippet.App.fsproj
create mode 100644 WoofWare.Whippet.Fantomas/Measure.fs
create mode 100644 WoofWare.Whippet/build/WoofWare.Whippet.targets
diff --git a/.github/workflows/dotnet.yaml b/.github/workflows/dotnet.yaml
index e0f444b..86ef06f 100644
--- a/.github/workflows/dotnet.yaml
+++ b/.github/workflows/dotnet.yaml
@@ -33,10 +33,10 @@ jobs:
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: Build source generator
+ run: nix develop --command dotnet build WoofWare.Whippet/
+ - name: Build solution
+ run: nix develop --command dotnet build --configuration ${{matrix.config}}
- name: Test
run: nix develop --command dotnet test --no-build --verbosity normal --configuration ${{matrix.config}}
@@ -61,20 +61,20 @@ jobs:
- 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
+# 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
@@ -139,10 +139,10 @@ jobs:
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: Build source generator
+ run: nix develop --command dotnet build WoofWare.Whippet/
+ - name: Build solution
+ run: nix develop --command dotnet build --configuration Release
- name: Pack
run: nix develop --command dotnet pack --configuration Release
- name: Upload NuGet artifact (runner)
@@ -160,6 +160,26 @@ jobs:
with:
name: nuget-package-fantomas
path: WoofWare.Whippet.Fantomas/bin/Release/WoofWare.Whippet.Fantomas.*.nupkg
+ - name: Upload NuGet artifact (JSON attrs)
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-package-json-attrs
+ path: Plugins/Json/WoofWare.Whippet.Plugin.Json.Attributes/bin/Release/WoofWare.Whippet.Plugin.Json.*.nupkg
+ - name: Upload NuGet artifact (JSON plugin)
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-package-json
+ path: Plugins/Json/WoofWare.Whippet.Plugin.Json/bin/Release/WoofWare.Whippet.Plugin.Json.*.nupkg
+ - name: Upload NuGet artifact (argparser attrs)
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-package-argparser-attrs
+ path: Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/bin/Release/WoofWare.Whippet.Plugin.ArgParser.*.nupkg
+ - name: Upload NuGet artifact (argparser plugin)
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-package-argparser
+ path: Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/bin/Release/WoofWare.Whippet.Plugin.ArgParser.*.nupkg
expected-pack:
needs: [nuget-pack]
@@ -189,9 +209,66 @@ jobs:
- name: Check NuGet contents
# Verify that there is exactly one nupkg in the artifact that would be NuGet published
run: if [[ $(find packed-fantomas -maxdepth 1 -name 'WoofWare.Whippet.Fantomas.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
+ - name: Download NuGet artifact (JSON attrs)
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-json-attrs
+ path: packed-json-attrs
+ - name: Check NuGet contents
+ # Verify that there is exactly one nupkg in the artifact that would be NuGet published
+ run: if [[ $(find packed-json-attrs -maxdepth 1 -name 'WoofWare.Whippet.Plugin.Json.Attributes.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
+ - name: Download NuGet artifact (JSON plugin)
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-json
+ path: packed-json
+ - name: Check NuGet contents
+ # Verify that there is exactly one nupkg in the artifact that would be NuGet published
+ run: if [[ $(find packed-json -maxdepth 1 -name 'WoofWare.Whippet.Plugin.Json.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
+ - name: Download NuGet artifact (argparser attrs)
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-argparser-attrs
+ path: packed-argparser-attrs
+ - name: Check NuGet contents
+ # Verify that there is exactly one nupkg in the artifact that would be NuGet published
+ run: if [[ $(find packed-argparser-attrs -maxdepth 1 -name 'WoofWare.Whippet.Plugin.ArgParser.Attributes.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
+ - name: Download NuGet artifact (argparser plugin)
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-argparser
+ path: packed-argparser
+ - name: Check NuGet contents
+ # Verify that there is exactly one nupkg in the artifact that would be NuGet published
+ run: if [[ $(find packed-argparser -maxdepth 1 -name 'WoofWare.Whippet.Plugin.ArgParser.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
+
+ check-accurate-generations:
+ runs-on: ubuntu-latest
+ 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: Whitespace change to invalidate all caches
+ run: |
+ find . -type f -name '*.fs' -exec bash -c "echo ' ' >> '{}'" \;
+ find . -type f -name 'Generated*.fs' -exec bash -c "rm '{}'" \;
+ - name: Build Whippet
+ run: nix develop --command dotnet build WoofWare.Whippet
+ - name: Generate code
+ run: nix develop --command dotnet build
+ - name: Run Fantomas
+ run: nix run .#fantomas -- .
+ - name: Verify there is no diff
+ run: git diff --name-only --no-color --exit-code
all-required-checks-complete:
- needs: [check-dotnet-format, check-nix-format, build, build-nix, linkcheck, flake-check, nuget-pack, expected-pack, analyzers]
+ needs: [check-dotnet-format, check-nix-format, build, linkcheck, flake-check, nuget-pack, expected-pack, analyzers, check-accurate-generations]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
@@ -199,6 +276,39 @@ jobs:
with:
needs-context: ${{ toJSON(needs) }}
+ nuget-publish:
+ runs-on: ubuntu-latest
+ if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
+ needs: [all-required-checks-complete]
+ environment: main-deploy
+ permissions:
+ id-token: write
+ attestations: write
+ contents: read
+ steps:
+ - 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: Download NuGet artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-runner
+ path: packed
+ - name: Identify `dotnet`
+ id: dotnet-identify
+ run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
+ - name: Publish to NuGet
+ id: publish-success
+ uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059
+ with:
+ package-name: WoofWare.Whippet
+ nuget-key: ${{ secrets.NUGET_API_KEY }}
+ nupkg-dir: packed/
+ dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
+
nuget-publish-fantomas:
runs-on: ubuntu-latest
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
@@ -232,3 +342,134 @@ jobs:
nupkg-dir: packed/
dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
+ nuget-publish-json-plugin:
+ runs-on: ubuntu-latest
+ if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
+ needs: [all-required-checks-complete]
+ environment: main-deploy
+ permissions:
+ id-token: write
+ attestations: write
+ contents: read
+ steps:
+ - 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: Download NuGet artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-json
+ path: packed
+ - name: Identify `dotnet`
+ id: dotnet-identify
+ run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
+ - name: Publish to NuGet
+ id: publish-success
+ uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059
+ with:
+ package-name: WoofWare.Whippet.Plugin.Json
+ nuget-key: ${{ secrets.NUGET_API_KEY }}
+ nupkg-dir: packed/
+ dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
+
+ nuget-publish-json-attrs:
+ runs-on: ubuntu-latest
+ if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
+ needs: [all-required-checks-complete]
+ environment: main-deploy
+ permissions:
+ id-token: write
+ attestations: write
+ contents: read
+ steps:
+ - 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: Download NuGet artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-json-attrs
+ path: packed
+ - name: Identify `dotnet`
+ id: dotnet-identify
+ run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
+ - name: Publish to NuGet
+ id: publish-success
+ uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059
+ with:
+ package-name: WoofWare.Whippet.Plugin.Json.Attributes
+ nuget-key: ${{ secrets.NUGET_API_KEY }}
+ nupkg-dir: packed/
+ dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
+
+ nuget-publish-argparser-plugin:
+ runs-on: ubuntu-latest
+ if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
+ needs: [all-required-checks-complete]
+ environment: main-deploy
+ permissions:
+ id-token: write
+ attestations: write
+ contents: read
+ steps:
+ - 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: Download NuGet artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-argparser
+ path: packed
+ - name: Identify `dotnet`
+ id: dotnet-identify
+ run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
+ - name: Publish to NuGet
+ id: publish-success
+ uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059
+ with:
+ package-name: WoofWare.Whippet.Plugin.ArgParser
+ nuget-key: ${{ secrets.NUGET_API_KEY }}
+ nupkg-dir: packed/
+ dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
+
+ nuget-publish-argparser-attrs:
+ runs-on: ubuntu-latest
+ if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
+ needs: [all-required-checks-complete]
+ environment: main-deploy
+ permissions:
+ id-token: write
+ attestations: write
+ contents: read
+ steps:
+ - 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: Download NuGet artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package-argparser-attrs
+ path: packed
+ - name: Identify `dotnet`
+ id: dotnet-identify
+ run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
+ - name: Publish to NuGet
+ id: publish-success
+ uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059
+ with:
+ package-name: WoofWare.Whippet.Plugin.ArgParser.Attributes
+ nuget-key: ${{ secrets.NUGET_API_KEY }}
+ nupkg-dir: packed/
+ dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
diff --git a/NuGet.config b/NuGet.config
new file mode 100644
index 0000000..7f5f908
--- /dev/null
+++ b/NuGet.config
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/Attributes.fs b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/Attributes.fs
new file mode 100644
index 0000000..c35fa1d
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/Attributes.fs
@@ -0,0 +1,96 @@
+namespace WoofWare.Whippet.Plugin.ArgParser
+
+open System
+
+/// Attribute indicating a record type to which the "build arg parser" Whippet generator should apply during build.
+///
+/// If you supply isExtensionMethod = false, you will get a module rather than extension methods.
+/// These can only be consumed from F#, but the benefit is that they don't use up the module name
+/// (since by default we create a module called "{TypeName}").
+type ArgParserAttribute (isExtensionMethod : bool) =
+ inherit Attribute ()
+
+ /// The default value of `isExtensionMethod`, the optional argument to the ArgParserAttribute constructor.
+ static member DefaultIsExtensionMethod = true
+
+ /// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
+ new () = ArgParserAttribute ArgParserAttribute.DefaultIsExtensionMethod
+
+/// Attribute indicating that this field shall accumulate all unmatched args,
+/// as well as any that appear after a bare `--`.
+///
+/// Set `includeFlagLike = true` to include args that begin `--` in the
+/// positional args.
+/// (By default, `includeFlagLike = false` and we throw when encountering
+/// an argument which looks like a flag but which we don't recognise.)
+/// We will still interpret `--help` as requesting help, unless it comes after
+/// a standalone `--` separator.
+type PositionalArgsAttribute (includeFlagLike : bool) =
+ inherit Attribute ()
+
+ /// The default value of `isExtensionMethod`, the optional argument to the ArgParserAttribute constructor.
+ static member DefaultIncludeFlagLike = false
+
+ /// Shorthand for the "includeFlagLike = false" constructor; see documentation there for details.
+ new () = PositionalArgsAttribute PositionalArgsAttribute.DefaultIncludeFlagLike
+
+/// Attribute indicating that this field shall have a default value derived
+/// from calling an appropriately named static method on the type.
+///
+/// This attribute can only be placed on fields of type `Choice<_, _>` where both type parameters
+/// are the same.
+/// After a successful parse, the value is Choice1Of2 if the user supplied an input,
+/// or Choice2Of2 if the input was obtained by calling the default function.
+///
+/// The static method we call for field `FieldName : 'a` is `DefaultFieldName : unit -> 'a`.
+type ArgumentDefaultFunctionAttribute () =
+ inherit Attribute ()
+
+/// Attribute indicating that this field shall have a default value derived
+/// from an environment variable (whose name you give in the attribute constructor).
+///
+/// This attribute can only be placed on fields of type `Choice<_, _>` where both type parameters
+/// are the same.
+/// After a successful parse, the value is Choice1Of2 if the user supplied an input,
+/// or Choice2Of2 if the input was obtained by pulling a value from `Environment.GetEnvironmentVariable`.
+type ArgumentDefaultEnvironmentVariableAttribute (envVar : string) =
+ inherit Attribute ()
+
+/// Attribute indicating that this field shall have the given help text, when `--help` is invoked
+/// or when a parse error causes us to print help text.
+type ArgumentHelpTextAttribute (helpText : string) =
+ inherit Attribute ()
+
+/// Attribute indicating that this field should be parsed with a ParseExact method on its type.
+/// For example, on a TimeSpan field, with [], we will call
+/// `TimeSpan.ParseExact (s, @"hh\:mm\:ss", CultureInfo.CurrentCulture).
+type ParseExactAttribute (format : string) =
+ inherit Attribute ()
+
+/// Attribute indicating that this field should be parsed in the invariant culture, rather than the
+/// default current culture.
+/// For example, on a TimeSpan field, with [] and [], we will call
+/// `TimeSpan.ParseExact (s, @"hh\:mm\:ss", CultureInfo.InvariantCulture).
+type InvariantCultureAttribute () =
+ inherit Attribute ()
+
+/// Attribute placed on a field of a two-case no-data discriminated union, indicating that this is "basically a bool".
+/// For example: `type DryRun = | [] Dry | [] Wet`
+/// A record with `{ DryRun : DryRun }` will then be parsed like `{ DryRun : bool }` (so the user supplies `--dry-run`),
+/// but that you get this strongly-typed value directly in the code (so you `match args.DryRun with | DryRun.Dry ...`).
+///
+/// You must put this attribute on both cases of the discriminated union, with opposite values in each case.
+type ArgumentFlagAttribute (flagValue : bool) =
+ inherit Attribute ()
+
+/// Attribute placed on a field of a record to specify a different long form from the default. If you place this
+/// attribute, you won't get the default: ArgFoo would normally be expressed as `--arg-foo`, but if you instead
+/// say `[]` or `[]`, you instead use `--thingy-blah`
+/// or `--thingy` respectively.
+///
+/// You can place this argument multiple times.
+///
+/// Omit the initial `--` that you expect the user to type.
+[]
+type ArgumentLongForm (s : string) =
+ inherit Attribute ()
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/README.md b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/README.md
new file mode 100644
index 0000000..d0c5620
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/README.md
@@ -0,0 +1,5 @@
+# WoofWare.Whippet.Plugin.ArgParser.Attributes
+
+This is a very slim runtime dependency we expect consumers of WoofWare.Whippet.Plugin.ArgParser to take.
+This dependency contains attributes which control that source generator.
+Please see WoofWare.Whippet.Plugin.ArgParser's README for further information.
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/SurfaceBaseline.txt b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/SurfaceBaseline.txt
new file mode 100644
index 0000000..d6fefe6
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/SurfaceBaseline.txt
@@ -0,0 +1,24 @@
+WoofWare.Whippet.Plugin.ArgParser.ArgParserAttribute inherit System.Attribute
+WoofWare.Whippet.Plugin.ArgParser.ArgParserAttribute..ctor [constructor]: bool
+WoofWare.Whippet.Plugin.ArgParser.ArgParserAttribute..ctor [constructor]: unit
+WoofWare.Whippet.Plugin.ArgParser.ArgParserAttribute.DefaultIsExtensionMethod [static property]: [read-only] bool
+WoofWare.Whippet.Plugin.ArgParser.ArgParserAttribute.get_DefaultIsExtensionMethod [static method]: unit -> bool
+WoofWare.Whippet.Plugin.ArgParser.ArgumentDefaultEnvironmentVariableAttribute inherit System.Attribute
+WoofWare.Whippet.Plugin.ArgParser.ArgumentDefaultEnvironmentVariableAttribute..ctor [constructor]: string
+WoofWare.Whippet.Plugin.ArgParser.ArgumentDefaultFunctionAttribute inherit System.Attribute
+WoofWare.Whippet.Plugin.ArgParser.ArgumentDefaultFunctionAttribute..ctor [constructor]: unit
+WoofWare.Whippet.Plugin.ArgParser.ArgumentFlagAttribute inherit System.Attribute
+WoofWare.Whippet.Plugin.ArgParser.ArgumentFlagAttribute..ctor [constructor]: bool
+WoofWare.Whippet.Plugin.ArgParser.ArgumentHelpTextAttribute inherit System.Attribute
+WoofWare.Whippet.Plugin.ArgParser.ArgumentHelpTextAttribute..ctor [constructor]: string
+WoofWare.Whippet.Plugin.ArgParser.ArgumentLongForm inherit System.Attribute
+WoofWare.Whippet.Plugin.ArgParser.ArgumentLongForm..ctor [constructor]: string
+WoofWare.Whippet.Plugin.ArgParser.InvariantCultureAttribute inherit System.Attribute
+WoofWare.Whippet.Plugin.ArgParser.InvariantCultureAttribute..ctor [constructor]: unit
+WoofWare.Whippet.Plugin.ArgParser.ParseExactAttribute inherit System.Attribute
+WoofWare.Whippet.Plugin.ArgParser.ParseExactAttribute..ctor [constructor]: string
+WoofWare.Whippet.Plugin.ArgParser.PositionalArgsAttribute inherit System.Attribute
+WoofWare.Whippet.Plugin.ArgParser.PositionalArgsAttribute..ctor [constructor]: bool
+WoofWare.Whippet.Plugin.ArgParser.PositionalArgsAttribute..ctor [constructor]: unit
+WoofWare.Whippet.Plugin.ArgParser.PositionalArgsAttribute.DefaultIncludeFlagLike [static property]: [read-only] bool
+WoofWare.Whippet.Plugin.ArgParser.PositionalArgsAttribute.get_DefaultIncludeFlagLike [static method]: unit -> bool
\ No newline at end of file
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/WoofWare.Whippet.Plugin.ArgParser.Attributes.fsproj b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/WoofWare.Whippet.Plugin.ArgParser.Attributes.fsproj
new file mode 100644
index 0000000..159e473
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/WoofWare.Whippet.Plugin.ArgParser.Attributes.fsproj
@@ -0,0 +1,33 @@
+
+
+
+ netstandard2.0
+ true
+ Patrick Stevens
+ Copyright (c) Patrick Stevens 2024
+ Attributes to accompany the WoofWare.Whippet.Plugin.ArgParser source generator, to indicate what you want your types to be doing.
+ git
+ https://github.com/Smaug123/WoofWare.Whippet
+ MIT
+ README.md
+ fsharp;source-generator;source-gen;whippet;arguments;arg-parser
+ true
+ FS3559
+ WoofWare.Whippet.Plugin.ArgParser.Attributes
+
+
+
+
+
+
+
+ True
+ /
+ README.md
+
+
+
+
+
+
+
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/version.json b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/version.json
new file mode 100644
index 0000000..47ae36c
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Attributes/version.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.2",
+ "publicReleaseRefSpec": [
+ "^refs/heads/main$"
+ ],
+ "pathFilters": [
+ "./",
+ ":/global.json",
+ ":/Directory.Build.props"
+ ]
+}
\ No newline at end of file
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/Args.fs b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/Args.fs
new file mode 100644
index 0000000..a64e62f
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/Args.fs
@@ -0,0 +1,237 @@
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+[]
+type BasicNoPositionals =
+ {
+ Foo : int
+ Bar : string
+ Baz : bool
+ Rest : int list
+ }
+
+[]
+type Basic =
+ {
+ []
+ Foo : int
+ Bar : string
+ Baz : bool
+ []
+ []
+ Rest : string list
+ }
+
+[]
+type BasicWithIntPositionals =
+ {
+ Foo : int
+ Bar : string
+ Baz : bool
+ []
+ Rest : int list
+ }
+
+[]
+type LoadsOfTypes =
+ {
+ Foo : int
+ Bar : string
+ Baz : bool
+ SomeFile : FileInfo
+ SomeDirectory : DirectoryInfo
+ SomeList : DirectoryInfo list
+ OptionalThingWithNoDefault : int option
+ []
+ Positionals : int list
+ []
+ OptionalThing : Choice
+ []
+ AnotherOptionalThing : Choice
+ []
+ YetAnotherOptionalThing : Choice
+ }
+
+ static member DefaultOptionalThing () = true
+
+ static member DefaultAnotherOptionalThing () = 3
+
+[]
+type LoadsOfTypesNoPositionals =
+ {
+ Foo : int
+ Bar : string
+ Baz : bool
+ SomeFile : FileInfo
+ SomeDirectory : DirectoryInfo
+ SomeList : DirectoryInfo list
+ OptionalThingWithNoDefault : int option
+ []
+ OptionalThing : Choice
+ []
+ AnotherOptionalThing : Choice
+ []
+ YetAnotherOptionalThing : Choice
+ }
+
+ static member DefaultOptionalThing () = false
+
+ static member DefaultAnotherOptionalThing () = 3
+
+[]
+type DatesAndTimes =
+ {
+ Plain : TimeSpan
+ []
+ Invariant : TimeSpan
+ []
+ []
+ Exact : TimeSpan
+ []
+ InvariantExact : TimeSpan
+ }
+
+type ChildRecord =
+ {
+ Thing1 : int
+ Thing2 : string
+ }
+
+[]
+type ParentRecord =
+ {
+ Child : ChildRecord
+ AndAnother : bool
+ }
+
+type ChildRecordWithPositional =
+ {
+ Thing1 : int
+ []
+ Thing2 : Uri list
+ }
+
+[]
+type ParentRecordChildPos =
+ {
+ Child : ChildRecordWithPositional
+ AndAnother : bool
+ }
+
+[]
+type ParentRecordSelfPos =
+ {
+ Child : ChildRecord
+ []
+ AndAnother : bool list
+ }
+
+[]
+type ChoicePositionals =
+ {
+ []
+ Args : Choice list
+ }
+
+[]
+type ContainsBoolEnvVar =
+ {
+ []
+ BoolVar : Choice
+ }
+
+[]
+module Consts =
+ []
+ let FALSE = false
+
+ []
+ let TRUE = true
+
+type DryRunMode =
+ | [] Wet
+ | [] Dry
+
+[]
+type WithFlagDu =
+ {
+ DryRun : DryRunMode
+ }
+
+[]
+type ContainsFlagEnvVar =
+ {
+ // This phrasing is odd, but it's for a test. Nobody's really going to have `--dry-run`
+ // controlled by an env var!
+ []
+ DryRun : Choice
+ }
+
+[]
+type ContainsFlagDefaultValue =
+ {
+ []
+ DryRun : Choice
+ }
+
+ static member DefaultDryRun () = DryRunMode.Wet
+
+[]
+type ManyLongForms =
+ {
+ []
+ []
+ DoTheThing : string
+
+ []
+ []
+ SomeFlag : bool
+ }
+
+[]
+type private IrrelevantDu =
+ | Foo
+ | Bar
+
+[]
+type FlagsIntoPositionalArgs =
+ {
+ A : string
+ []
+ GrabEverything : string list
+ }
+
+[]
+type FlagsIntoPositionalArgsChoice =
+ {
+ A : string
+ []
+ GrabEverything : Choice list
+ }
+
+[]
+type FlagsIntoPositionalArgsInt =
+ {
+ A : string
+ []
+ GrabEverything : int list
+ }
+
+[]
+type FlagsIntoPositionalArgsIntChoice =
+ {
+ A : string
+ []
+ GrabEverything : Choice list
+ }
+
+[]
+type FlagsIntoPositionalArgs' =
+ {
+ A : string
+ []
+ DontGrabEverything : string list
+ }
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/GeneratedArgs.fs b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/GeneratedArgs.fs
new file mode 100644
index 0000000..44b4a7a
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/GeneratedArgs.fs
@@ -0,0 +1,4403 @@
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type BasicNoPositionals
+[]
+module BasicNoPositionalsArgParse =
+ type private ParseState_BasicNoPositionals =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type BasicNoPositionals with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : BasicNoPositionals =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s int32%s%s" (sprintf "--%s" "foo") "" "")
+ (sprintf "%s string%s%s" (sprintf "--%s" "bar") "" "")
+ (sprintf "%s bool%s%s" (sprintf "--%s" "baz") "" "")
+ (sprintf "%s int32%s%s" (sprintf "--%s" "rest") " (can be repeated)" "")
+ ]
+ |> String.concat "\n"
+
+ let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : int option = None
+ let mutable arg_1 : string option = None
+ let mutable arg_2 : bool option = None
+ let arg_3 : int ResizeArray = ResizeArray ()
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "rest", System.StringComparison.OrdinalIgnoreCase) then
+ value |> (fun x -> System.Int32.Parse x) |> arg_3.Add
+ () |> Ok
+ else if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "baz")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_2 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "bar", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "bar")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "foo", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "foo")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_2 <- true |> Some
+ true
+ else
+ false
+
+ let rec go (state : ParseState_BasicNoPositionals) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_BasicNoPositionals.AwaitingKey -> ()
+ | ParseState_BasicNoPositionals.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_BasicNoPositionals.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_BasicNoPositionals.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_BasicNoPositionals.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_BasicNoPositionals.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> parser_LeftoverArgs.Add
+ go ParseState_BasicNoPositionals.AwaitingKey args
+ | ParseState_BasicNoPositionals.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_BasicNoPositionals.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_BasicNoPositionals.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_BasicNoPositionals.AwaitingKey args
+
+ let parser_LeftoverArgs =
+ if 0 = parser_LeftoverArgs.Count then
+ ()
+ else
+ parser_LeftoverArgs
+ |> String.concat " "
+ |> sprintf "There were leftover args: %s"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "foo")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_1 =
+ match arg_1 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "bar")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_2 =
+ match arg_2 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_3 = arg_3 |> Seq.toList
+
+ if 0 = ArgParser_errors.Count then
+ {
+ Bar = arg_1
+ Baz = arg_2
+ Foo = arg_0
+ Rest = arg_3
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : BasicNoPositionals =
+ BasicNoPositionals.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type Basic
+[]
+module BasicArgParse =
+ type private ParseState_Basic =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type Basic with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : Basic =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s int32%s%s" (sprintf "--%s" "foo") "" (sprintf " : %s" ("This is a foo!")))
+ (sprintf "%s string%s%s" (sprintf "--%s" "bar") "" "")
+ (sprintf "%s bool%s%s" (sprintf "--%s" "baz") "" "")
+ (sprintf
+ "%s string%s%s"
+ (sprintf "--%s" "rest")
+ " (positional args) (can be repeated)"
+ (sprintf " : %s" ("Here's where the rest of the args go")))
+ ]
+ |> String.concat "\n"
+
+ let arg_3 : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : int option = None
+ let mutable arg_1 : string option = None
+ let mutable arg_2 : bool option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "baz")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_2 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "bar", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "bar")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "foo", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "foo")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "rest", System.StringComparison.OrdinalIgnoreCase)
+ then
+ value |> (fun x -> x) |> arg_3.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_2 <- true |> Some
+ true
+ else
+ false
+
+ let rec go (state : ParseState_Basic) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_Basic.AwaitingKey -> ()
+ | ParseState_Basic.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_3.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_Basic.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_Basic.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_Basic.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_Basic.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> arg_3.Add
+ go ParseState_Basic.AwaitingKey args
+ | ParseState_Basic.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_Basic.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_Basic.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_Basic.AwaitingKey args
+ let arg_3 = arg_3 |> Seq.toList
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "foo")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_1 =
+ match arg_1 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "bar")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_2 =
+ match arg_2 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ Bar = arg_1
+ Baz = arg_2
+ Foo = arg_0
+ Rest = arg_3
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : Basic =
+ Basic.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type BasicWithIntPositionals
+[]
+module BasicWithIntPositionalsArgParse =
+ type private ParseState_BasicWithIntPositionals =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type BasicWithIntPositionals with
+
+ static member parse'
+ (getEnvironmentVariable : string -> string)
+ (args : string list)
+ : BasicWithIntPositionals
+ =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s int32%s%s" (sprintf "--%s" "foo") "" "")
+ (sprintf "%s string%s%s" (sprintf "--%s" "bar") "" "")
+ (sprintf "%s bool%s%s" (sprintf "--%s" "baz") "" "")
+ (sprintf "%s int32%s%s" (sprintf "--%s" "rest") " (positional args) (can be repeated)" "")
+ ]
+ |> String.concat "\n"
+
+ let arg_3 : int ResizeArray = ResizeArray ()
+ let mutable arg_0 : int option = None
+ let mutable arg_1 : string option = None
+ let mutable arg_2 : bool option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "baz")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_2 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "bar", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "bar")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "foo", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "foo")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "rest", System.StringComparison.OrdinalIgnoreCase)
+ then
+ value |> (fun x -> System.Int32.Parse x) |> arg_3.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_2 <- true |> Some
+ true
+ else
+ false
+
+ let rec go (state : ParseState_BasicWithIntPositionals) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_BasicWithIntPositionals.AwaitingKey -> ()
+ | ParseState_BasicWithIntPositionals.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_3.AddRange (rest |> Seq.map (fun x -> System.Int32.Parse x))
+ | arg :: args ->
+ match state with
+ | ParseState_BasicWithIntPositionals.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_BasicWithIntPositionals.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_BasicWithIntPositionals.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_BasicWithIntPositionals.AwaitingKey args
+ else
+ arg |> (fun x -> System.Int32.Parse x) |> arg_3.Add
+ go ParseState_BasicWithIntPositionals.AwaitingKey args
+ | ParseState_BasicWithIntPositionals.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_BasicWithIntPositionals.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_BasicWithIntPositionals.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_BasicWithIntPositionals.AwaitingKey args
+ let arg_3 = arg_3 |> Seq.toList
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "foo")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_1 =
+ match arg_1 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "bar")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_2 =
+ match arg_2 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ Bar = arg_1
+ Baz = arg_2
+ Foo = arg_0
+ Rest = arg_3
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : BasicWithIntPositionals =
+ BasicWithIntPositionals.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type LoadsOfTypes
+[]
+module LoadsOfTypesArgParse =
+ type private ParseState_LoadsOfTypes =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type LoadsOfTypes with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : LoadsOfTypes =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s int32%s%s" (sprintf "--%s" "foo") "" "")
+ (sprintf "%s string%s%s" (sprintf "--%s" "bar") "" "")
+ (sprintf "%s bool%s%s" (sprintf "--%s" "baz") "" "")
+ (sprintf "%s FileInfo%s%s" (sprintf "--%s" "some-file") "" "")
+ (sprintf "%s DirectoryInfo%s%s" (sprintf "--%s" "some-directory") "" "")
+ (sprintf "%s DirectoryInfo%s%s" (sprintf "--%s" "some-list") " (can be repeated)" "")
+ (sprintf "%s int32%s%s" (sprintf "--%s" "optional-thing-with-no-default") " (optional)" "")
+
+ (sprintf
+ "%s bool%s%s"
+ (sprintf "--%s" "optional-thing")
+ (LoadsOfTypes.DefaultOptionalThing ()
+ |> (fun x -> x.ToString ())
+ |> sprintf " (default value: %s)")
+ "")
+
+ (sprintf
+ "%s int32%s%s"
+ (sprintf "--%s" "another-optional-thing")
+ (LoadsOfTypes.DefaultAnotherOptionalThing ()
+ |> (fun x -> x.ToString ())
+ |> sprintf " (default value: %s)")
+ "")
+
+ (sprintf
+ "%s string%s%s"
+ (sprintf "--%s" "yet-another-optional-thing")
+ ("CONSUMEPLUGIN_THINGS" |> sprintf " (default value populated from env var %s)")
+ "")
+ (sprintf "%s int32%s%s" (sprintf "--%s" "positionals") " (positional args) (can be repeated)" "")
+ ]
+ |> String.concat "\n"
+
+ let arg_7 : int ResizeArray = ResizeArray ()
+ let mutable arg_0 : int option = None
+ let mutable arg_1 : string option = None
+ let mutable arg_2 : bool option = None
+ let mutable arg_3 : FileInfo option = None
+ let mutable arg_4 : DirectoryInfo option = None
+ let arg_5 : DirectoryInfo ResizeArray = ResizeArray ()
+ let mutable arg_6 : int option = None
+ let mutable arg_8 : bool option = None
+ let mutable arg_9 : int option = None
+ let mutable arg_10 : string option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "yet-another-optional-thing",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_10 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "yet-another-optional-thing")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_10 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "another-optional-thing",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_9 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "another-optional-thing")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_9 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "optional-thing",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_8 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "optional-thing")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_8 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "optional-thing-with-no-default",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_6 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "optional-thing-with-no-default")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_6 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "some-list", System.StringComparison.OrdinalIgnoreCase)
+ then
+ value |> (fun x -> System.IO.DirectoryInfo x) |> arg_5.Add
+ () |> Ok
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "some-directory",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_4 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "some-directory")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_4 <- value |> (fun x -> System.IO.DirectoryInfo x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "some-file", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_3 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "some-file")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_3 <- value |> (fun x -> System.IO.FileInfo x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "baz")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_2 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "bar", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "bar")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "foo", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "foo")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "positionals", System.StringComparison.OrdinalIgnoreCase)
+ then
+ value |> (fun x -> System.Int32.Parse x) |> arg_7.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "optional-thing",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_8 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "optional-thing")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_8 <- true |> Some
+ true
+ else if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_2 <- true |> Some
+ true
+ else
+ false
+
+ let rec go (state : ParseState_LoadsOfTypes) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_LoadsOfTypes.AwaitingKey -> ()
+ | ParseState_LoadsOfTypes.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_7.AddRange (rest |> Seq.map (fun x -> System.Int32.Parse x))
+ | arg :: args ->
+ match state with
+ | ParseState_LoadsOfTypes.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_LoadsOfTypes.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_LoadsOfTypes.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_LoadsOfTypes.AwaitingKey args
+ else
+ arg |> (fun x -> System.Int32.Parse x) |> arg_7.Add
+ go ParseState_LoadsOfTypes.AwaitingKey args
+ | ParseState_LoadsOfTypes.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_LoadsOfTypes.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_LoadsOfTypes.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_LoadsOfTypes.AwaitingKey args
+ let arg_7 = arg_7 |> Seq.toList
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "foo")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_1 =
+ match arg_1 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "bar")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_2 =
+ match arg_2 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_3 =
+ match arg_3 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "some-file")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_4 =
+ match arg_4 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "some-directory")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_5 = arg_5 |> Seq.toList
+ let arg_6 = arg_6
+
+ let arg_8 =
+ match arg_8 with
+ | None -> LoadsOfTypes.DefaultOptionalThing () |> Choice2Of2
+ | Some x -> Choice1Of2 x
+
+ let arg_9 =
+ match arg_9 with
+ | None -> LoadsOfTypes.DefaultAnotherOptionalThing () |> Choice2Of2
+ | Some x -> Choice1Of2 x
+
+ let arg_10 =
+ match arg_10 with
+ | None ->
+ match "CONSUMEPLUGIN_THINGS" |> getEnvironmentVariable with
+ | null ->
+ sprintf
+ "No value was supplied for %s, nor was environment variable %s set"
+ (sprintf "--%s" "yet-another-optional-thing")
+ "CONSUMEPLUGIN_THINGS"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | x -> x |> (fun x -> x)
+ |> Choice2Of2
+ | Some x -> Choice1Of2 x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ AnotherOptionalThing = arg_9
+ Bar = arg_1
+ Baz = arg_2
+ Foo = arg_0
+ OptionalThing = arg_8
+ OptionalThingWithNoDefault = arg_6
+ Positionals = arg_7
+ SomeDirectory = arg_4
+ SomeFile = arg_3
+ SomeList = arg_5
+ YetAnotherOptionalThing = arg_10
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : LoadsOfTypes =
+ LoadsOfTypes.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type LoadsOfTypesNoPositionals
+[]
+module LoadsOfTypesNoPositionalsArgParse =
+ type private ParseState_LoadsOfTypesNoPositionals =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type LoadsOfTypesNoPositionals with
+
+ static member parse'
+ (getEnvironmentVariable : string -> string)
+ (args : string list)
+ : LoadsOfTypesNoPositionals
+ =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s int32%s%s" (sprintf "--%s" "foo") "" "")
+ (sprintf "%s string%s%s" (sprintf "--%s" "bar") "" "")
+ (sprintf "%s bool%s%s" (sprintf "--%s" "baz") "" "")
+ (sprintf "%s FileInfo%s%s" (sprintf "--%s" "some-file") "" "")
+ (sprintf "%s DirectoryInfo%s%s" (sprintf "--%s" "some-directory") "" "")
+ (sprintf "%s DirectoryInfo%s%s" (sprintf "--%s" "some-list") " (can be repeated)" "")
+ (sprintf "%s int32%s%s" (sprintf "--%s" "optional-thing-with-no-default") " (optional)" "")
+
+ (sprintf
+ "%s bool%s%s"
+ (sprintf "--%s" "optional-thing")
+ (LoadsOfTypesNoPositionals.DefaultOptionalThing ()
+ |> (fun x -> x.ToString ())
+ |> sprintf " (default value: %s)")
+ "")
+
+ (sprintf
+ "%s int32%s%s"
+ (sprintf "--%s" "another-optional-thing")
+ (LoadsOfTypesNoPositionals.DefaultAnotherOptionalThing ()
+ |> (fun x -> x.ToString ())
+ |> sprintf " (default value: %s)")
+ "")
+ (sprintf
+ "%s string%s%s"
+ (sprintf "--%s" "yet-another-optional-thing")
+ ("CONSUMEPLUGIN_THINGS" |> sprintf " (default value populated from env var %s)")
+ "")
+ ]
+ |> String.concat "\n"
+
+ let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : int option = None
+ let mutable arg_1 : string option = None
+ let mutable arg_2 : bool option = None
+ let mutable arg_3 : FileInfo option = None
+ let mutable arg_4 : DirectoryInfo option = None
+ let arg_5 : DirectoryInfo ResizeArray = ResizeArray ()
+ let mutable arg_6 : int option = None
+ let mutable arg_7 : bool option = None
+ let mutable arg_8 : int option = None
+ let mutable arg_9 : string option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "yet-another-optional-thing",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_9 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "yet-another-optional-thing")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_9 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "another-optional-thing",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_8 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "another-optional-thing")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_8 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "optional-thing",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_7 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "optional-thing")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_7 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "optional-thing-with-no-default",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_6 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "optional-thing-with-no-default")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_6 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "some-list", System.StringComparison.OrdinalIgnoreCase)
+ then
+ value |> (fun x -> System.IO.DirectoryInfo x) |> arg_5.Add
+ () |> Ok
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "some-directory",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_4 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "some-directory")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_4 <- value |> (fun x -> System.IO.DirectoryInfo x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "some-file", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_3 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "some-file")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_3 <- value |> (fun x -> System.IO.FileInfo x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "baz")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_2 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "bar", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "bar")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if System.String.Equals (key, sprintf "--%s" "foo", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "foo")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "optional-thing",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_7 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "optional-thing")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_7 <- true |> Some
+ true
+ else if System.String.Equals (key, sprintf "--%s" "baz", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_2 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_2 <- true |> Some
+ true
+ else
+ false
+
+ let rec go (state : ParseState_LoadsOfTypesNoPositionals) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_LoadsOfTypesNoPositionals.AwaitingKey -> ()
+ | ParseState_LoadsOfTypesNoPositionals.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_LoadsOfTypesNoPositionals.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_LoadsOfTypesNoPositionals.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_LoadsOfTypesNoPositionals.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_LoadsOfTypesNoPositionals.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> parser_LeftoverArgs.Add
+ go ParseState_LoadsOfTypesNoPositionals.AwaitingKey args
+ | ParseState_LoadsOfTypesNoPositionals.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_LoadsOfTypesNoPositionals.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_LoadsOfTypesNoPositionals.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_LoadsOfTypesNoPositionals.AwaitingKey args
+
+ let parser_LeftoverArgs =
+ if 0 = parser_LeftoverArgs.Count then
+ ()
+ else
+ parser_LeftoverArgs
+ |> String.concat " "
+ |> sprintf "There were leftover args: %s"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "foo")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_1 =
+ match arg_1 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "bar")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_2 =
+ match arg_2 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "baz")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_3 =
+ match arg_3 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "some-file")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_4 =
+ match arg_4 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "some-directory")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_5 = arg_5 |> Seq.toList
+ let arg_6 = arg_6
+
+ let arg_7 =
+ match arg_7 with
+ | None -> LoadsOfTypesNoPositionals.DefaultOptionalThing () |> Choice2Of2
+ | Some x -> Choice1Of2 x
+
+ let arg_8 =
+ match arg_8 with
+ | None -> LoadsOfTypesNoPositionals.DefaultAnotherOptionalThing () |> Choice2Of2
+ | Some x -> Choice1Of2 x
+
+ let arg_9 =
+ match arg_9 with
+ | None ->
+ match "CONSUMEPLUGIN_THINGS" |> getEnvironmentVariable with
+ | null ->
+ sprintf
+ "No value was supplied for %s, nor was environment variable %s set"
+ (sprintf "--%s" "yet-another-optional-thing")
+ "CONSUMEPLUGIN_THINGS"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | x -> x |> (fun x -> x)
+ |> Choice2Of2
+ | Some x -> Choice1Of2 x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ AnotherOptionalThing = arg_8
+ Bar = arg_1
+ Baz = arg_2
+ Foo = arg_0
+ OptionalThing = arg_7
+ OptionalThingWithNoDefault = arg_6
+ SomeDirectory = arg_4
+ SomeFile = arg_3
+ SomeList = arg_5
+ YetAnotherOptionalThing = arg_9
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : LoadsOfTypesNoPositionals =
+ LoadsOfTypesNoPositionals.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type DatesAndTimes
+[]
+module DatesAndTimesArgParse =
+ type private ParseState_DatesAndTimes =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type DatesAndTimes with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : DatesAndTimes =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s TimeSpan%s%s" (sprintf "--%s" "plain") "" "")
+ (sprintf "%s TimeSpan%s%s" (sprintf "--%s" "invariant") "" "")
+
+ (sprintf
+ "%s TimeSpan%s%s"
+ (sprintf "--%s" "exact")
+ ""
+ (sprintf " : %s" (sprintf "%s [Parse format (.NET): %s]" "An exact time please" @"hh\:mm\:ss")))
+ (sprintf
+ "%s TimeSpan%s%s"
+ (sprintf "--%s" "invariant-exact")
+ ""
+ (sprintf " : %s" (sprintf "[Parse format (.NET): %s]" @"hh\:mm\:ss")))
+ ]
+ |> String.concat "\n"
+
+ let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : TimeSpan option = None
+ let mutable arg_1 : TimeSpan option = None
+ let mutable arg_2 : TimeSpan option = None
+ let mutable arg_3 : TimeSpan option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "invariant-exact",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_3 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "invariant-exact")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_3 <-
+ value
+ |> (fun x ->
+ System.TimeSpan.ParseExact (
+ x,
+ @"hh\:mm\:ss",
+ System.Globalization.CultureInfo.InvariantCulture
+ )
+ )
+ |> Some
+
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "exact", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_2 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "exact")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_2 <-
+ value
+ |> (fun x ->
+ System.TimeSpan.ParseExact (
+ x,
+ @"hh\:mm\:ss",
+ System.Globalization.CultureInfo.CurrentCulture
+ )
+ )
+ |> Some
+
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "invariant", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "invariant")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <-
+ value
+ |> (fun x ->
+ System.TimeSpan.Parse (x, System.Globalization.CultureInfo.InvariantCulture)
+ )
+ |> Some
+
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "plain", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "plain")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.TimeSpan.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool = false
+
+ let rec go (state : ParseState_DatesAndTimes) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_DatesAndTimes.AwaitingKey -> ()
+ | ParseState_DatesAndTimes.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_DatesAndTimes.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_DatesAndTimes.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_DatesAndTimes.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_DatesAndTimes.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> parser_LeftoverArgs.Add
+ go ParseState_DatesAndTimes.AwaitingKey args
+ | ParseState_DatesAndTimes.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_DatesAndTimes.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_DatesAndTimes.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_DatesAndTimes.AwaitingKey args
+
+ let parser_LeftoverArgs =
+ if 0 = parser_LeftoverArgs.Count then
+ ()
+ else
+ parser_LeftoverArgs
+ |> String.concat " "
+ |> sprintf "There were leftover args: %s"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "plain")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_1 =
+ match arg_1 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "invariant")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_2 =
+ match arg_2 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "exact")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_3 =
+ match arg_3 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "invariant-exact")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ Exact = arg_2
+ Invariant = arg_1
+ InvariantExact = arg_3
+ Plain = arg_0
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : DatesAndTimes =
+ DatesAndTimes.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type ParentRecord
+[]
+module ParentRecordArgParse =
+ type private ParseState_ParentRecord =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type ParentRecord with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : ParentRecord =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s int32%s%s" (sprintf "--%s" "thing1") "" "")
+ (sprintf "%s string%s%s" (sprintf "--%s" "thing2") "" "")
+ (sprintf "%s bool%s%s" (sprintf "--%s" "and-another") "" "")
+ ]
+ |> String.concat "\n"
+
+ let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : int option = None
+ let mutable arg_1 : string option = None
+ let mutable arg_2 : bool option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if
+ System.String.Equals (key, sprintf "--%s" "and-another", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_2 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "and-another")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_2 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "thing2", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "thing2")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "thing1", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "thing1")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if
+ System.String.Equals (key, sprintf "--%s" "and-another", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_2 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "and-another")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_2 <- true |> Some
+ true
+ else
+ false
+
+ let rec go (state : ParseState_ParentRecord) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_ParentRecord.AwaitingKey -> ()
+ | ParseState_ParentRecord.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_ParentRecord.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_ParentRecord.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_ParentRecord.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_ParentRecord.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> parser_LeftoverArgs.Add
+ go ParseState_ParentRecord.AwaitingKey args
+ | ParseState_ParentRecord.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_ParentRecord.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_ParentRecord.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_ParentRecord.AwaitingKey args
+
+ let parser_LeftoverArgs =
+ if 0 = parser_LeftoverArgs.Count then
+ ()
+ else
+ parser_LeftoverArgs
+ |> String.concat " "
+ |> sprintf "There were leftover args: %s"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "thing1")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_1 =
+ match arg_1 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "thing2")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_2 =
+ match arg_2 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "and-another")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ AndAnother = arg_2
+ Child =
+ {
+ Thing1 = arg_0
+ Thing2 = arg_1
+ }
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : ParentRecord =
+ ParentRecord.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type ParentRecordChildPos
+[]
+module ParentRecordChildPosArgParse =
+ type private ParseState_ParentRecordChildPos =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type ParentRecordChildPos with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : ParentRecordChildPos =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s bool%s%s" (sprintf "--%s" "and-another") "" "")
+ (sprintf "%s int32%s%s" (sprintf "--%s" "thing1") "" "")
+ (sprintf "%s URI%s%s" (sprintf "--%s" "thing2") " (positional args) (can be repeated)" "")
+ ]
+ |> String.concat "\n"
+
+ let arg_1 : Uri ResizeArray = ResizeArray ()
+ let mutable arg_2 : bool option = None
+ let mutable arg_0 : int option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "thing1", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "thing1")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "and-another", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_2 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "and-another")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_2 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "thing2", System.StringComparison.OrdinalIgnoreCase)
+ then
+ value |> (fun x -> System.Uri x) |> arg_1.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if
+ System.String.Equals (key, sprintf "--%s" "and-another", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_2 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "and-another")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_2 <- true |> Some
+ true
+ else
+ false
+
+ let rec go (state : ParseState_ParentRecordChildPos) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_ParentRecordChildPos.AwaitingKey -> ()
+ | ParseState_ParentRecordChildPos.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_1.AddRange (rest |> Seq.map (fun x -> System.Uri x))
+ | arg :: args ->
+ match state with
+ | ParseState_ParentRecordChildPos.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_ParentRecordChildPos.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_ParentRecordChildPos.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_ParentRecordChildPos.AwaitingKey args
+ else
+ arg |> (fun x -> System.Uri x) |> arg_1.Add
+ go ParseState_ParentRecordChildPos.AwaitingKey args
+ | ParseState_ParentRecordChildPos.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_ParentRecordChildPos.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_ParentRecordChildPos.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_ParentRecordChildPos.AwaitingKey args
+ let arg_1 = arg_1 |> Seq.toList
+
+ let arg_2 =
+ match arg_2 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "and-another")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "thing1")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ AndAnother = arg_2
+ Child =
+ {
+ Thing1 = arg_0
+ Thing2 = arg_1
+ }
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : ParentRecordChildPos =
+ ParentRecordChildPos.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type ParentRecordSelfPos
+[]
+module ParentRecordSelfPosArgParse =
+ type private ParseState_ParentRecordSelfPos =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type ParentRecordSelfPos with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : ParentRecordSelfPos =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s int32%s%s" (sprintf "--%s" "thing1") "" "")
+ (sprintf "%s string%s%s" (sprintf "--%s" "thing2") "" "")
+ (sprintf "%s bool%s%s" (sprintf "--%s" "and-another") " (positional args) (can be repeated)" "")
+ ]
+ |> String.concat "\n"
+
+ let arg_2 : bool ResizeArray = ResizeArray ()
+ let mutable arg_0 : int option = None
+ let mutable arg_1 : string option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "thing2", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "thing2")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "thing1", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "thing1")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.Int32.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "and-another", System.StringComparison.OrdinalIgnoreCase)
+ then
+ value |> (fun x -> System.Boolean.Parse x) |> arg_2.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool = false
+
+ let rec go (state : ParseState_ParentRecordSelfPos) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_ParentRecordSelfPos.AwaitingKey -> ()
+ | ParseState_ParentRecordSelfPos.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_2.AddRange (rest |> Seq.map (fun x -> System.Boolean.Parse x))
+ | arg :: args ->
+ match state with
+ | ParseState_ParentRecordSelfPos.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_ParentRecordSelfPos.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_ParentRecordSelfPos.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_ParentRecordSelfPos.AwaitingKey args
+ else
+ arg |> (fun x -> System.Boolean.Parse x) |> arg_2.Add
+ go ParseState_ParentRecordSelfPos.AwaitingKey args
+ | ParseState_ParentRecordSelfPos.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_ParentRecordSelfPos.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_ParentRecordSelfPos.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_ParentRecordSelfPos.AwaitingKey args
+ let arg_2 = arg_2 |> Seq.toList
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "thing1")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_1 =
+ match arg_1 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "thing2")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ AndAnother = arg_2
+ Child =
+ {
+ Thing1 = arg_0
+ Thing2 = arg_1
+ }
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : ParentRecordSelfPos =
+ ParentRecordSelfPos.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type ChoicePositionals
+[]
+module ChoicePositionalsArgParse =
+ type private ParseState_ChoicePositionals =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type ChoicePositionals with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : ChoicePositionals =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s string%s%s" (sprintf "--%s" "args") " (positional args) (can be repeated)" "")
+ ]
+ |> String.concat "\n"
+
+ let arg_0 : Choice ResizeArray = ResizeArray ()
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "args", System.StringComparison.OrdinalIgnoreCase) then
+ value |> (fun x -> x) |> Choice1Of2 |> arg_0.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool = false
+
+ let rec go (state : ParseState_ChoicePositionals) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_ChoicePositionals.AwaitingKey -> ()
+ | ParseState_ChoicePositionals.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_0.AddRange (rest |> Seq.map (fun x -> x) |> Seq.map Choice2Of2)
+ | arg :: args ->
+ match state with
+ | ParseState_ChoicePositionals.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_ChoicePositionals.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_ChoicePositionals.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_ChoicePositionals.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> Choice1Of2 |> arg_0.Add
+ go ParseState_ChoicePositionals.AwaitingKey args
+ | ParseState_ChoicePositionals.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_ChoicePositionals.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_ChoicePositionals.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_ChoicePositionals.AwaitingKey args
+ let arg_0 = arg_0 |> Seq.toList
+
+ if 0 = ArgParser_errors.Count then
+ {
+ Args = arg_0
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : ChoicePositionals =
+ ChoicePositionals.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type ContainsBoolEnvVar
+[]
+module ContainsBoolEnvVarArgParse =
+ type private ParseState_ContainsBoolEnvVar =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type ContainsBoolEnvVar with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : ContainsBoolEnvVar =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf
+ "%s bool%s%s"
+ (sprintf "--%s" "bool-var")
+ ("CONSUMEPLUGIN_THINGS" |> sprintf " (default value populated from env var %s)")
+ "")
+ ]
+ |> String.concat "\n"
+
+ let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : bool option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "bool-var", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "bool-var")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if System.String.Equals (key, sprintf "--%s" "bool-var", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "bool-var")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_0 <- true |> Some
+ true
+ else
+ false
+
+ let rec go (state : ParseState_ContainsBoolEnvVar) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_ContainsBoolEnvVar.AwaitingKey -> ()
+ | ParseState_ContainsBoolEnvVar.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_ContainsBoolEnvVar.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_ContainsBoolEnvVar.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_ContainsBoolEnvVar.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_ContainsBoolEnvVar.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> parser_LeftoverArgs.Add
+ go ParseState_ContainsBoolEnvVar.AwaitingKey args
+ | ParseState_ContainsBoolEnvVar.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_ContainsBoolEnvVar.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_ContainsBoolEnvVar.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_ContainsBoolEnvVar.AwaitingKey args
+
+ let parser_LeftoverArgs =
+ if 0 = parser_LeftoverArgs.Count then
+ ()
+ else
+ parser_LeftoverArgs
+ |> String.concat " "
+ |> sprintf "There were leftover args: %s"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ match "CONSUMEPLUGIN_THINGS" |> getEnvironmentVariable with
+ | null ->
+ sprintf
+ "No value was supplied for %s, nor was environment variable %s set"
+ (sprintf "--%s" "bool-var")
+ "CONSUMEPLUGIN_THINGS"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | x ->
+ if System.String.Equals (x, "1", System.StringComparison.OrdinalIgnoreCase) then
+ true
+ else if System.String.Equals (x, "0", System.StringComparison.OrdinalIgnoreCase) then
+ false
+ else
+ x |> (fun x -> System.Boolean.Parse x)
+ |> Choice2Of2
+ | Some x -> Choice1Of2 x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ BoolVar = arg_0
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : ContainsBoolEnvVar =
+ ContainsBoolEnvVar.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type WithFlagDu
+[]
+module WithFlagDuArgParse =
+ type private ParseState_WithFlagDu =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type WithFlagDu with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : WithFlagDu =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [ (sprintf "%s bool%s%s" (sprintf "--%s" "dry-run") "" "") ]
+ |> String.concat "\n"
+
+ let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : DryRunMode option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "dry-run", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "dry-run")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <-
+ value
+ |> (fun x ->
+ if System.Boolean.Parse x = Consts.FALSE then
+ DryRunMode.Wet
+ else
+ DryRunMode.Dry
+ )
+ |> Some
+
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if System.String.Equals (key, sprintf "--%s" "dry-run", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "dry-run")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_0 <-
+ if true = Consts.FALSE then
+ DryRunMode.Wet
+ else
+ DryRunMode.Dry
+ |> Some
+
+ true
+ else
+ false
+
+ let rec go (state : ParseState_WithFlagDu) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_WithFlagDu.AwaitingKey -> ()
+ | ParseState_WithFlagDu.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_WithFlagDu.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_WithFlagDu.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_WithFlagDu.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_WithFlagDu.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> parser_LeftoverArgs.Add
+ go ParseState_WithFlagDu.AwaitingKey args
+ | ParseState_WithFlagDu.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_WithFlagDu.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_WithFlagDu.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_WithFlagDu.AwaitingKey args
+
+ let parser_LeftoverArgs =
+ if 0 = parser_LeftoverArgs.Count then
+ ()
+ else
+ parser_LeftoverArgs
+ |> String.concat " "
+ |> sprintf "There were leftover args: %s"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "dry-run")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ DryRun = arg_0
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : WithFlagDu =
+ WithFlagDu.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type ContainsFlagEnvVar
+[]
+module ContainsFlagEnvVarArgParse =
+ type private ParseState_ContainsFlagEnvVar =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type ContainsFlagEnvVar with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : ContainsFlagEnvVar =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf
+ "%s bool%s%s"
+ (sprintf "--%s" "dry-run")
+ ("CONSUMEPLUGIN_THINGS" |> sprintf " (default value populated from env var %s)")
+ "")
+ ]
+ |> String.concat "\n"
+
+ let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : DryRunMode option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "dry-run", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "dry-run")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <-
+ value
+ |> (fun x ->
+ if System.Boolean.Parse x = Consts.FALSE then
+ DryRunMode.Wet
+ else
+ DryRunMode.Dry
+ )
+ |> Some
+
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if System.String.Equals (key, sprintf "--%s" "dry-run", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "dry-run")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_0 <-
+ if true = Consts.FALSE then
+ DryRunMode.Wet
+ else
+ DryRunMode.Dry
+ |> Some
+
+ true
+ else
+ false
+
+ let rec go (state : ParseState_ContainsFlagEnvVar) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_ContainsFlagEnvVar.AwaitingKey -> ()
+ | ParseState_ContainsFlagEnvVar.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_ContainsFlagEnvVar.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_ContainsFlagEnvVar.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_ContainsFlagEnvVar.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_ContainsFlagEnvVar.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> parser_LeftoverArgs.Add
+ go ParseState_ContainsFlagEnvVar.AwaitingKey args
+ | ParseState_ContainsFlagEnvVar.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_ContainsFlagEnvVar.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_ContainsFlagEnvVar.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_ContainsFlagEnvVar.AwaitingKey args
+
+ let parser_LeftoverArgs =
+ if 0 = parser_LeftoverArgs.Count then
+ ()
+ else
+ parser_LeftoverArgs
+ |> String.concat " "
+ |> sprintf "There were leftover args: %s"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ match "CONSUMEPLUGIN_THINGS" |> getEnvironmentVariable with
+ | null ->
+ sprintf
+ "No value was supplied for %s, nor was environment variable %s set"
+ (sprintf "--%s" "dry-run")
+ "CONSUMEPLUGIN_THINGS"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | x ->
+ if System.String.Equals (x, "1", System.StringComparison.OrdinalIgnoreCase) then
+ if true = Consts.FALSE then
+ DryRunMode.Wet
+ else
+ DryRunMode.Dry
+ else if System.String.Equals (x, "0", System.StringComparison.OrdinalIgnoreCase) then
+ if false = Consts.FALSE then
+ DryRunMode.Wet
+ else
+ DryRunMode.Dry
+ else
+ x
+ |> (fun x ->
+ if System.Boolean.Parse x = Consts.FALSE then
+ DryRunMode.Wet
+ else
+ DryRunMode.Dry
+ )
+ |> Choice2Of2
+ | Some x -> Choice1Of2 x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ DryRun = arg_0
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : ContainsFlagEnvVar =
+ ContainsFlagEnvVar.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type ContainsFlagDefaultValue
+[]
+module ContainsFlagDefaultValueArgParse =
+ type private ParseState_ContainsFlagDefaultValue =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type ContainsFlagDefaultValue with
+
+ static member parse'
+ (getEnvironmentVariable : string -> string)
+ (args : string list)
+ : ContainsFlagDefaultValue
+ =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf
+ "%s bool%s%s"
+ (sprintf "--%s" "dry-run")
+ (match ContainsFlagDefaultValue.DefaultDryRun () with
+ | DryRunMode.Wet -> if Consts.FALSE = true then "true" else "false"
+ | DryRunMode.Dry -> if true = true then "true" else "false"
+ |> (fun x -> x.ToString ())
+ |> sprintf " (default value: %s)")
+ "")
+ ]
+ |> String.concat "\n"
+
+ let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : DryRunMode option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "dry-run", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "dry-run")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <-
+ value
+ |> (fun x ->
+ if System.Boolean.Parse x = Consts.FALSE then
+ DryRunMode.Wet
+ else
+ DryRunMode.Dry
+ )
+ |> Some
+
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if System.String.Equals (key, sprintf "--%s" "dry-run", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "dry-run")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_0 <-
+ if true = Consts.FALSE then
+ DryRunMode.Wet
+ else
+ DryRunMode.Dry
+ |> Some
+
+ true
+ else
+ false
+
+ let rec go (state : ParseState_ContainsFlagDefaultValue) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_ContainsFlagDefaultValue.AwaitingKey -> ()
+ | ParseState_ContainsFlagDefaultValue.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_ContainsFlagDefaultValue.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_ContainsFlagDefaultValue.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_ContainsFlagDefaultValue.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_ContainsFlagDefaultValue.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> parser_LeftoverArgs.Add
+ go ParseState_ContainsFlagDefaultValue.AwaitingKey args
+ | ParseState_ContainsFlagDefaultValue.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_ContainsFlagDefaultValue.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_ContainsFlagDefaultValue.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_ContainsFlagDefaultValue.AwaitingKey args
+
+ let parser_LeftoverArgs =
+ if 0 = parser_LeftoverArgs.Count then
+ ()
+ else
+ parser_LeftoverArgs
+ |> String.concat " "
+ |> sprintf "There were leftover args: %s"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+
+ let arg_0 =
+ match arg_0 with
+ | None -> ContainsFlagDefaultValue.DefaultDryRun () |> Choice2Of2
+ | Some x -> Choice1Of2 x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ DryRun = arg_0
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : ContainsFlagDefaultValue =
+ ContainsFlagDefaultValue.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type ManyLongForms
+[]
+module ManyLongFormsArgParse =
+ type private ParseState_ManyLongForms =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type ManyLongForms with
+
+ static member parse' (getEnvironmentVariable : string -> string) (args : string list) : ManyLongForms =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s string%s%s" (sprintf "--%s / --%s" "do-something-else" "anotherarg") "" "")
+ (sprintf "%s bool%s%s" (sprintf "--%s / --%s" "turn-it-on" "dont-turn-it-off") "" "")
+ ]
+ |> String.concat "\n"
+
+ let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : string option = None
+ let mutable arg_1 : bool option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "dont-turn-it-off",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s / --%s" "turn-it-on" "dont-turn-it-off")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "turn-it-on", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s / --%s" "turn-it-on" "dont-turn-it-off")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_1 <- value |> (fun x -> System.Boolean.Parse x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (key, sprintf "--%s" "anotherarg", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s / --%s" "do-something-else" "anotherarg")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "do-something-else",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s / --%s" "do-something-else" "anotherarg")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool =
+ if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "dont-turn-it-off",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Flag '%s' was supplied multiple times"
+ (sprintf "--%s / --%s" "turn-it-on" "dont-turn-it-off")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_1 <- true |> Some
+ true
+ else if
+ System.String.Equals (key, sprintf "--%s" "turn-it-on", System.StringComparison.OrdinalIgnoreCase)
+ then
+ match arg_1 with
+ | Some x ->
+ sprintf
+ "Flag '%s' was supplied multiple times"
+ (sprintf "--%s / --%s" "turn-it-on" "dont-turn-it-off")
+ |> ArgParser_errors.Add
+
+ true
+ | None ->
+ arg_1 <- true |> Some
+ true
+ else
+ false
+
+ let rec go (state : ParseState_ManyLongForms) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_ManyLongForms.AwaitingKey -> ()
+ | ParseState_ManyLongForms.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_ManyLongForms.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_ManyLongForms.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_ManyLongForms.AwaitingKey args
+ | Error x ->
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_ManyLongForms.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> parser_LeftoverArgs.Add
+ go ParseState_ManyLongForms.AwaitingKey args
+ | ParseState_ManyLongForms.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_ManyLongForms.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_ManyLongForms.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_ManyLongForms.AwaitingKey args
+
+ let parser_LeftoverArgs =
+ if 0 = parser_LeftoverArgs.Count then
+ ()
+ else
+ parser_LeftoverArgs
+ |> String.concat " "
+ |> sprintf "There were leftover args: %s"
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf
+ "Required argument '%s' received no value"
+ (sprintf "--%s / --%s" "do-something-else" "anotherarg")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ let arg_1 =
+ match arg_1 with
+ | None ->
+ sprintf
+ "Required argument '%s' received no value"
+ (sprintf "--%s / --%s" "turn-it-on" "dont-turn-it-off")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ DoTheThing = arg_0
+ SomeFlag = arg_1
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : ManyLongForms =
+ ManyLongForms.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type FlagsIntoPositionalArgs
+[]
+module FlagsIntoPositionalArgsArgParse =
+ type private ParseState_FlagsIntoPositionalArgs =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type FlagsIntoPositionalArgs with
+
+ static member parse'
+ (getEnvironmentVariable : string -> string)
+ (args : string list)
+ : FlagsIntoPositionalArgs
+ =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s string%s%s" (sprintf "--%s" "a") "" "")
+ (sprintf
+ "%s string%s%s"
+ (sprintf "--%s" "grab-everything")
+ " (positional args) (can be repeated)"
+ "")
+ ]
+ |> String.concat "\n"
+
+ let arg_1 : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : string option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "a", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "a")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "grab-everything",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ value |> (fun x -> x) |> arg_1.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool = false
+
+ let rec go (state : ParseState_FlagsIntoPositionalArgs) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgs.AwaitingKey -> ()
+ | ParseState_FlagsIntoPositionalArgs.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_1.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgs.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_FlagsIntoPositionalArgs.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgs.AwaitingKey args
+ | Error x ->
+ if true then
+ arg |> (fun x -> x) |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgs.AwaitingKey args
+ else
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_FlagsIntoPositionalArgs.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgs.AwaitingKey args
+ | ParseState_FlagsIntoPositionalArgs.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgs.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_FlagsIntoPositionalArgs.AwaitingKey (arg :: args)
+ else if true then
+ key |> (fun x -> x) |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgs.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_FlagsIntoPositionalArgs.AwaitingKey args
+ let arg_1 = arg_1 |> Seq.toList
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "a")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ A = arg_0
+ GrabEverything = arg_1
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : FlagsIntoPositionalArgs =
+ FlagsIntoPositionalArgs.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type FlagsIntoPositionalArgsChoice
+[]
+module FlagsIntoPositionalArgsChoiceArgParse =
+ type private ParseState_FlagsIntoPositionalArgsChoice =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type FlagsIntoPositionalArgsChoice with
+
+ static member parse'
+ (getEnvironmentVariable : string -> string)
+ (args : string list)
+ : FlagsIntoPositionalArgsChoice
+ =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s string%s%s" (sprintf "--%s" "a") "" "")
+ (sprintf
+ "%s string%s%s"
+ (sprintf "--%s" "grab-everything")
+ " (positional args) (can be repeated)"
+ "")
+ ]
+ |> String.concat "\n"
+
+ let arg_1 : Choice ResizeArray = ResizeArray ()
+ let mutable arg_0 : string option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "a", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "a")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "grab-everything",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ value |> (fun x -> x) |> Choice1Of2 |> arg_1.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool = false
+
+ let rec go (state : ParseState_FlagsIntoPositionalArgsChoice) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey -> ()
+ | ParseState_FlagsIntoPositionalArgsChoice.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_1.AddRange (rest |> Seq.map (fun x -> x) |> Seq.map Choice2Of2)
+ | arg :: args ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_FlagsIntoPositionalArgsChoice.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey args
+ | Error x ->
+ if true then
+ arg |> (fun x -> x) |> Choice1Of2 |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey args
+ else
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> Choice1Of2 |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey args
+ | ParseState_FlagsIntoPositionalArgsChoice.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey (arg :: args)
+ else if true then
+ key |> (fun x -> x) |> Choice1Of2 |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_FlagsIntoPositionalArgsChoice.AwaitingKey args
+ let arg_1 = arg_1 |> Seq.toList
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "a")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ A = arg_0
+ GrabEverything = arg_1
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : FlagsIntoPositionalArgsChoice =
+ FlagsIntoPositionalArgsChoice.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type FlagsIntoPositionalArgsInt
+[]
+module FlagsIntoPositionalArgsIntArgParse =
+ type private ParseState_FlagsIntoPositionalArgsInt =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type FlagsIntoPositionalArgsInt with
+
+ static member parse'
+ (getEnvironmentVariable : string -> string)
+ (args : string list)
+ : FlagsIntoPositionalArgsInt
+ =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s string%s%s" (sprintf "--%s" "a") "" "")
+ (sprintf
+ "%s int32%s%s"
+ (sprintf "--%s" "grab-everything")
+ " (positional args) (can be repeated)"
+ "")
+ ]
+ |> String.concat "\n"
+
+ let arg_1 : int ResizeArray = ResizeArray ()
+ let mutable arg_0 : string option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "a", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "a")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "grab-everything",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ value |> (fun x -> System.Int32.Parse x) |> arg_1.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool = false
+
+ let rec go (state : ParseState_FlagsIntoPositionalArgsInt) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgsInt.AwaitingKey -> ()
+ | ParseState_FlagsIntoPositionalArgsInt.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_1.AddRange (rest |> Seq.map (fun x -> System.Int32.Parse x))
+ | arg :: args ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgsInt.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_FlagsIntoPositionalArgsInt.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgsInt.AwaitingKey args
+ | Error x ->
+ if true then
+ arg |> (fun x -> System.Int32.Parse x) |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgsInt.AwaitingKey args
+ else
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_FlagsIntoPositionalArgsInt.AwaitingKey args
+ else
+ arg |> (fun x -> System.Int32.Parse x) |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgsInt.AwaitingKey args
+ | ParseState_FlagsIntoPositionalArgsInt.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgsInt.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_FlagsIntoPositionalArgsInt.AwaitingKey (arg :: args)
+ else if true then
+ key |> (fun x -> System.Int32.Parse x) |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgsInt.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_FlagsIntoPositionalArgsInt.AwaitingKey args
+ let arg_1 = arg_1 |> Seq.toList
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "a")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ A = arg_0
+ GrabEverything = arg_1
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : FlagsIntoPositionalArgsInt =
+ FlagsIntoPositionalArgsInt.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type FlagsIntoPositionalArgsIntChoice
+[]
+module FlagsIntoPositionalArgsIntChoiceArgParse =
+ type private ParseState_FlagsIntoPositionalArgsIntChoice =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type FlagsIntoPositionalArgsIntChoice with
+
+ static member parse'
+ (getEnvironmentVariable : string -> string)
+ (args : string list)
+ : FlagsIntoPositionalArgsIntChoice
+ =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s string%s%s" (sprintf "--%s" "a") "" "")
+ (sprintf
+ "%s int32%s%s"
+ (sprintf "--%s" "grab-everything")
+ " (positional args) (can be repeated)"
+ "")
+ ]
+ |> String.concat "\n"
+
+ let arg_1 : Choice ResizeArray = ResizeArray ()
+ let mutable arg_0 : string option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "a", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "a")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "grab-everything",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ value |> (fun x -> System.Int32.Parse x) |> Choice1Of2 |> arg_1.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool = false
+
+ let rec go (state : ParseState_FlagsIntoPositionalArgsIntChoice) (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey -> ()
+ | ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_1.AddRange (rest |> Seq.map (fun x -> System.Int32.Parse x) |> Seq.map Choice2Of2)
+ | arg :: args ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey args
+ | Error x ->
+ if true then
+ arg |> (fun x -> System.Int32.Parse x) |> Choice1Of2 |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey args
+ else
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey args
+ else
+ arg |> (fun x -> System.Int32.Parse x) |> Choice1Of2 |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey args
+ | ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey (arg :: args)
+ else if true then
+ key |> (fun x -> System.Int32.Parse x) |> Choice1Of2 |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_FlagsIntoPositionalArgsIntChoice.AwaitingKey args
+ let arg_1 = arg_1 |> Seq.toList
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "a")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ A = arg_0
+ GrabEverything = arg_1
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : FlagsIntoPositionalArgsIntChoice =
+ FlagsIntoPositionalArgsIntChoice.parse' System.Environment.GetEnvironmentVariable args
+namespace Consumer
+
+open System
+open System.IO
+open WoofWare.Whippet.Plugin.ArgParser
+
+/// Methods to parse arguments for the type FlagsIntoPositionalArgs'
+[]
+module FlagsIntoPositionalArgs'ArgParse =
+ type private ParseState_FlagsIntoPositionalArgs' =
+ /// Ready to consume a key or positional arg
+ | AwaitingKey
+ /// Waiting to receive a value for the key we've already consumed
+ | AwaitingValue of key : string
+
+ /// Extension methods for argument parsing
+ type FlagsIntoPositionalArgs' with
+
+ static member parse'
+ (getEnvironmentVariable : string -> string)
+ (args : string list)
+ : FlagsIntoPositionalArgs'
+ =
+ let ArgParser_errors = ResizeArray ()
+
+ let helpText () =
+ [
+ (sprintf "%s string%s%s" (sprintf "--%s" "a") "" "")
+ (sprintf
+ "%s string%s%s"
+ (sprintf "--%s" "dont-grab-everything")
+ " (positional args) (can be repeated)"
+ "")
+ ]
+ |> String.concat "\n"
+
+ let arg_1 : string ResizeArray = ResizeArray ()
+ let mutable arg_0 : string option = None
+
+ /// Processes the key-value pair, returning Error if no key was matched.
+ /// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error().
+ /// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
+ let processKeyValue (key : string) (value : string) : Result =
+ if System.String.Equals (key, sprintf "--%s" "a", System.StringComparison.OrdinalIgnoreCase) then
+ match arg_0 with
+ | Some x ->
+ sprintf
+ "Argument '%s' was supplied multiple times: %s and %s"
+ (sprintf "--%s" "a")
+ (x.ToString ())
+ (value.ToString ())
+ |> ArgParser_errors.Add
+
+ Ok ()
+ | None ->
+ try
+ arg_0 <- value |> (fun x -> x) |> Some
+ Ok ()
+ with _ as exc ->
+ exc.Message |> Some |> Error
+ else if
+ System.String.Equals (
+ key,
+ sprintf "--%s" "dont-grab-everything",
+ System.StringComparison.OrdinalIgnoreCase
+ )
+ then
+ value |> (fun x -> x) |> arg_1.Add
+ () |> Ok
+ else
+ Error None
+
+ /// Returns false if we didn't set a value.
+ let setFlagValue (key : string) : bool = false
+
+ let rec go (state : ParseState_FlagsIntoPositionalArgs') (args : string list) =
+ match args with
+ | [] ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgs'.AwaitingKey -> ()
+ | ParseState_FlagsIntoPositionalArgs'.AwaitingValue key ->
+ if setFlagValue key then
+ ()
+ else
+ sprintf
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ key
+ |> ArgParser_errors.Add
+ | "--" :: rest -> arg_1.AddRange (rest |> Seq.map (fun x -> x))
+ | arg :: args ->
+ match state with
+ | ParseState_FlagsIntoPositionalArgs'.AwaitingKey ->
+ if arg.StartsWith ("--", System.StringComparison.Ordinal) then
+ if arg = "--help" then
+ helpText () |> failwithf "Help text requested.\n%s"
+ else
+ let equals = arg.IndexOf (char 61)
+
+ if equals < 0 then
+ args |> go (ParseState_FlagsIntoPositionalArgs'.AwaitingValue arg)
+ else
+ let key = arg.[0 .. equals - 1]
+ let value = arg.[equals + 1 ..]
+
+ match processKeyValue key value with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgs'.AwaitingKey args
+ | Error x ->
+ if false then
+ arg |> (fun x -> x) |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgs'.AwaitingKey args
+ else
+ match x with
+ | None ->
+ failwithf
+ "Unable to process argument %s as key %s and value %s"
+ arg
+ key
+ value
+ | Some msg ->
+ sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
+ go ParseState_FlagsIntoPositionalArgs'.AwaitingKey args
+ else
+ arg |> (fun x -> x) |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgs'.AwaitingKey args
+ | ParseState_FlagsIntoPositionalArgs'.AwaitingValue key ->
+ match processKeyValue key arg with
+ | Ok () -> go ParseState_FlagsIntoPositionalArgs'.AwaitingKey args
+ | Error exc ->
+ if setFlagValue key then
+ go ParseState_FlagsIntoPositionalArgs'.AwaitingKey (arg :: args)
+ else if false then
+ key |> (fun x -> x) |> arg_1.Add
+ go ParseState_FlagsIntoPositionalArgs'.AwaitingKey (arg :: args)
+ else
+ match exc with
+ | None ->
+ failwithf
+ "Unable to process supplied arg %s. Help text follows.\n%s"
+ key
+ (helpText ())
+ | Some msg -> msg |> ArgParser_errors.Add
+
+ go ParseState_FlagsIntoPositionalArgs'.AwaitingKey args
+ let arg_1 = arg_1 |> Seq.toList
+
+ let arg_0 =
+ match arg_0 with
+ | None ->
+ sprintf "Required argument '%s' received no value" (sprintf "--%s" "a")
+ |> ArgParser_errors.Add
+
+ Unchecked.defaultof<_>
+ | Some x -> x
+
+ if 0 = ArgParser_errors.Count then
+ {
+ A = arg_0
+ DontGrabEverything = arg_1
+ }
+ else
+ ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
+
+ static member parse (args : string list) : FlagsIntoPositionalArgs' =
+ FlagsIntoPositionalArgs'.parse' System.Environment.GetEnvironmentVariable args
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/WoofWare.Whippet.Plugin.ArgParser.Consumer.fsproj b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/WoofWare.Whippet.Plugin.ArgParser.Consumer.fsproj
new file mode 100644
index 0000000..430484e
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser.Consumer/WoofWare.Whippet.Plugin.ArgParser.Consumer.fsproj
@@ -0,0 +1,26 @@
+
+
+
+ net8.0
+ true
+ false
+ $(MSBuildThisFileDirectory)/../../../WoofWare.Whippet/bin/$(Configuration)
+
+
+
+
+
+ Args.fs
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/ArgParserGenerator.fs b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/ArgParserGenerator.fs
new file mode 100644
index 0000000..88e5609
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/ArgParserGenerator.fs
@@ -0,0 +1,1807 @@
+namespace WoofWare.Whippet.Plugin.ArgParser
+
+open System
+open System.Text
+open Fantomas.FCS.Syntax
+open WoofWare.Whippet.Core
+open WoofWare.Whippet.Fantomas
+open Fantomas.FCS.Text.Range
+open TypeEquality
+
+type internal ArgParserOutputSpec =
+ {
+ ExtensionMethods : bool
+ }
+
+type internal FlagDu =
+ {
+ Name : Ident
+ Case1Name : Ident
+ Case2Name : Ident
+ /// Hopefully this is simply the const bool True or False, but it might e.g. be a literal
+ Case1Arg : SynExpr
+ /// Hopefully this is simply the const bool True or False, but it might e.g. be a literal
+ Case2Arg : SynExpr
+ }
+
+ static member FromBoolean (flagDu : FlagDu) (value : SynExpr) =
+ SynExpr.ifThenElse
+ (SynExpr.equals value flagDu.Case1Arg)
+ (SynExpr.createLongIdent' [ flagDu.Name ; flagDu.Case2Name ])
+ (SynExpr.createLongIdent' [ flagDu.Name ; flagDu.Case1Name ])
+
+/// The default value of an argument which admits default values can be pulled from different sources.
+/// This defines which source a particular default value comes from.
+type private ArgumentDefaultSpec =
+ /// From parsing the environment variable with the given name (e.g. "WOOFWARE_DISABLE_FOO" or whatever).
+ | EnvironmentVariable of name : SynExpr
+ /// From calling the static member `{typeWeParseInto}.Default{name}()`
+ /// For example, if `type MyArgs = { Thing : Choice }`, then
+ /// we would use `MyArgs.DefaultThing () : int`.
+ ///
+ | FunctionCall of name : Ident
+
+type private Accumulation<'choice> =
+ | Required
+ | Optional
+ | Choice of 'choice
+ | List of Accumulation<'choice>
+
+type private ParseFunction<'acc> =
+ {
+ FieldName : Ident
+ TargetVariable : Ident
+ /// Any of the forms in this set are acceptable, but make sure they all start with a dash, or we might
+ /// get confused with positional args or something! I haven't thought that hard about this.
+ /// In the default case, this is `Const("arg-name")` for the `ArgName : blah` field; note that we have
+ /// omitted the initial `--` that will be required at runtime.
+ ArgForm : SynExpr list
+ /// If this is a boolean-like field (e.g. a bool or a flag DU), the help text should look a bit different:
+ /// we should lie to the user about the value of the cases there.
+ /// Similarly, if we're reading from an environment variable with the laxer parsing rules of accepting e.g.
+ /// "0" instead of "false", we need to know if we're reading a bool.
+ /// In that case, `boolCases` is Some, and contains the construction of the flag (or boolean, in which case
+ /// you get no data).
+ BoolCases : Choice option
+ Help : SynExpr option
+ /// A function string -> %TargetType%, where TargetVariable is probably a `%TargetType% option`.
+ /// (Depending on `Accumulation`, we'll remove the `option` at the end of the parse, asserting that the
+ /// argument was supplied.)
+ /// This is allowed to throw if it fails to parse.
+ Parser : SynExpr
+ /// If `Accumulation` is `List`, then this is the type of the list *element*; analogously for optionals
+ /// and choices and so on.
+ TargetType : SynType
+ Accumulation : 'acc
+ }
+
+ /// A SynExpr of type `string` which we can display to the user at generated-program runtime to display all
+ /// the ways they can refer to this arg.
+ member arg.HumanReadableArgForm : SynExpr =
+ let formatString = List.replicate arg.ArgForm.Length "--%s" |> String.concat " / "
+
+ (SynExpr.applyFunction (SynExpr.createIdent "sprintf") (SynExpr.CreateConst formatString), arg.ArgForm)
+ ||> List.fold SynExpr.applyFunction
+ |> SynExpr.paren
+
+[]
+type private ChoicePositional =
+ | Normal of includeFlagLike : SynExpr option
+ | Choice of includeFlagLike : SynExpr option
+
+type private ParseFunctionPositional = ParseFunction
+type private ParseFunctionNonPositional = ParseFunction>
+
+type private ParserSpec =
+ {
+ NonPositionals : ParseFunctionNonPositional list
+ /// The variable into which positional arguments will be accumulated.
+ /// In this case, the TargetVariable is a `ResizeArray` rather than the usual `option`.
+ Positionals : ParseFunctionPositional option
+ }
+
+type private HasPositional = HasPositional
+type private HasNoPositional = HasNoPositional
+
+[]
+module private TeqUtils =
+ let exFalso<'a> (_ : Teq) : 'a = failwith "LOGIC ERROR!"
+ let exFalso'<'a> (_ : Teq) : 'a = failwith "LOGIC ERROR!"
+
+[]
+type private ParseTree<'hasPositional> =
+ | NonPositionalLeaf of ParseFunctionNonPositional * Teq<'hasPositional, HasNoPositional>
+ | PositionalLeaf of ParseFunctionPositional * Teq<'hasPositional, HasPositional>
+ /// `assemble` takes the SynExpr's (e.g. each record field contents) corresponding to each `Ident` in
+ /// the branch (e.g. each record field name),
+ /// and composes them into a `SynExpr` (e.g. the record-typed object).
+ | Branch of
+ fields : (Ident * ParseTree) list *
+ assemble : (Map -> SynExpr) *
+ Teq<'hasPositional, HasNoPositional>
+ /// `assemble` takes the SynExpr's (e.g. each record field contents) corresponding to each `Ident` in
+ /// the branch (e.g. each record field name),
+ /// and composes them into a `SynExpr` (e.g. the record-typed object).
+ | BranchPos of
+ posField : Ident *
+ fields : ParseTree *
+ (Ident * ParseTree) list *
+ assemble : (Map -> SynExpr) *
+ Teq<'hasPositional, HasPositional>
+
+type private ParseTreeEval<'ret> =
+ abstract Eval<'a> : ParseTree<'a> -> 'ret
+
+type private ParseTreeCrate =
+ abstract Apply<'ret> : ParseTreeEval<'ret> -> 'ret
+
+[]
+module private ParseTreeCrate =
+ let make<'a> (p : ParseTree<'a>) =
+ { new ParseTreeCrate with
+ member _.Apply a = a.Eval p
+ }
+
+[]
+module private ParseTree =
+ []
+ type State =
+ | Positional of ParseTree * ParseTree list
+ | NoPositional of ParseTree list
+
+ let private cast (t : Teq<'a, 'b>) : Teq, ParseTree<'b>> = Teq.Cong.believeMe t
+
+ /// The `Ident` here is the field name.
+ let branch (assemble : Map -> SynExpr) (subs : (Ident * ParseTreeCrate) list) : ParseTreeCrate =
+ let rec go
+ (selfIdent : Ident option)
+ (acc : (Ident * ParseTree) list, pos : (Ident * ParseTree) option)
+ (subs : (Ident * ParseTreeCrate) list)
+ : ParseTreeCrate
+ =
+ match subs with
+ | [] ->
+ match pos with
+ | None -> ParseTree.Branch (List.rev acc, assemble, Teq.refl) |> ParseTreeCrate.make
+ | Some (posField, pos) ->
+ ParseTree.BranchPos (posField, pos, List.rev acc, assemble, Teq.refl)
+ |> ParseTreeCrate.make
+ | (fieldName, sub) :: subs ->
+ { new ParseTreeEval<_> with
+ member _.Eval (t : ParseTree<'a>) =
+ match t with
+ | ParseTree.NonPositionalLeaf (_, teq)
+ | ParseTree.Branch (_, _, teq) ->
+ go selfIdent (((fieldName, Teq.cast (cast teq) t) :: acc), pos) subs
+ | ParseTree.PositionalLeaf (_, teq)
+ | ParseTree.BranchPos (_, _, _, _, teq) ->
+ match pos with
+ | None -> go selfIdent (acc, Some (fieldName, Teq.cast (cast teq) t)) subs
+ | Some (ident, _) ->
+ failwith
+ $"Multiple entries tried to claim positional args! %s{ident.idText} and %s{fieldName.idText}"
+ }
+ |> sub.Apply
+
+ go None ([], None) subs
+
+ let rec accumulatorsNonPos (tree : ParseTree) : ParseFunctionNonPositional list =
+ match tree with
+ | ParseTree.PositionalLeaf (_, teq) -> exFalso teq
+ | ParseTree.BranchPos (_, _, _, _, teq) -> exFalso teq
+ | ParseTree.NonPositionalLeaf (pf, _) -> [ pf ]
+ | ParseTree.Branch (trees, _, _) -> trees |> List.collect (snd >> accumulatorsNonPos)
+
+ /// Returns the positional arg separately.
+ let rec accumulatorsPos
+ (tree : ParseTree)
+ : ParseFunctionNonPositional list * ParseFunctionPositional
+ =
+ match tree with
+ | ParseTree.PositionalLeaf (pf, _) -> [], pf
+ | ParseTree.NonPositionalLeaf (_, teq) -> exFalso' teq
+ | ParseTree.Branch (_, _, teq) -> exFalso' teq
+ | ParseTree.BranchPos (_, tree, trees, _, _) ->
+ let nonPos = trees |> List.collect (snd >> accumulatorsNonPos)
+
+ let nonPos2, pos = accumulatorsPos tree
+ nonPos @ nonPos2, pos
+
+ /// Collect all the ParseFunctions which are necessary to define variables, throwing away
+ /// all information relevant to composing the resulting variables into records.
+ /// Returns the list of non-positional parsers, and any positional parser that exists.
+ let accumulators<'a> (tree : ParseTree<'a>) : ParseFunctionNonPositional list * ParseFunctionPositional option =
+ // Sad duplication of some code here, but it was the easiest way to make it type-safe :(
+ match tree with
+ | ParseTree.PositionalLeaf (pf, _) -> [], Some pf
+ | ParseTree.NonPositionalLeaf (pf, _) -> [ pf ], None
+ | ParseTree.Branch (trees, _, _) -> trees |> List.collect (snd >> accumulatorsNonPos) |> (fun i -> i, None)
+ | ParseTree.BranchPos (_, tree, trees, _, _) ->
+ let nonPos = trees |> List.collect (snd >> accumulatorsNonPos)
+
+ let nonPos2, pos = accumulatorsPos tree
+ nonPos @ nonPos2, Some pos
+
+ |> fun (nonPos, pos) ->
+ let duplicateArgs =
+ // This is best-effort. We can't necessarily detect all SynExprs here, but usually it'll be strings.
+ Option.toList (pos |> Option.map _.ArgForm) @ (nonPos |> List.map _.ArgForm)
+ |> Seq.concat
+ |> Seq.choose (fun expr ->
+ match expr |> SynExpr.stripOptionalParen with
+ | SynExpr.Const (SynConst.String (s, _, _), _) -> Some s
+ | _ -> None
+ )
+ |> List.ofSeq
+ |> List.groupBy id
+ |> List.choose (fun (key, v) -> if v.Length > 1 then Some key else None)
+
+ match duplicateArgs with
+ | [] -> nonPos, pos
+ | dups ->
+ let dups = dups |> String.concat " "
+ failwith $"Duplicate args detected! %s{dups}"
+
+ /// Build the return value.
+ let rec instantiate<'a> (tree : ParseTree<'a>) : SynExpr =
+ match tree with
+ | ParseTree.NonPositionalLeaf (pf, _) -> SynExpr.createIdent' pf.TargetVariable
+ | ParseTree.PositionalLeaf (pf, _) -> SynExpr.createIdent' pf.TargetVariable
+ | ParseTree.Branch (trees, assemble, _) ->
+ trees
+ |> List.map (fun (fieldName, contents) ->
+ let instantiated = instantiate contents
+ fieldName.idText, instantiated
+ )
+ |> Map.ofList
+ |> assemble
+ | ParseTree.BranchPos (posField, tree, trees, assemble, _) ->
+ let withPos = instantiate tree
+
+ trees
+ |> List.map (fun (fieldName, contents) ->
+ let instantiated = instantiate contents
+ fieldName.idText, instantiated
+ )
+ |> Map.ofList
+ |> Map.add posField.idText withPos
+ |> assemble
+
+[]
+module internal ArgParserGenerator =
+
+ /// Convert e.g. "Foo" into "--foo".
+ let argify (ident : Ident) : string =
+ let result = StringBuilder ()
+
+ for c in ident.idText do
+ if Char.IsUpper c then
+ result.Append('-').Append (Char.ToLowerInvariant c) |> ignore
+ else
+ result.Append c |> ignore
+
+ result.ToString().TrimStart '-'
+
+ let private identifyAsFlag (flagDus : FlagDu list) (ty : SynType) : FlagDu option =
+ match ty with
+ | SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
+ flagDus
+ |> List.tryPick (fun du ->
+ let duName = du.Name.idText
+ let ident = List.last(ident).idText
+ if duName = ident then Some du else None
+ )
+ | _ -> None
+
+ /// Builds a function or lambda of one string argument, which returns a `ty` (as modified by the `Accumulation`;
+ /// for example, maybe it returns a `ty option` or a `ty list`).
+ /// The resulting SynType is the type of the *element* being parsed; so if the Accumulation is List, the SynType
+ /// is the list element.
+ let rec private createParseFunction<'choice>
+ (choice : ArgumentDefaultSpec option -> 'choice)
+ (flagDus : FlagDu list)
+ (fieldName : Ident)
+ (attrs : SynAttribute list)
+ (ty : SynType)
+ : SynExpr * Accumulation<'choice> * SynType
+ =
+ match ty with
+ | String -> SynExpr.createLambda "x" (SynExpr.createIdent "x"), Accumulation.Required, SynType.string
+ | PrimitiveType pt ->
+ SynExpr.createLambda
+ "x"
+ (SynExpr.applyFunction
+ (SynExpr.createLongIdent' (pt @ [ Ident.create "Parse" ]))
+ (SynExpr.createIdent "x")),
+ Accumulation.Required,
+ ty
+ | Uri ->
+ SynExpr.createLambda
+ "x"
+ (SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "Uri" ]) (SynExpr.createIdent "x")),
+ Accumulation.Required,
+ ty
+ | TimeSpan ->
+ let parseExact =
+ attrs
+ |> List.tryPick (fun attr ->
+ match attr.TypeName with
+ | SynLongIdent.SynLongIdent (idents, _, _) ->
+ match idents |> List.map (fun i -> i.idText) |> List.tryLast with
+ | Some "ParseExactAttribute"
+ | Some "ParseExact" -> Some attr.ArgExpr
+ | _ -> None
+ )
+
+ let culture =
+ attrs
+ |> List.tryPick (fun attr ->
+ match attr.TypeName with
+ | SynLongIdent.SynLongIdent (idents, _, _) ->
+ match idents |> List.map (fun i -> i.idText) |> List.tryLast with
+ | Some "InvariantCultureAttribute"
+ | Some "InvariantCulture" -> Some ()
+ | _ -> None
+ )
+
+ let parser =
+ match parseExact, culture with
+ | None, None ->
+ SynExpr.createIdent "x"
+ |> SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "TimeSpan" ; "Parse" ])
+ | Some format, None ->
+ [
+ SynExpr.createIdent "x"
+ format
+ SynExpr.createLongIdent [ "System" ; "Globalization" ; "CultureInfo" ; "CurrentCulture" ]
+ ]
+ |> SynExpr.tuple
+ |> SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "TimeSpan" ; "ParseExact" ])
+ | None, Some () ->
+ [
+ SynExpr.createIdent "x"
+ SynExpr.createLongIdent [ "System" ; "Globalization" ; "CultureInfo" ; "InvariantCulture" ]
+ ]
+ |> SynExpr.tuple
+ |> SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "TimeSpan" ; "Parse" ])
+ | Some format, Some () ->
+ [
+ SynExpr.createIdent "x"
+ format
+ SynExpr.createLongIdent [ "System" ; "Globalization" ; "CultureInfo" ; "InvariantCulture" ]
+ ]
+ |> SynExpr.tuple
+ |> SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "TimeSpan" ; "ParseExact" ])
+ |> SynExpr.createLambda "x"
+
+ parser, Accumulation.Required, ty
+ | FileInfo ->
+ SynExpr.createLambda
+ "x"
+ (SynExpr.applyFunction
+ (SynExpr.createLongIdent [ "System" ; "IO" ; "FileInfo" ])
+ (SynExpr.createIdent "x")),
+ Accumulation.Required,
+ ty
+ | DirectoryInfo ->
+ SynExpr.createLambda
+ "x"
+ (SynExpr.applyFunction
+ (SynExpr.createLongIdent [ "System" ; "IO" ; "DirectoryInfo" ])
+ (SynExpr.createIdent "x")),
+ Accumulation.Required,
+ ty
+ | OptionType eltTy ->
+ let parseElt, acc, childTy =
+ createParseFunction choice flagDus fieldName attrs eltTy
+
+ match acc with
+ | Accumulation.Optional ->
+ failwith
+ $"ArgParser does not support optionals containing options at field %s{fieldName.idText}: %O{ty}"
+ | Accumulation.Choice _ ->
+ failwith
+ $"ArgParser does not support optionals containing choices at field %s{fieldName.idText}: %O{ty}"
+ | Accumulation.List _ ->
+ failwith $"ArgParser does not support optional lists at field %s{fieldName.idText}: %O{ty}"
+ | Accumulation.Required -> parseElt, Accumulation.Optional, childTy
+ | ChoiceType elts ->
+ match elts with
+ | [ elt1 ; elt2 ] ->
+ if not (SynType.provablyEqual elt1 elt2) then
+ failwith
+ $"ArgParser was unable to prove types %O{elt1} and %O{elt2} to be equal in a Choice. We require them to be equal."
+
+ let parseElt, acc, childTy = createParseFunction choice flagDus fieldName attrs elt1
+
+ match acc with
+ | Accumulation.Optional ->
+ failwith
+ $"ArgParser does not support choices containing options at field %s{fieldName.idText}: %O{ty}"
+ | Accumulation.List _ ->
+ failwith
+ $"ArgParser does not support choices containing lists at field %s{fieldName.idText}: %O{ty}"
+ | Accumulation.Choice _ ->
+ failwith
+ $"ArgParser does not support choices containing choices at field %s{fieldName.idText}: %O{ty}"
+ | Accumulation.Required ->
+
+ let relevantAttrs =
+ attrs
+ |> List.choose (fun attr ->
+ let (SynLongIdent.SynLongIdent (name, _, _)) = attr.TypeName
+
+ match name |> List.map _.idText |> List.last with
+ | "ArgumentDefaultFunction"
+ | "ArgumentDefaultFunctionAttribute" ->
+ ArgumentDefaultSpec.FunctionCall (Ident.create ("Default" + fieldName.idText))
+ |> Some
+ | "ArgumentDefaultEnvironmentVariable"
+ | "ArgumentDefaultEnvironmentVariableAttribute" ->
+ ArgumentDefaultSpec.EnvironmentVariable attr.ArgExpr |> Some
+ | _ -> None
+ )
+
+ let relevantAttr =
+ match relevantAttrs with
+ | [] -> None
+ | [ x ] -> Some x
+ | _ ->
+ failwith
+ $"Expected Choice to be annotated with at most one ArgumentDefaultFunction or similar, but it was annotated with multiple. Field: %s{fieldName.idText}"
+
+ parseElt, Accumulation.Choice (choice relevantAttr), childTy
+ | elts ->
+ let elts = elts |> List.map string |> String.concat ", "
+
+ failwith
+ $"ArgParser requires Choice to be of the form Choice<'a, 'a>; that is, two arguments, both the same. For field %s{fieldName.idText}, got: %s{elts}"
+ | ListType eltTy ->
+ let parseElt, acc, childTy =
+ createParseFunction choice flagDus fieldName attrs eltTy
+
+ parseElt, Accumulation.List acc, childTy
+ | ty ->
+ match identifyAsFlag flagDus ty with
+ | None -> failwith $"Could not decide how to parse arguments for field %s{fieldName.idText} of type %O{ty}"
+ | Some flagDu ->
+ // Parse as a bool, and then do the `if-then` dance.
+ let parser =
+ SynExpr.createIdent "x"
+ |> SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "Boolean" ; "Parse" ])
+ |> FlagDu.FromBoolean flagDu
+ |> SynExpr.createLambda "x"
+
+ parser, Accumulation.Required, ty
+
+ let rec private toParseSpec
+ (counter : int)
+ (flagDus : FlagDu list)
+ (ambientRecords : RecordType list)
+ (finalRecord : RecordType)
+ : ParseTreeCrate * int
+ =
+ finalRecord.Fields
+ |> List.iter (fun (SynField.SynField (isStatic = isStatic)) ->
+ if isStatic then
+ failwith "No static record fields allowed in ArgParserGenerator"
+ )
+
+ let counter, contents =
+ ((counter, []), finalRecord.Fields)
+ ||> List.fold (fun (counter, acc) (SynField.SynField (attrs, _, identOption, fieldType, _, _, _, _, _)) ->
+ let attrs = attrs |> List.collect (fun a -> a.Attributes)
+
+ let positionalArgAttr =
+ attrs
+ |> List.tryPick (fun a ->
+ match (List.last a.TypeName.LongIdent).idText with
+ | "PositionalArgsAttribute"
+ | "PositionalArgs" ->
+ match a.ArgExpr with
+ | SynExpr.Const (SynConst.Unit, _) -> Some None
+ | a -> Some (Some a)
+ | _ -> None
+ )
+
+ let parseExactModifier =
+ attrs
+ |> List.tryPick (fun a ->
+ match (List.last a.TypeName.LongIdent).idText with
+ | "ParseExactAttribute"
+ | "ParseExact" -> Some a.ArgExpr
+ | _ -> None
+ )
+
+ let helpText =
+ attrs
+ |> List.tryPick (fun a ->
+ match (List.last a.TypeName.LongIdent).idText with
+ | "ArgumentHelpTextAttribute"
+ | "ArgumentHelpText" -> Some a.ArgExpr
+ | _ -> None
+ )
+
+ let helpText =
+ match parseExactModifier, helpText with
+ | None, ht -> ht
+ | Some pe, None ->
+ SynExpr.createIdent "sprintf"
+ |> SynExpr.applyTo (SynExpr.CreateConst "[Parse format (.NET): %s]")
+ |> SynExpr.applyTo pe
+ |> Some
+ | Some pe, Some ht ->
+ SynExpr.createIdent "sprintf"
+ |> SynExpr.applyTo (SynExpr.CreateConst "%s [Parse format (.NET): %s]")
+ |> SynExpr.applyTo ht
+ |> SynExpr.applyTo pe
+ |> Some
+
+ let ident =
+ match identOption with
+ | None -> failwith "expected args field to have a name, but it did not"
+ | Some i -> i
+
+ let longForms =
+ attrs
+ |> List.choose (fun attr ->
+ match attr.TypeName with
+ | SynLongIdent.SynLongIdent (ident, _, _) ->
+ if (List.last ident).idText = "ArgumentLongForm" then
+ Some attr.ArgExpr
+ else
+ None
+ )
+ |> function
+ | [] -> List.singleton (SynExpr.CreateConst (argify ident))
+ | l -> List.ofSeq l
+
+ let ambientRecordMatch =
+ match fieldType with
+ | SynType.LongIdent (SynLongIdent.SynLongIdent (id, _, _)) ->
+ let target = List.last(id).idText
+ ambientRecords |> List.tryFind (fun r -> r.Name.idText = target)
+ | _ -> None
+
+ match ambientRecordMatch with
+ | Some ambient ->
+ // This field has a type we need to obtain from parsing another record.
+ let spec, counter = toParseSpec counter flagDus ambientRecords ambient
+ counter, (ident, spec) :: acc
+ | None ->
+
+ match positionalArgAttr with
+ | Some includeFlagLike ->
+ let getChoice (spec : ArgumentDefaultSpec option) : unit =
+ match spec with
+ | Some _ ->
+ failwith
+ "Positional Choice args cannot have default values. Remove [] from the positional arg."
+ | None -> ()
+
+ let parser, accumulation, parseTy =
+ createParseFunction getChoice flagDus ident attrs fieldType
+
+ let isBoolLike =
+ match parseTy with
+ | PrimitiveType ident when ident |> List.map _.idText = [ "System" ; "Boolean" ] ->
+ Some (Choice2Of2 ())
+ | parseTy -> identifyAsFlag flagDus parseTy |> Option.map Choice1Of2
+
+ match accumulation with
+ | Accumulation.List (Accumulation.List _) ->
+ failwith "A list of positional args cannot contain lists."
+ | Accumulation.List Accumulation.Optional ->
+ failwith "A list of positional args cannot contain optionals. What would that even mean?"
+ | Accumulation.List (Accumulation.Choice ()) ->
+ {
+ FieldName = ident
+ Parser = parser
+ TargetVariable = Ident.create $"arg_%i{counter}"
+ Accumulation = ChoicePositional.Choice includeFlagLike
+ TargetType = parseTy
+ ArgForm = longForms
+ Help = helpText
+ BoolCases = isBoolLike
+ }
+ |> fun t -> ParseTree.PositionalLeaf (t, Teq.refl)
+ | Accumulation.List Accumulation.Required ->
+ {
+ FieldName = ident
+ Parser = parser
+ TargetVariable = Ident.create $"arg_%i{counter}"
+ Accumulation = ChoicePositional.Normal includeFlagLike
+ TargetType = parseTy
+ ArgForm = longForms
+ Help = helpText
+ BoolCases = isBoolLike
+ }
+ |> fun t -> ParseTree.PositionalLeaf (t, Teq.refl)
+ | Accumulation.Choice _
+ | Accumulation.Optional
+ | Accumulation.Required ->
+ failwith $"Expected positional arg accumulation type to be List, but it was %O{fieldType}"
+ |> ParseTreeCrate.make
+ | None ->
+ let getChoice (spec : ArgumentDefaultSpec option) : ArgumentDefaultSpec =
+ match spec with
+ | None ->
+ failwith
+ "Non-positional Choice args must have an `[]` attribute on them."
+ | Some spec -> spec
+
+ let parser, accumulation, parseTy =
+ createParseFunction getChoice flagDus ident attrs fieldType
+
+ let isBoolLike =
+ match parseTy with
+ | PrimitiveType ident when ident |> List.map _.idText = [ "System" ; "Boolean" ] ->
+ Some (Choice2Of2 ())
+ | parseTy -> identifyAsFlag flagDus parseTy |> Option.map Choice1Of2
+
+ {
+ FieldName = ident
+ Parser = parser
+ TargetVariable = Ident.create $"arg_%i{counter}"
+ Accumulation = accumulation
+ TargetType = parseTy
+ ArgForm = longForms
+ Help = helpText
+ BoolCases = isBoolLike
+ }
+ |> fun t -> ParseTree.NonPositionalLeaf (t, Teq.refl)
+ |> ParseTreeCrate.make
+ |> fun tree -> counter + 1, (ident, tree) :: acc
+ )
+
+ let tree =
+ contents
+ |> List.rev
+ |> ParseTree.branch (fun args ->
+ args
+ |> Map.toList
+ |> List.map (fun (ident, expr) -> SynLongIdent.create [ Ident.create ident ], expr)
+ |> AstHelper.instantiateRecord
+ )
+
+ tree, counter
+
+ /// let helpText : string = ...
+ let private helpText
+ (typeName : Ident)
+ (positional : ParseFunctionPositional option)
+ (args : ParseFunctionNonPositional list)
+ : SynBinding
+ =
+ let describeNonPositional
+ (acc : Accumulation)
+ (flagCases : Choice option)
+ : SynExpr
+ =
+ match acc with
+ | Accumulation.Required -> SynExpr.CreateConst ""
+ | Accumulation.Optional -> SynExpr.CreateConst " (optional)"
+ | Accumulation.Choice (ArgumentDefaultSpec.EnvironmentVariable var) ->
+ // We don't print out the default value in case it's a secret. People often pass secrets
+ // through env vars!
+ var
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.applyFunction
+ (SynExpr.createIdent "sprintf")
+ (SynExpr.CreateConst " (default value populated from env var %s)")
+ )
+ |> SynExpr.paren
+ | Accumulation.Choice (ArgumentDefaultSpec.FunctionCall var) ->
+ match flagCases with
+ | None -> SynExpr.callMethod var.idText (SynExpr.createIdent' typeName)
+ | Some (Choice2Of2 ()) -> SynExpr.callMethod var.idText (SynExpr.createIdent' typeName)
+ | Some (Choice1Of2 flagDu) ->
+ // Care required here. The return value from the Default call is not a bool,
+ // but we should display it as such to the user!
+ [
+ SynMatchClause.create
+ (SynPat.identWithArgs [ flagDu.Name ; flagDu.Case1Name ] (SynArgPats.create []))
+ (SynExpr.ifThenElse
+ (SynExpr.equals flagDu.Case1Arg (SynExpr.CreateConst true))
+ (SynExpr.CreateConst "false")
+ (SynExpr.CreateConst "true"))
+ SynMatchClause.create
+ (SynPat.identWithArgs [ flagDu.Name ; flagDu.Case2Name ] (SynArgPats.create []))
+ (SynExpr.ifThenElse
+ (SynExpr.equals flagDu.Case2Arg (SynExpr.CreateConst true))
+ (SynExpr.CreateConst "false")
+ (SynExpr.CreateConst "true"))
+ ]
+ |> SynExpr.createMatch (SynExpr.callMethod var.idText (SynExpr.createIdent' typeName))
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.createLambda "x" (SynExpr.callMethod "ToString" (SynExpr.createIdent "x"))
+ )
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.applyFunction (SynExpr.createIdent "sprintf") (SynExpr.CreateConst " (default value: %s)")
+ )
+ |> SynExpr.paren
+ | Accumulation.List _ -> SynExpr.CreateConst " (can be repeated)"
+
+ let describePositional _ _ =
+ SynExpr.CreateConst " (positional args) (can be repeated)"
+
+ /// We may sometimes lie about the type name, if e.g. this is a flag DU which we're pretending is a boolean.
+ /// So the `renderTypeName` takes the Accumulation which tells us whether we're lying.
+ let toPrintable (describe : 'a -> Choice option -> SynExpr) (arg : ParseFunction<'a>) : SynExpr =
+ let ty =
+ match arg.BoolCases with
+ | None -> SynType.toHumanReadableString arg.TargetType
+ | Some _ -> "bool"
+
+ let helpText =
+ match arg.Help with
+ | None -> SynExpr.CreateConst ""
+ | Some helpText ->
+ SynExpr.applyFunction (SynExpr.createIdent "sprintf") (SynExpr.CreateConst " : %s")
+ |> SynExpr.applyTo (SynExpr.paren helpText)
+ |> SynExpr.paren
+
+ let descriptor = describe arg.Accumulation arg.BoolCases
+
+ SynExpr.applyFunction (SynExpr.createIdent "sprintf") (SynExpr.CreateConst $"%%s %s{ty}%%s%%s")
+ |> SynExpr.applyTo arg.HumanReadableArgForm
+ |> SynExpr.applyTo descriptor
+ |> SynExpr.applyTo helpText
+ |> SynExpr.paren
+
+ args
+ |> List.map (toPrintable describeNonPositional)
+ |> fun l ->
+ match positional with
+ | None -> l
+ | Some pos -> l @ [ toPrintable describePositional pos ]
+ |> SynExpr.listLiteral
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.applyFunction (SynExpr.createLongIdent [ "String" ; "concat" ]) (SynExpr.CreateConst @"\n")
+ )
+ |> SynBinding.basic [ Ident.create "helpText" ] [ SynPat.unit ]
+
+ /// `let processKeyValue (key : string) (value : string) : Result = ...`
+ /// Returns a possible error.
+ /// A parse failure might not be fatal (e.g. maybe the input was optionally of arity 0, and we failed to do
+ /// the parse because in fact the key decided not to take this argument); in that case we return Error None.
+ let private processKeyValue
+ (argParseErrors : Ident)
+ (pos : ParseFunctionPositional option)
+ (args : ParseFunctionNonPositional list)
+ : SynBinding
+ =
+ let args =
+ args
+ |> List.map (fun arg ->
+ match arg.Accumulation with
+ | Accumulation.Required
+ | Accumulation.Choice _
+ | Accumulation.Optional ->
+ let multipleErrorMessage =
+ SynExpr.createIdent "sprintf"
+ |> SynExpr.applyTo (SynExpr.CreateConst "Argument '%s' was supplied multiple times: %s and %s")
+ |> SynExpr.applyTo arg.HumanReadableArgForm
+ |> SynExpr.applyTo (SynExpr.createIdent "x" |> SynExpr.callMethod "ToString" |> SynExpr.paren)
+ |> SynExpr.applyTo (
+ SynExpr.createIdent "value" |> SynExpr.callMethod "ToString" |> SynExpr.paren
+ )
+
+ let performAssignment =
+ [
+ SynExpr.createIdent "value"
+ |> SynExpr.pipeThroughFunction arg.Parser
+ |> SynExpr.pipeThroughFunction (SynExpr.createIdent "Some")
+ |> SynExpr.assign (SynLongIdent.createI arg.TargetVariable)
+
+ SynExpr.applyFunction (SynExpr.createIdent "Ok") (SynExpr.CreateConst ())
+ ]
+ |> SynExpr.sequential
+
+ [
+ SynMatchClause.create
+ (SynPat.nameWithArgs "Some" [ SynPat.named "x" ])
+ (SynExpr.sequential
+ [
+ multipleErrorMessage
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.dotGet "Add" (SynExpr.createIdent' argParseErrors)
+ )
+ SynExpr.applyFunction (SynExpr.createIdent "Ok") (SynExpr.CreateConst ())
+ ])
+ SynMatchClause.create
+ (SynPat.named "None")
+ (SynExpr.pipeThroughTryWith
+ SynPat.anon
+ (SynExpr.createLongIdent [ "exc" ; "Message" ]
+ |> SynExpr.pipeThroughFunction (SynExpr.createIdent "Some")
+ |> SynExpr.pipeThroughFunction (SynExpr.createIdent "Error"))
+ performAssignment)
+ ]
+ |> SynExpr.createMatch (SynExpr.createIdent' arg.TargetVariable)
+ | Accumulation.List (Accumulation.List _)
+ | Accumulation.List Accumulation.Optional
+ | Accumulation.List (Accumulation.Choice _) ->
+ failwith
+ "WoofWare.Whippet.Plugin.ArgParser invariant violated: expected a list to contain only a Required accumulation. Non-positional lists cannot be optional or Choice, nor can they themselves contain lists."
+ | Accumulation.List Accumulation.Required ->
+ [
+ SynExpr.createIdent "value"
+ |> SynExpr.pipeThroughFunction arg.Parser
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.createLongIdent' [ arg.TargetVariable ; Ident.create "Add" ]
+ )
+ SynExpr.CreateConst () |> SynExpr.pipeThroughFunction (SynExpr.createIdent "Ok")
+ ]
+ |> SynExpr.sequential
+ |> fun expr -> arg.ArgForm, expr
+ )
+
+ let posArg =
+ match pos with
+ | None -> []
+ | Some pos ->
+ [
+ SynExpr.createIdent "value"
+ |> SynExpr.pipeThroughFunction pos.Parser
+ |> fun p ->
+ match pos.Accumulation with
+ | ChoicePositional.Choice _ ->
+ p |> SynExpr.pipeThroughFunction (SynExpr.createIdent "Choice1Of2")
+ | ChoicePositional.Normal _ -> p
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.createLongIdent' [ pos.TargetVariable ; Ident.create "Add" ]
+ )
+ SynExpr.CreateConst () |> SynExpr.pipeThroughFunction (SynExpr.createIdent "Ok")
+ ]
+ |> SynExpr.sequential
+ |> fun expr -> pos.ArgForm, expr
+ |> List.singleton
+
+ (SynExpr.applyFunction (SynExpr.createIdent "Error") (SynExpr.createIdent "None"), posArg @ args)
+ ||> List.fold (fun finalBranch (argForm, arg) ->
+ (finalBranch, argForm)
+ ||> List.fold (fun finalBranch argForm ->
+ arg
+ |> SynExpr.ifThenElse
+ (SynExpr.applyFunction
+ (SynExpr.createLongIdent [ "System" ; "String" ; "Equals" ])
+ (SynExpr.tuple
+ [
+ SynExpr.createIdent "key"
+ SynExpr.applyFunction
+ (SynExpr.applyFunction
+ (SynExpr.createIdent "sprintf")
+ (SynExpr.CreateConst "--%s"))
+ argForm
+ SynExpr.createLongIdent [ "System" ; "StringComparison" ; "OrdinalIgnoreCase" ]
+ ]))
+ finalBranch
+ )
+ )
+ |> SynBinding.basic
+ [ Ident.create "processKeyValue" ]
+ [
+ SynPat.annotateType SynType.string (SynPat.named "key")
+ SynPat.annotateType SynType.string (SynPat.named "value")
+ ]
+ |> SynBinding.withReturnAnnotation (
+ SynType.app "Result" [ SynType.unit ; SynType.appPostfix "option" SynType.string ]
+ )
+ |> SynBinding.withXmlDoc (
+ [
+ " Processes the key-value pair, returning Error if no key was matched."
+ " If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error()."
+ " This can nevertheless be a successful parse, e.g. when the key may have arity 0."
+ ]
+ |> PreXmlDoc.create'
+ )
+
+ /// `let setFlagValue (key : string) : bool = ...`
+ /// The second member of the `flags` list tuple is the constant "true" with which we will interpret the
+ /// arity-0 `--foo`. So in the case of a boolean-typed field, this is `true`; in the case of a Flag-typed field,
+ /// this is `FlagType.WhicheverCaseHadTrue`.
+ let private setFlagValue (argParseErrors : Ident) (flags : (ParseFunction<'a> * SynExpr) list) : SynBinding =
+ (SynExpr.CreateConst false, flags)
+ ||> List.fold (fun finalExpr (flag, trueCase) ->
+ let multipleErrorMessage =
+ SynExpr.createIdent "sprintf"
+ |> SynExpr.applyTo (SynExpr.CreateConst "Flag '%s' was supplied multiple times")
+ |> SynExpr.applyTo flag.HumanReadableArgForm
+
+ let matchFlag =
+ [
+ SynMatchClause.create
+ (SynPat.nameWithArgs "Some" [ SynPat.named "x" ])
+ // This is an error, but it's one we can gracefully report at the end.
+ (SynExpr.sequential
+ [
+ multipleErrorMessage
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.dotGet "Add" (SynExpr.createIdent' argParseErrors)
+ )
+ SynExpr.CreateConst true
+ ])
+
+ SynMatchClause.create
+ (SynPat.named "None")
+ ([
+ SynExpr.assign
+ (SynLongIdent.createI flag.TargetVariable)
+ (SynExpr.pipeThroughFunction (SynExpr.createIdent "Some") trueCase)
+ SynExpr.CreateConst true
+ ]
+ |> SynExpr.sequential)
+ ]
+ |> SynExpr.createMatch (SynExpr.createIdent' flag.TargetVariable)
+
+ (finalExpr, flag.ArgForm)
+ ||> List.fold (fun finalExpr argForm ->
+ SynExpr.ifThenElse
+ (SynExpr.applyFunction
+ (SynExpr.createLongIdent [ "System" ; "String" ; "Equals" ])
+ (SynExpr.tuple
+ [
+ SynExpr.createIdent "key"
+ SynExpr.applyFunction
+ (SynExpr.applyFunction
+ (SynExpr.createIdent "sprintf")
+ (SynExpr.CreateConst "--%s"))
+ argForm
+ SynExpr.createLongIdent [ "System" ; "StringComparison" ; "OrdinalIgnoreCase" ]
+ ]))
+ finalExpr
+ matchFlag
+ )
+ )
+ |> SynBinding.basic [ Ident.create "setFlagValue" ] [ SynPat.annotateType SynType.string (SynPat.named "key") ]
+ |> SynBinding.withReturnAnnotation (SynType.named "bool")
+ |> SynBinding.withXmlDoc (PreXmlDoc.create "Returns false if we didn't set a value.")
+
+ /// `let rec go (state : %ParseState%) (args : string list) : unit = ...`
+ let private mainLoop
+ (parseState : Ident)
+ (errorAcc : Ident)
+ (leftoverArgAcc : ChoicePositional)
+ (leftoverArgs : Ident)
+ (leftoverArgParser : SynExpr)
+ : SynBinding
+ =
+ /// `go (AwaitingValue arg) args`
+ let recurseValue =
+ SynExpr.createIdent "go"
+ |> SynExpr.applyTo (
+ SynExpr.paren (
+ SynExpr.applyFunction
+ (SynExpr.createLongIdent' [ parseState ; Ident.create "AwaitingValue" ])
+ (SynExpr.createIdent "arg")
+ )
+ )
+
+ /// `go AwaitingKey args`
+ let recurseKey =
+ (SynExpr.createIdent "go")
+ |> SynExpr.applyTo (SynExpr.createLongIdent' [ parseState ; Ident.create "AwaitingKey" ])
+ |> SynExpr.applyTo (SynExpr.createIdent "args")
+
+ /// `failwithf "Unable to process argument ..."`
+ let fail =
+ SynExpr.createIdent "failwithf"
+ |> SynExpr.applyTo (SynExpr.CreateConst "Unable to process argument %s as key %s and value %s")
+ |> SynExpr.applyTo (SynExpr.createIdent "arg")
+ |> SynExpr.applyTo (SynExpr.createIdent "key")
+ |> SynExpr.applyTo (SynExpr.createIdent "value")
+
+ let processAsPositional =
+ SynExpr.sequential
+ [
+ SynExpr.createIdent "arg"
+ |> SynExpr.pipeThroughFunction leftoverArgParser
+ |> fun p ->
+ match leftoverArgAcc with
+ | ChoicePositional.Normal _ -> p
+ | ChoicePositional.Choice _ ->
+ p |> SynExpr.pipeThroughFunction (SynExpr.createIdent "Choice1Of2")
+ |> SynExpr.pipeThroughFunction (SynExpr.createLongIdent' [ leftoverArgs ; Ident.create "Add" ])
+
+ recurseKey
+ ]
+
+ let posAttr =
+ match leftoverArgAcc with
+ | ChoicePositional.Choice a
+ | ChoicePositional.Normal a -> a
+
+ let notMatched =
+ let handleFailure =
+ [
+ SynMatchClause.create (SynPat.named "None") fail
+
+ SynMatchClause.create
+ (SynPat.nameWithArgs "Some" [ SynPat.named "msg" ])
+ (SynExpr.sequential
+ [
+ SynExpr.createIdent "sprintf"
+ |> SynExpr.applyTo (SynExpr.CreateConst "%s (at arg %s)")
+ |> SynExpr.applyTo (SynExpr.createIdent "msg")
+ |> SynExpr.applyTo (SynExpr.createIdent "arg")
+ |> SynExpr.pipeThroughFunction (SynExpr.dotGet "Add" (SynExpr.createIdent' errorAcc))
+
+ recurseKey
+ ])
+ ]
+ |> SynExpr.createMatch (SynExpr.createIdent "x")
+
+ match posAttr with
+ | None -> handleFailure
+ | Some posAttr -> SynExpr.ifThenElse posAttr handleFailure processAsPositional
+
+ let argStartsWithDashes =
+ SynExpr.createIdent "arg"
+ |> SynExpr.callMethodArg
+ "StartsWith"
+ (SynExpr.tuple
+ [
+ SynExpr.CreateConst "--"
+ SynExpr.createLongIdent [ "System" ; "StringComparison" ; "Ordinal" ]
+ ])
+
+ let processKey =
+ SynExpr.ifThenElse
+ argStartsWithDashes
+ processAsPositional
+ (SynExpr.ifThenElse
+ (SynExpr.equals (SynExpr.createIdent "arg") (SynExpr.CreateConst "--help"))
+ (SynExpr.createLet
+ [
+ SynBinding.basic
+ [ Ident.create "equals" ]
+ []
+ (SynExpr.callMethodArg "IndexOf" (SynExpr.CreateConst '=') (SynExpr.createIdent "arg"))
+ ]
+ (SynExpr.ifThenElse
+ (SynExpr.lessThan (SynExpr.CreateConst 0) (SynExpr.createIdent "equals"))
+ (SynExpr.createLet
+ [
+ SynBinding.basic
+ [ Ident.create "key" ]
+ []
+ (SynExpr.arrayIndexRange
+ (Some (SynExpr.CreateConst 0))
+ (Some (SynExpr.minusN (SynLongIdent.createS "equals") 1))
+ (SynExpr.createIdent "arg"))
+ SynBinding.basic
+ [ Ident.create "value" ]
+ []
+ (SynExpr.arrayIndexRange
+ (Some (SynExpr.plus (SynExpr.createIdent "equals") (SynExpr.CreateConst 1)))
+ None
+ (SynExpr.createIdent "arg"))
+ ]
+ (SynExpr.createMatch
+ (SynExpr.createIdent "processKeyValue"
+ |> SynExpr.applyTo (SynExpr.createIdent "key")
+ |> SynExpr.applyTo (SynExpr.createIdent "value"))
+ [
+ SynMatchClause.create (SynPat.nameWithArgs "Ok" [ SynPat.unit ]) recurseKey
+
+ SynMatchClause.create
+ (SynPat.nameWithArgs "Error" [ SynPat.named "x" ])
+ notMatched
+ ]))
+ (SynExpr.createIdent "args" |> SynExpr.pipeThroughFunction recurseValue)))
+ (SynExpr.createIdent "helpText"
+ |> SynExpr.applyTo (SynExpr.CreateConst ())
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.applyFunction
+ (SynExpr.createIdent "failwithf")
+ (SynExpr.CreateConst @"Help text requested.\n%s")
+ )))
+
+ let processValue =
+ // During failure, we've received an optional exception message that happened when we tried to parse
+ // the value; it's in the variable `exc`.
+ // `fail` is for the case where we're genuinely emitting an error.
+ // If we're in `PositionalArgs true` mode, though, we won't call `fail`.
+ let fail =
+ [
+ SynExpr.createIdent "failwithf"
+ |> SynExpr.applyTo (
+ SynExpr.CreateConst @"Unable to process supplied arg %s. Help text follows.\n%s"
+ )
+ |> SynExpr.applyTo (SynExpr.createIdent "key")
+ |> SynExpr.applyTo (
+ SynExpr.applyFunction (SynExpr.createIdent "helpText") (SynExpr.CreateConst ())
+ |> SynExpr.paren
+ )
+ |> SynMatchClause.create (SynPat.named "None")
+
+ SynExpr.createIdent "msg"
+ |> SynExpr.pipeThroughFunction (SynExpr.dotGet "Add" (SynExpr.createIdent' errorAcc))
+ |> SynMatchClause.create (SynPat.nameWithArgs "Some" [ SynPat.named "msg" ])
+ ]
+ |> SynExpr.createMatch (SynExpr.createIdent "exc")
+
+ let onFailure =
+ match posAttr with
+ | None -> fail
+ | Some includeFlagLike ->
+ [
+ SynExpr.createIdent "key"
+ |> SynExpr.pipeThroughFunction leftoverArgParser
+ |> fun i ->
+ match leftoverArgAcc with
+ | ChoicePositional.Choice _ ->
+ i |> SynExpr.pipeThroughFunction (SynExpr.createIdent "Choice1Of2")
+ | ChoicePositional.Normal _ -> i
+ |> SynExpr.pipeThroughFunction (SynExpr.createLongIdent' [ leftoverArgs ; Ident.create "Add" ])
+
+ SynExpr.createIdent "go"
+ |> SynExpr.applyTo (SynExpr.createLongIdent' [ parseState ; Ident.create "AwaitingKey" ])
+ |> SynExpr.applyTo (SynExpr.listCons (SynExpr.createIdent "arg") (SynExpr.createIdent "args"))
+ ]
+ |> SynExpr.sequential
+ |> SynExpr.ifThenElse includeFlagLike fail
+
+ [
+ SynMatchClause.create
+ (SynPat.nameWithArgs "Ok" [ SynPat.unit ])
+ (SynExpr.applyFunction
+ (SynExpr.applyFunction
+ (SynExpr.createIdent "go")
+ (SynExpr.createLongIdent' [ parseState ; Ident.create "AwaitingKey" ]))
+ (SynExpr.createIdent "args"))
+ SynMatchClause.create
+ (SynPat.nameWithArgs "Error" [ SynPat.named "exc" ])
+ (SynExpr.ifThenElse
+ (SynExpr.applyFunction (SynExpr.createIdent "setFlagValue") (SynExpr.createIdent "key"))
+ onFailure
+ (SynExpr.createIdent "go"
+ |> SynExpr.applyTo (SynExpr.createLongIdent' [ parseState ; Ident.create "AwaitingKey" ])
+ |> SynExpr.applyTo (SynExpr.listCons (SynExpr.createIdent "arg") (SynExpr.createIdent "args"))))
+ ]
+ |> SynExpr.createMatch (
+ SynExpr.applyFunction
+ (SynExpr.applyFunction (SynExpr.createIdent "processKeyValue") (SynExpr.createIdent "key"))
+ (SynExpr.createIdent "arg")
+ )
+
+ let argBody =
+ [
+ SynMatchClause.create
+ (SynPat.identWithArgs [ parseState ; Ident.create "AwaitingKey" ] (SynArgPats.create []))
+ processKey
+ SynMatchClause.create
+ (SynPat.identWithArgs
+ [ parseState ; Ident.create "AwaitingValue" ]
+ (SynArgPats.createNamed [ "key" ]))
+ processValue
+ ]
+ |> SynExpr.createMatch (SynExpr.createIdent "state")
+
+ let body =
+ let trailingArgMessage =
+ SynExpr.createIdent "sprintf"
+ |> SynExpr.applyTo (
+ SynExpr.CreateConst
+ "Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
+ )
+ |> SynExpr.applyTo (SynExpr.createIdent "key")
+
+ [
+ SynMatchClause.create
+ SynPat.emptyList
+ (SynExpr.createMatch
+ (SynExpr.createIdent "state")
+ [
+ SynMatchClause.create
+ (SynPat.identWithArgs [ parseState ; Ident.create "AwaitingKey" ] (SynArgPats.create []))
+ (SynExpr.CreateConst ())
+ SynMatchClause.create
+ (SynPat.identWithArgs
+ [ parseState ; Ident.create "AwaitingValue" ]
+ (SynArgPats.createNamed [ "key" ]))
+ (SynExpr.ifThenElse
+ (SynExpr.applyFunction
+ (SynExpr.createIdent "setFlagValue")
+ (SynExpr.createIdent "key"))
+ (trailingArgMessage
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.dotGet "Add" (SynExpr.createIdent' errorAcc)
+ ))
+ (SynExpr.CreateConst ()))
+ ])
+ SynMatchClause.create
+ (SynPat.listCons (SynPat.createConst (SynConst.Create "--")) (SynPat.named "rest"))
+ (SynExpr.callMethodArg
+ "AddRange"
+ (SynExpr.paren (
+ SynExpr.createIdent "rest"
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.applyFunction (SynExpr.createLongIdent [ "Seq" ; "map" ]) leftoverArgParser
+ )
+ |> fun p ->
+ match leftoverArgAcc with
+ | ChoicePositional.Normal _ -> p
+ | ChoicePositional.Choice _ ->
+ p
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.applyFunction
+ (SynExpr.createLongIdent [ "Seq" ; "map" ])
+ (SynExpr.createIdent "Choice2Of2")
+ )
+ ))
+ (SynExpr.createIdent' leftoverArgs))
+ SynMatchClause.create (SynPat.listCons (SynPat.named "arg") (SynPat.named "args")) argBody
+ ]
+ |> SynExpr.createMatch (SynExpr.createIdent "args")
+
+ let args =
+ [
+ SynPat.named "state"
+ |> SynPat.annotateType (SynType.createLongIdent [ parseState ])
+ SynPat.named "args"
+ |> SynPat.annotateType (SynType.appPostfix "list" SynType.string)
+ ]
+
+ SynBinding.basic [ Ident.create "go" ] args body
+ |> SynBinding.withRecursion true
+
+ /// Takes a single argument, `args : string list`, and returns something of the type indicated by `recordType`.
+ let createRecordParse
+ (parseState : Ident)
+ (flagDus : FlagDu list)
+ (ambientRecords : RecordType list)
+ (recordType : RecordType)
+ : SynExpr
+ =
+ let spec, _ = toParseSpec 0 flagDus ambientRecords recordType
+ // For each argument (positional and non-positional), create an accumulator for it.
+ let nonPos, pos =
+ { new ParseTreeEval<_> with
+ member _.Eval tree = ParseTree.accumulators tree
+ }
+ |> spec.Apply
+
+ let bindings =
+ nonPos
+ |> List.map (fun pf ->
+ match pf.Accumulation with
+ | Accumulation.Required
+ | Accumulation.Choice _
+ | Accumulation.Optional ->
+ SynExpr.createIdent "None"
+ |> SynBinding.basic [ pf.TargetVariable ] []
+ |> SynBinding.withMutability true
+ |> SynBinding.withReturnAnnotation (SynType.appPostfix "option" pf.TargetType)
+ | Accumulation.List (Accumulation.List _)
+ | Accumulation.List Accumulation.Optional
+ | Accumulation.List (Accumulation.Choice _) ->
+ failwith
+ "WoofWare.Whippet.Plugin.ArgParser invariant violated: expected a list to contain only a Required accumulation. Non-positional lists cannot be optional or Choice, nor can they themselves contain lists."
+ | Accumulation.List Accumulation.Required ->
+ SynExpr.createIdent "ResizeArray"
+ |> SynExpr.applyTo (SynExpr.CreateConst ())
+ |> SynBinding.basic [ pf.TargetVariable ] []
+ |> SynBinding.withReturnAnnotation (SynType.appPostfix "ResizeArray" pf.TargetType)
+ )
+
+ let bindings, leftoverArgsName, leftoverArgsParser =
+ let bindingName, leftoverArgsParser, leftoverArgsType =
+ match pos with
+ | None ->
+ Ident.create "parser_LeftoverArgs",
+ (SynExpr.createLambda "x" (SynExpr.createIdent "x")),
+ SynType.string
+ | Some pf ->
+ match pf.Accumulation with
+ | ChoicePositional.Choice _ ->
+ pf.TargetVariable, pf.Parser, SynType.app "Choice" [ pf.TargetType ; pf.TargetType ]
+ | ChoicePositional.Normal _ -> pf.TargetVariable, pf.Parser, pf.TargetType
+
+ let bindings =
+ SynExpr.createIdent "ResizeArray"
+ |> SynExpr.applyTo (SynExpr.CreateConst ())
+ |> SynBinding.basic [ bindingName ] []
+ |> SynBinding.withReturnAnnotation (SynType.appPostfix "ResizeArray" leftoverArgsType)
+ |> fun b -> b :: bindings
+
+ bindings, bindingName, leftoverArgsParser
+
+ let argParseErrors = Ident.create "ArgParser_errors"
+
+ let errorCollection : SynBinding =
+ SynExpr.createIdent "ResizeArray"
+ |> SynExpr.applyTo (SynExpr.CreateConst ())
+ |> SynBinding.basic [ argParseErrors ] []
+
+ let helpText = helpText recordType.Name pos nonPos
+
+ let bindings = errorCollection :: helpText :: bindings
+
+ let unchecked =
+ SynExpr.createLongIdent [ "Unchecked" ; "defaultof" ]
+ |> SynExpr.typeApp [ SynType.anon ]
+
+ // Determine whether any required arg is missing, and freeze args into immutable form.
+ let freezeNonPositionalArgs =
+ nonPos
+ |> List.map (fun pf ->
+ match pf.Accumulation with
+ | Accumulation.Choice spec ->
+ let getDefaultValue =
+ match spec with
+ | ArgumentDefaultSpec.EnvironmentVariable name ->
+ let result =
+ name
+ |> SynExpr.pipeThroughFunction (SynExpr.createIdent "getEnvironmentVariable")
+
+ /// Assumes access to a non-null variable `x` containing the string value.
+ let parser =
+ match pf.BoolCases with
+ | Some boolLike ->
+ let trueCase, falseCase =
+ match boolLike with
+ | Choice2Of2 () -> SynExpr.CreateConst true, SynExpr.CreateConst false
+ | Choice1Of2 flag ->
+ FlagDu.FromBoolean flag (SynExpr.CreateConst true),
+ FlagDu.FromBoolean flag (SynExpr.CreateConst false)
+
+ // We permit environment variables to be populated with 0 and 1 as well.
+ SynExpr.ifThenElse
+ (SynExpr.applyFunction
+ (SynExpr.createLongIdent [ "System" ; "String" ; "Equals" ])
+ (SynExpr.tuple
+ [
+ SynExpr.createIdent "x"
+ SynExpr.CreateConst "1"
+ SynExpr.createLongIdent
+ [ "System" ; "StringComparison" ; "OrdinalIgnoreCase" ]
+ ]))
+ (SynExpr.ifThenElse
+ (SynExpr.applyFunction
+ (SynExpr.createLongIdent [ "System" ; "String" ; "Equals" ])
+ (SynExpr.tuple
+ [
+ SynExpr.createIdent "x"
+ SynExpr.CreateConst "0"
+ SynExpr.createLongIdent
+ [ "System" ; "StringComparison" ; "OrdinalIgnoreCase" ]
+ ]))
+ (SynExpr.createIdent "x" |> SynExpr.pipeThroughFunction pf.Parser)
+ falseCase)
+ trueCase
+ | None -> (SynExpr.createIdent "x" |> SynExpr.pipeThroughFunction pf.Parser)
+
+ let errorMessage =
+ SynExpr.createIdent "sprintf"
+ |> SynExpr.applyTo (
+ SynExpr.CreateConst
+ "No value was supplied for %s, nor was environment variable %s set"
+ )
+ |> SynExpr.applyTo pf.HumanReadableArgForm
+ |> SynExpr.applyTo name
+
+ [
+ SynMatchClause.create
+ SynPat.createNull
+ (SynExpr.sequential
+ [
+ errorMessage
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.dotGet "Add" (SynExpr.createIdent' argParseErrors)
+ )
+ unchecked
+ ])
+
+ SynMatchClause.create (SynPat.named "x") parser
+ ]
+ |> SynExpr.createMatch result
+ | ArgumentDefaultSpec.FunctionCall name ->
+ SynExpr.callMethod name.idText (SynExpr.createIdent' recordType.Name)
+
+ [
+ SynMatchClause.create
+ (SynPat.named "None")
+ (getDefaultValue
+ |> SynExpr.pipeThroughFunction (SynExpr.createIdent "Choice2Of2"))
+ SynMatchClause.create
+ (SynPat.nameWithArgs "Some" [ SynPat.named "x" ])
+ (SynExpr.applyFunction (SynExpr.createIdent "Choice1Of2") (SynExpr.createIdent "x"))
+ ]
+ |> SynExpr.createMatch (SynExpr.createIdent' pf.TargetVariable)
+ |> SynBinding.basic [ pf.TargetVariable ] []
+ | Accumulation.Optional ->
+ SynBinding.basic [ pf.TargetVariable ] [] (SynExpr.createIdent' pf.TargetVariable)
+ | Accumulation.List (Accumulation.List _)
+ | Accumulation.List Accumulation.Optional
+ | Accumulation.List (Accumulation.Choice _) ->
+ failwith
+ "WoofWare.Whippet.Plugin.ArgParser invariant violated: expected a list to contain only a Required accumulation. Non-positional lists cannot be optional or Choice, nor can they themselves contain lists."
+ | Accumulation.List Accumulation.Required ->
+ SynBinding.basic
+ [ pf.TargetVariable ]
+ []
+ (SynExpr.createIdent' pf.TargetVariable
+ |> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "Seq" ; "toList" ]))
+ | Accumulation.Required ->
+ let errorMessage =
+ SynExpr.createIdent "sprintf"
+ |> SynExpr.applyTo (SynExpr.CreateConst "Required argument '%s' received no value")
+ |> SynExpr.applyTo pf.HumanReadableArgForm
+
+ [
+ SynMatchClause.create
+ (SynPat.named "None")
+ (SynExpr.sequential
+ [
+ errorMessage
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.dotGet "Add" (SynExpr.createIdent' argParseErrors)
+ )
+ unchecked
+ ])
+
+ SynMatchClause.create
+ (SynPat.nameWithArgs "Some" [ SynPat.named "x" ])
+ (SynExpr.createIdent "x")
+ ]
+ |> SynExpr.createMatch (SynExpr.createIdent' pf.TargetVariable)
+ |> SynBinding.basic [ pf.TargetVariable ] []
+ )
+
+ let freezePositional =
+ match pos with
+ | None ->
+ // Check if there are leftover args. If there are, throw.
+ let errorMessage =
+ SynExpr.createIdent' leftoverArgsName
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.applyFunction
+ (SynExpr.createLongIdent [ "String" ; "concat" ])
+ (SynExpr.CreateConst " ")
+ )
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.applyFunction
+ (SynExpr.createIdent "sprintf")
+ (SynExpr.CreateConst "There were leftover args: %s")
+ )
+
+ SynExpr.ifThenElse
+ (SynExpr.dotGet "Count" (SynExpr.createIdent' leftoverArgsName)
+ |> SynExpr.equals (SynExpr.CreateConst 0))
+ (SynExpr.sequential
+ [
+ errorMessage
+ |> SynExpr.pipeThroughFunction (SynExpr.dotGet "Add" (SynExpr.createIdent' argParseErrors))
+ unchecked
+ ])
+ (SynExpr.CreateConst ())
+ | Some _ ->
+ SynExpr.createIdent' leftoverArgsName
+ |> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "Seq" ; "toList" ])
+ |> SynBinding.basic [ leftoverArgsName ] []
+ |> List.singleton
+
+ let freezeArgs = freezePositional @ freezeNonPositionalArgs
+
+ let retValue =
+ let happyPath =
+ { new ParseTreeEval<_> with
+ member _.Eval tree = ParseTree.instantiate tree
+ }
+ |> spec.Apply
+
+ let sadPath =
+ SynExpr.createIdent' argParseErrors
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.applyFunction (SynExpr.createLongIdent [ "String" ; "concat" ]) (SynExpr.CreateConst @"\n")
+ )
+ |> SynExpr.pipeThroughFunction (
+ SynExpr.createIdent "failwithf"
+ |> SynExpr.applyTo (SynExpr.CreateConst @"Errors during parse!\n%s")
+ )
+
+ let areErrors =
+ SynExpr.dotGet "Count" (SynExpr.createIdent' argParseErrors)
+ |> SynExpr.equals (SynExpr.CreateConst 0)
+
+ SynExpr.ifThenElse areErrors sadPath happyPath
+
+ let flags =
+ nonPos
+ |> List.choose (fun pf ->
+ match pf.TargetType with
+ | PrimitiveType pt ->
+ if (pt |> List.map _.idText) = [ "System" ; "Boolean" ] then
+ Some (pf, SynExpr.CreateConst true)
+ else
+ None
+ | ty ->
+ match identifyAsFlag flagDus ty with
+ | Some flag -> (pf, FlagDu.FromBoolean flag (SynExpr.CreateConst true)) |> Some
+ | _ -> None
+ )
+
+ let leftoverArgAcc =
+ match pos with
+ | None -> ChoicePositional.Normal None
+ | Some pos -> pos.Accumulation
+
+ [
+ SynExpr.createIdent "go"
+ |> SynExpr.applyTo (SynExpr.createLongIdent' [ parseState ; Ident.create "AwaitingKey" ])
+ |> SynExpr.applyTo (SynExpr.createIdent "args")
+
+ SynExpr.createLet freezeArgs retValue
+ ]
+ |> SynExpr.sequential
+ |> SynExpr.createLet (
+ bindings
+ @ [
+ processKeyValue argParseErrors pos nonPos
+ setFlagValue argParseErrors flags
+ mainLoop parseState argParseErrors leftoverArgAcc leftoverArgsName leftoverArgsParser
+ ]
+ )
+
+ // The type for which we're generating args may refer to any of the supplied records/unions.
+ let createModule
+ (opens : SynOpenDeclTarget list)
+ (ns : LongIdent)
+ ((taggedType : SynTypeDefn, spec : ArgParserOutputSpec))
+ (allUnionTypes : UnionType list)
+ (allRecordTypes : RecordType list)
+ : SynModuleOrNamespace
+ =
+ let flagDus =
+ allUnionTypes
+ |> List.choose (fun ty ->
+ match ty.Cases with
+ | [ c1 ; c2 ] ->
+ let c1Attr =
+ c1.Attributes
+ |> List.tryPick (fun attr ->
+ match attr.TypeName with
+ | SynLongIdent.SynLongIdent (id, _, _) ->
+ match id |> List.last |> _.idText with
+ | "ArgumentFlagAttribute"
+ | "ArgumentFlag" -> Some (SynExpr.stripOptionalParen attr.ArgExpr)
+ | _ -> None
+ )
+
+ let c2Attr =
+ c2.Attributes
+ |> List.tryPick (fun attr ->
+ match attr.TypeName with
+ | SynLongIdent.SynLongIdent (id, _, _) ->
+ match id |> List.last |> _.idText with
+ | "ArgumentFlagAttribute"
+ | "ArgumentFlag" -> Some (SynExpr.stripOptionalParen attr.ArgExpr)
+ | _ -> None
+ )
+
+ match c1Attr, c2Attr with
+ | Some _, None
+ | None, Some _ ->
+ failwith
+ "[] must be placed on both cases of a two-case discriminated union, with opposite argument values on each case."
+ | None, None -> None
+ | Some c1Attr, Some c2Attr ->
+
+ // Sanity check where possible
+ match c1Attr, c2Attr with
+ | SynExpr.Const (SynConst.Bool b1, _), SynExpr.Const (SynConst.Bool b2, _) ->
+ if b1 = b2 then
+ failwith
+ "[] must have opposite argument values on each case in a two-case discriminated union."
+ | _, _ -> ()
+
+ match c1.Fields, c2.Fields with
+ | [], [] ->
+ {
+ Name = ty.Name
+ Case1Name = c1.Name
+ Case1Arg = c1Attr
+ Case2Name = c2.Name
+ Case2Arg = c2Attr
+ }
+ |> Some
+ | _, _ ->
+ failwith "[] may only be placed on discriminated union members with no data."
+ | _ -> None
+ )
+
+ let taggedType =
+ match taggedType with
+ | SynTypeDefn.SynTypeDefn (sci,
+ SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (access, fields, _), _),
+ smd,
+ _,
+ _,
+ _) -> RecordType.OfRecord sci smd access fields
+ | _ -> failwith "[] currently only supports being placed on records."
+
+ let modAttrs, modName =
+ if spec.ExtensionMethods then
+ [ SynAttribute.autoOpen ], Ident.create (taggedType.Name.idText + "ArgParse")
+ else
+ [ SynAttribute.requireQualifiedAccess ; SynAttribute.compilationRepresentation ], taggedType.Name
+
+ let modInfo =
+ SynComponentInfo.create modName
+ |> SynComponentInfo.withDocString (
+ PreXmlDoc.create $"Methods to parse arguments for the type %s{taggedType.Name.idText}"
+ )
+ |> SynComponentInfo.addAttributes modAttrs
+
+ let parseStateIdent = Ident.create $"ParseState_%s{taggedType.Name.idText}"
+
+ let parseStateType =
+ [
+ SynUnionCase.create
+ {
+ Attributes = []
+ Fields = []
+ Name = Ident.create "AwaitingKey"
+ XmlDoc = Some (PreXmlDoc.create "Ready to consume a key or positional arg")
+ Access = None
+ }
+ SynUnionCase.create
+ {
+ Attributes = []
+ Fields =
+ [
+ {
+ Attrs = []
+ Ident = Some (Ident.create "key")
+ Type = SynType.string
+ }
+ ]
+ Name = Ident.create "AwaitingValue"
+ XmlDoc = Some (PreXmlDoc.create "Waiting to receive a value for the key we've already consumed")
+ Access = None
+ }
+ ]
+ |> SynTypeDefnRepr.union
+ |> SynTypeDefn.create (
+ SynComponentInfo.create parseStateIdent
+ |> SynComponentInfo.setAccessibility (Some (SynAccess.Private range0))
+ )
+ |> List.singleton
+ |> SynModuleDecl.createTypes
+
+ let taggedMod =
+ let argsParam =
+ SynPat.named "args"
+ |> SynPat.annotateType (SynType.appPostfix "list" SynType.string)
+
+ let parsePrime =
+ createRecordParse parseStateIdent flagDus allRecordTypes taggedType
+ |> SynBinding.basic
+ [ Ident.create "parse'" ]
+ [
+ SynPat.named "getEnvironmentVariable"
+ |> SynPat.annotateType (SynType.funFromDomain SynType.string SynType.string)
+ argsParam
+ ]
+ |> SynBinding.withReturnAnnotation (SynType.createLongIdent [ taggedType.Name ])
+
+ let parsePrimeCall =
+ if spec.ExtensionMethods then
+ // need to fully qualify
+ [ taggedType.Name ; Ident.create "parse'" ]
+ else
+ [ Ident.create "parse'" ]
+
+ let parse =
+ SynExpr.createLongIdent' parsePrimeCall
+ |> SynExpr.applyTo (SynExpr.createLongIdent [ "System" ; "Environment" ; "GetEnvironmentVariable" ])
+ |> SynExpr.applyTo (SynExpr.createIdent "args")
+ |> SynBinding.basic [ Ident.create "parse" ] [ argsParam ]
+ |> SynBinding.withReturnAnnotation (SynType.createLongIdent [ taggedType.Name ])
+
+ [
+ yield parseStateType
+
+ if spec.ExtensionMethods then
+ let bindingPrime = parsePrime |> SynMemberDefn.staticMember
+
+ let binding = parse |> SynMemberDefn.staticMember
+
+ let componentInfo =
+ SynComponentInfo.create taggedType.Name
+ |> SynComponentInfo.withDocString (PreXmlDoc.create "Extension methods for argument parsing")
+
+ let containingType =
+ SynTypeDefnRepr.augmentation ()
+ |> SynTypeDefn.create componentInfo
+ |> SynTypeDefn.withMemberDefns [ bindingPrime ; binding ]
+
+ yield SynModuleDecl.createTypes [ containingType ]
+ else
+ yield SynModuleDecl.createLet parsePrime
+
+ yield SynModuleDecl.createLet parse
+ ]
+ |> SynModuleDecl.nestedModule modInfo
+
+ [
+ for openStatement in opens do
+ yield SynModuleDecl.openAny openStatement
+ yield taggedMod
+ ]
+ |> SynModuleOrNamespace.createNamespace ns
+
+/// Whippet generator that provides an argument parser, taking a list of command line args and parsing them into a
+/// structured record.
+[]
+type ArgParserGenerator () =
+
+ interface IGenerateRawFromRaw with
+ member _.GenerateRawFromRaw (context : RawSourceGenerationArgs) =
+ if not (context.FilePath.EndsWith (".fs", StringComparison.Ordinal)) then
+ null
+ else
+
+ let ast = Ast.parse (Encoding.UTF8.GetString context.FileContents)
+
+ let types = Ast.getTypes ast
+
+ let opens = AstHelper.extractOpens ast
+
+ let namespaceAndTypes =
+ types
+ |> List.collect (fun (ns, types) ->
+ let typeWithAttr =
+ types
+ |> List.choose (fun ty ->
+ match SynTypeDefn.getAttribute "ArgParserAttribute" ty with
+ | None -> None
+ | Some attr ->
+ let arg =
+ match SynExpr.stripOptionalParen attr.ArgExpr with
+ | SynExpr.Const (SynConst.Bool value, _) -> value
+ | SynExpr.Const (SynConst.Unit, _) -> true
+ | arg ->
+ failwith
+ $"Unrecognised argument %+A{arg} to []. Literals are not supported. Use `true` or `false` (or unit) only."
+
+ let spec =
+ {
+ ExtensionMethods = arg
+ }
+
+ Some (ty, spec)
+ )
+
+ typeWithAttr
+ |> List.map (fun taggedType ->
+ let unions, records, others =
+ (([], [], []), types)
+ ||> List.fold (fun
+ (unions, records, others)
+ (SynTypeDefn.SynTypeDefn (sci, repr, smd, _, _, _) as ty) ->
+ match repr with
+ | SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union (access, cases, _), _) ->
+ UnionType.OfUnion sci smd access cases :: unions, records, others
+ | SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (access, fields, _), _) ->
+ unions, RecordType.OfRecord sci smd access fields :: records, others
+ | _ -> unions, records, ty :: others
+ )
+
+ if not others.IsEmpty then
+ failwith
+ $"Error: all types recursively defined together with an ArgParserGenerator type must be discriminated unions or records. %+A{others}"
+
+ (ns, taggedType, unions, records)
+ )
+ )
+
+ let modules =
+ namespaceAndTypes
+ |> List.map (fun (ns, taggedType, unions, records) ->
+ ArgParserGenerator.createModule opens ns taggedType unions records
+ )
+
+ Ast.render modules |> Option.toObj
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/README.md b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/README.md
new file mode 100644
index 0000000..c552a7d
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/README.md
@@ -0,0 +1,97 @@
+# WoofWare.Whippet.Plugin.ArgParser
+
+This is a [Whippet](https://github.com/Smaug123/WoofWare.Whippet) plugin defining an argument parser.
+
+It is a copy of the corresponding [Myriad](https://github.com/MoiraeSoftware/myriad) arg parser in [WoofWare.Myriad](https://github.com/Smaug123/WoofWare.Myriad), taken from commit d59ebdfccb87a06579fb99008a15f58ea8be394e.
+
+## Usage
+
+Define an `Args.fs` file like the following:
+
+```fsharp
+namespace MyNamespace
+
+[]
+type LoadsOfTypes =
+ {
+ Foo : int
+ Bar : string
+ Baz : bool
+ SomeFile : FileInfo
+ SomeDirectory : DirectoryInfo
+ SomeList : DirectoryInfo list
+ OptionalThingWithNoDefault : int option
+ []
+ Positionals : int list
+ []
+ OptionalThing : Choice
+ []
+ AnotherOptionalThing : Choice
+ []
+ YetAnotherOptionalThing : Choice
+ }
+
+ static member DefaultOptionalThing () = true
+
+ static member DefaultAnotherOptionalThing () = 3
+```
+
+In your fsproj:
+
+```xml
+
+
+
+
+ Args.fs
+
+
+
+
+
+
+
+
+
+
+
+```
+
+The generator will produce a file like the following:
+
+```fsharp
+[]
+module LoadsOfTypes =
+ // in case you want to test it, you get one with dependencies injected
+ let parse' (getEnvVar : string -> string) (args : string list) : LoadsOfTypes = ...
+ // this is the one we expect you actually want to use, if you don't want to test the arg parser
+ let parse (args : string list) : LoadsOfTypes = ...
+```
+
+## Features
+
+* Default arguments are handled as `Choice<'a, 'a>`: you get a `Choice1Of2` if the user provided the input, or a `Choice2Of2` if the parser filled in your specified default value.
+* Default arguments from the environment, specified with `[]`. If such an arg is not supplied on the command line, its value is parsed from the value of that env var.
+* Default arguments, specified with `[]`. If an arg `[] Foo : Choice<'a, 'a>` is not supplied on the command line, the parser calls `DefaultFoo : unit -> 'a` to obtain its value.
+* Positional arguments: a list with attribute `[]` will accumulate all args which didn't match anything else. By default, the parser will fail if any of these arguments looks like an arg itself (i.e. it starts with `--`) but comes *before* a positional arg separator `--`; you can optionally give this attribute the argument `(* includeFlagLike = *) true` to instead just put such flag-like args into the accumulator.
+* Positional args can also be of type `Choice<'a, 'a> list`, in which case we tell you whether the arg came before (`Choice1Of2`) or after (`Choice2Of2`) any `--` positional args separator.
+* You can control TimeSpan and friends with the `[]` and `[]` attributes.
+* By default, we generate F# extension methods for the type; you can instead create a module with the type's name, using `[]`.
+* If `--help` appears in a position where the parser is expecting a key (e.g. in the first position, or after a `--foo=bar`), the parser fails with help text. The parser also makes a limited effort to supply help text when encountering an invalid parse.
+* "Flag DUs": if a two-case DU appears in the generator input file, you can tag its cases as in `type DryRun = | [] Wet | [] Dry`. Then you can consume the flag like a bool: `[] type Args = { DryRun : DryRun }`, so `--dry-run` is parsed into `DryRun.Dry`.
+* Control long forms of arguments with `[] Foo : int`, so that instead of accepting the default `--foo=3`, we accept `--alternative-name=3`.
+* Custom help text for individual args is supplied with `[]`, and similarly help text for the entire args object is supplied with `[] [] type Args = ...`.
+
+## Limitations
+
+This is very bare-bones, but do raise GitHub issues if you like (or if you find cases where the parser does the wrong thing).
+
+* Help is signalled by throwing an exception, so you'll get an unsightly stack trace and a nonzero exit code.
+* Help doesn't take into account any arguments the user has entered. Ideally you'd get contextual information like an identification of which args the user has supplied at the point where the parse failed or help was requested.
+* I don't handle very many types, and in particular a real arg parser would handle DUs and records with nesting.
+* I don't try very hard to find a valid parse. It may well be possible to find a case where I fail to parse despite there existing a valid parse.
+* There's no subcommand support (you'll have to do that yourself).
+
+It should work fine if you just want to compose a few primitive types, though.
diff --git a/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/WoofWare.Whippet.Plugin.ArgParser.Test/TestArgParser.fs b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/WoofWare.Whippet.Plugin.ArgParser.Test/TestArgParser.fs
new file mode 100644
index 0000000..afba69c
--- /dev/null
+++ b/Plugins/ArgParser/WoofWare.Whippet.Plugin.ArgParser/WoofWare.Whippet.Plugin.ArgParser.Test/TestArgParser.fs
@@ -0,0 +1,706 @@
+namespace WoofWare.Whippet.Plugin.ArgParser.Test
+
+open System
+open System.Threading
+open NUnit.Framework
+open FsUnitTyped
+open Consumer
+open FsCheck
+
+[]
+module TestArgParser =
+
+ []
+ []
+ let ``Positionals get parsed: they don't have to be strings`` (sep : bool) =
+ let getEnvVar (_ : string) = failwith "should not call"
+
+ let property
+ (fooSep : bool)
+ (barSep : bool)
+ (bazSep : bool)
+ (pos0 : int list)
+ (pos1 : int list)
+ (pos2 : int list)
+ (pos3 : int list)
+ (pos4 : int list)
+ =
+ let args =
+ [
+ yield! pos0 |> List.map string
+ if fooSep then
+ yield "--foo=3"
+ else
+ yield "--foo"
+ yield "3"
+ yield! pos1 |> List.map string
+ if barSep then
+ yield "--bar=4"
+ else
+ yield "--bar"
+ yield "4"
+ yield! pos2 |> List.map string
+ if bazSep then
+ yield "--baz=true"
+ else
+ yield "--baz"
+ yield "true"
+ yield! pos3 |> List.map string
+ if sep then
+ yield "--"
+ yield! pos4 |> List.map string
+ ]
+
+ BasicWithIntPositionals.parse' getEnvVar args
+ |> shouldEqual
+ {
+ Foo = 3
+ Bar = "4"
+ Baz = true
+ Rest = pos0 @ pos1 @ pos2 @ pos3 @ pos4
+ }
+
+ Check.QuickThrowOnFailure property
+
+ []
+ let ``Arg-like thing appearing before double dash`` () =
+ let envCalls = ref 0
+
+ let getEnvVar (_ : string) =
+ Interlocked.Increment envCalls |> ignore
+ ""
+
+ let args = [ "--foo=3" ; "--non-existent" ; "--bar=4" ; "--baz=true" ]
+
+ let exc =
+ Assert.Throws (fun () -> Basic.parse' getEnvVar args |> ignore)
+
+ envCalls.Value |> shouldEqual 0
+
+ exc.Message
+ |> shouldEqual
+ """Unable to process supplied arg --non-existent. Help text follows.
+--foo int32 : This is a foo!
+--bar string
+--baz bool
+--rest string (positional args) (can be repeated) : Here's where the rest of the args go"""
+
+ []
+ let ``Can supply positional args with key`` () =
+ let envCalls = ref 0
+
+ let getEnvVar (_ : string) =
+ Interlocked.Increment envCalls |> ignore
+ ""
+
+ let property (args : (int * bool) list) (afterDoubleDash : int list option) =
+ let flatArgs =
+ args
+ |> List.collect (fun (value, sep) ->
+ if sep then
+ [ $"--rest=%i{value}" ]
+ else
+ [ "--rest" ; string value ]
+ )
+ |> fun l -> l @ [ "--foo=3" ; "--bar=4" ; "--baz=true" ]
+
+ let flatArgs, expected =
+ match afterDoubleDash with
+ | None -> flatArgs, List.map fst args
+ | Some rest -> flatArgs @ [ "--" ] @ (List.map string rest), List.map fst args @ rest
+
+ BasicWithIntPositionals.parse' getEnvVar flatArgs
+ |> shouldEqual
+ {
+ Foo = 3
+ Bar = "4"
+ Baz = true
+ Rest = expected
+ }
+
+ Check.QuickThrowOnFailure property
+ envCalls.Value |> shouldEqual 0
+
+ []
+ let ``Consume multiple occurrences of required arg`` () =
+ let envCalls = ref 0
+
+ let getEnvVar (_ : string) =
+ Interlocked.Increment envCalls |> ignore
+ ""
+
+ let args = [ "--foo=3" ; "--rest" ; "7" ; "--bar=4" ; "--baz=true" ; "--rest=8" ]
+
+ let result = BasicNoPositionals.parse' getEnvVar args
+
+ envCalls.Value |> shouldEqual 0
+
+ result
+ |> shouldEqual
+ {
+ Foo = 3
+ Bar = "4"
+ Baz = true
+ Rest = [ 7 ; 8 ]
+ }
+
+ []
+ let ``Gracefully handle invalid multiple occurrences of required arg`` () =
+ let envCalls = ref 0
+
+ let getEnvVar (_ : string) =
+ Interlocked.Increment envCalls |> ignore
+ ""
+
+ let args = [ "--foo=3" ; "--foo" ; "9" ; "--bar=4" ; "--baz=true" ; "--baz=false" ]
+
+ let exc =
+ Assert.Throws (fun () -> Basic.parse' getEnvVar args |> ignore)
+
+ envCalls.Value |> shouldEqual 0
+
+ exc.Message
+ |> shouldEqual
+ """Errors during parse!
+Argument '--foo' was supplied multiple times: 3 and 9
+Argument '--baz' was supplied multiple times: True and false"""
+
+ []
+ let ``Args appearing after double dash are positional`` () =
+ let envCalls = ref 0
+
+ let getEnvVar (_ : string) =
+ Interlocked.Increment envCalls |> ignore
+ ""
+
+ let args = [ "--" ; "--foo=3" ; "--bar=4" ; "--baz=true" ]
+
+ let exc =
+ Assert.Throws (fun () -> Basic.parse' getEnvVar args |> ignore)
+
+ exc.Message
+ |> shouldEqual
+ """Errors during parse!
+Required argument '--foo' received no value
+Required argument '--bar' received no value
+Required argument '--baz' received no value"""
+
+ envCalls.Value |> shouldEqual 0
+
+ []
+ let ``Help text`` () =
+ let getEnvVar (s : string) =
+ s |> shouldEqual "CONSUMEPLUGIN_THINGS"
+ "hi!"
+
+ let exc =
+ Assert.Throws (fun () -> Basic.parse' getEnvVar [ "--help" ] |> ignore)
+
+ exc.Message
+ |> shouldEqual
+ """Help text requested.
+--foo int32 : This is a foo!
+--bar string
+--baz bool
+--rest string (positional args) (can be repeated) : Here's where the rest of the args go"""
+
+ []
+ let ``Help text, with default values`` () =
+ let envVars = ref 0
+
+ let getEnvVar (_ : string) =
+ Interlocked.Increment envVars |> ignore
+ ""
+
+ let exc =
+ Assert.Throws (fun () -> LoadsOfTypes.parse' getEnvVar [ "--help" ] |> ignore)
+
+ exc.Message
+ |> shouldEqual
+ """Help text requested.
+--foo int32
+--bar string
+--baz bool
+--some-file FileInfo
+--some-directory DirectoryInfo
+--some-list DirectoryInfo (can be repeated)
+--optional-thing-with-no-default int32 (optional)
+--optional-thing bool (default value: True)
+--another-optional-thing int32 (default value: 3)
+--yet-another-optional-thing string (default value populated from env var CONSUMEPLUGIN_THINGS)
+--positionals int32 (positional args) (can be repeated)"""
+
+ envVars.Value |> shouldEqual 0
+
+ []
+ let ``Default values`` () =
+ let getEnvVar (s : string) =
+ s |> shouldEqual "CONSUMEPLUGIN_THINGS"
+ "hi!"
+
+ let args =
+ [
+ "--foo"
+ "3"
+ "--bar=some string"
+ "--baz"
+ "--some-file=/path/to/file"
+ "--some-directory"
+ "/a/dir"
+ "--another-optional-thing"
+ "3000"
+ ]
+
+ let result = LoadsOfTypes.parse' getEnvVar args
+
+ result.OptionalThing |> shouldEqual (Choice2Of2 true)
+ result.OptionalThingWithNoDefault |> shouldEqual None
+ result.AnotherOptionalThing |> shouldEqual (Choice1Of2 3000)
+ result.YetAnotherOptionalThing |> shouldEqual (Choice2Of2 "hi!")
+
+ []
+ let ``ParseExact and help`` () =
+ let count = ref 0
+
+ let getEnvVar (_ : string) =
+ Interlocked.Increment count |> ignore
+ ""
+
+ let exc =
+ Assert.Throws (fun () -> DatesAndTimes.parse' getEnvVar [ "--help" ] |> ignore)
+
+ exc.Message
+ |> shouldEqual
+ @"Help text requested.
+--plain TimeSpan
+--invariant TimeSpan
+--exact TimeSpan : An exact time please [Parse format (.NET): hh\:mm\:ss]
+--invariant-exact TimeSpan : [Parse format (.NET): hh\:mm\:ss]"
+
+ count.Value |> shouldEqual 0
+
+ []
+ let rec ``TimeSpans and their attributes`` () =
+ let count = ref 0
+
+ let getEnvVar (_ : string) =
+ Interlocked.Increment count |> ignore
+ ""
+
+ let parsed =
+ DatesAndTimes.parse'
+ getEnvVar
+ [
+ "--exact=11:34:00"
+ "--plain=1"
+ "--invariant=23:59"
+ "--invariant-exact=23:59:00"
+ ]
+
+ parsed.Plain |> shouldEqual (TimeSpan (1, 0, 0, 0))
+ parsed.Invariant |> shouldEqual (TimeSpan (23, 59, 00))
+ parsed.Exact |> shouldEqual (TimeSpan (11, 34, 00))
+ parsed.InvariantExact |> shouldEqual (TimeSpan (23, 59, 00))
+
+ let exc =
+ Assert.Throws (fun () ->
+ DatesAndTimes.parse'
+ getEnvVar
+ [
+ "--exact=11:34:00"
+ "--plain=1"
+ "--invariant=23:59"
+ "--invariant-exact=23:59"
+ ]
+ |> ignore
+ )
+
+ exc.Message
+ |> shouldEqual
+ """Errors during parse!
+Input string was not in a correct format. (at arg --invariant-exact=23:59)
+Required argument '--invariant-exact' received no value"""
+
+ let exc =
+ Assert.Throws (fun () ->
+ DatesAndTimes.parse'
+ getEnvVar
+ [
+ "--exact=11:34"
+ "--plain=1"
+ "--invariant=23:59"
+ "--invariant-exact=23:59:00"
+ ]
+ |> ignore
+ )
+
+ exc.Message
+ |> shouldEqual
+ """Errors during parse!
+Input string was not in a correct format. (at arg --exact=11:34)
+Required argument '--exact' received no value"""
+
+ count.Value |> shouldEqual 0
+
+ []
+ let ``Can consume stacked record without positionals`` () =
+ let getEnvVar (_ : string) = failwith "should not call"
+
+ let parsed =
+ ParentRecord.parse' getEnvVar [ "--and-another=true" ; "--thing1=9" ; "--thing2=a thing!" ]
+
+ parsed
+ |> shouldEqual
+ {
+ Child =
+ {
+ Thing1 = 9
+ Thing2 = "a thing!"
+ }
+ AndAnother = true
+ }
+
+ []
+ let ``Can consume stacked record, child has positionals`` () =
+ let getEnvVar (_ : string) = failwith "should not call"
+
+ let parsed =
+ ParentRecordChildPos.parse'
+ getEnvVar
+ [
+ "--and-another=true"
+ "--thing1=9"
+ "--thing2=https://example.com"
+ "--thing2=http://example.com"
+ ]
+
+ parsed.AndAnother |> shouldEqual true
+ parsed.Child.Thing1 |> shouldEqual 9
+
+ parsed.Child.Thing2
+ |> List.map (fun (x : Uri) -> x.ToString ())
+ |> shouldEqual [ "https://example.com/" ; "http://example.com/" ]
+
+ []
+ let ``Can consume stacked record, child has no positionals, parent has positionals`` () =
+ let getEnvVar (_ : string) = failwith "should not call"
+
+ let parsed =
+ ParentRecordSelfPos.parse'
+ getEnvVar
+ [
+ "--and-another=true"
+ "--and-another=false"
+ "--and-another=true"
+ "--thing1=9"
+ "--thing2=some"
+ ]
+
+ parsed
+ |> shouldEqual
+ {
+ Child =
+ {
+ Thing1 = 9
+ Thing2 = "some"
+ }
+ AndAnother = [ true ; false ; true ]
+ }
+
+ []
+ let ``Help text for stacked records`` () =
+ let getEnvVar (_ : string) = failwith "should not call"
+
+ let exc =
+ Assert.Throws