├── .github ├── CODEOWNERS └── workflows │ └── main.yml ├── .gitattributes ├── .vscode ├── spellright.dict └── settings.json ├── paket.cmd ├── paket.sh ├── src ├── BlackFox.FoxSharp.Tests │ ├── paket.references │ ├── Main.fs │ ├── CmdLineTests.fs │ ├── DotnetCoreUnixCommandLineTests.fs │ ├── BlackFox.FoxSharp.Tests.fsproj │ ├── MonoUnixCommandLineTests.fs │ ├── MsvcrCommandLineTests.fs │ └── JavaPropertiesFileTests.fs ├── BlackFox.VsWhere │ ├── Icon.png │ ├── Icon.afdesign │ ├── BlackFox.VsWhere.fsproj │ ├── Release Notes.md │ ├── Types.fs │ ├── Readme.md │ ├── ComTypes.fs │ └── VsInstances.fs ├── BlackFox.CommandLine │ ├── Icon.png │ ├── Icon.afdesign │ ├── BlackFox.CommandLine.fsproj │ ├── Release Notes.md │ ├── MonoUnixCommandLine.fs │ ├── MsvcrCommandLine.fs │ ├── Readme.md │ └── CommandLine.fs ├── BlackFox.Fake.BuildTask │ ├── Icon.pdn │ ├── Icon.png │ ├── BlackFox.Fake.BuildTask.fsproj │ ├── Release Notes.md │ ├── Readme.md │ └── BuildTask.fs ├── BlackFox.JavaPropertiesFile │ ├── Icon.png │ ├── Icon.afdesign │ ├── Types.fs │ ├── Readme.md │ ├── Release Notes.md │ ├── BlackFox.JavaPropertiesFile.fsproj │ ├── JavaPropertiesFile.fs │ └── Parser.fs ├── BlackFox.CachedFSharpReflection │ ├── Release Notes.md │ ├── BlackFox.CachedFSharpReflection.fsproj │ ├── FSharpReflectionCache.fs │ ├── DictCache.fs │ ├── Readme.md │ ├── FSharpTypeCache.fs │ └── FSharpValueCache.fs ├── BlackFox.PathEnvironment │ ├── Release Notes.md │ ├── BlackFox.PathEnvironment.fsproj │ ├── Readme.md │ └── PathEnvironment.fs ├── BlackFox.VsWhereCmd │ ├── Main.fs │ └── BlackFox.VsWhereCmd.fsproj ├── BlackFox.CommandLine.TestParsers │ ├── BlackFox.CommandLine.TestParsers.csproj │ ├── MonoUnix.cs │ └── DotnetCoreUnix.cs ├── BlackFox.FoxSharp.Build │ ├── Program.fs │ ├── BlackFox.FoxSharp.Build.fsproj │ ├── paket.references │ └── Tasks.fs └── Directory.Build.props ├── global.json ├── .config └── dotnet-tools.json ├── .editorconfig ├── paket.dependencies ├── LICENSE.md ├── Readme.md ├── .gitignore ├── FoxSharp.sln └── paket.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @vbfox 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf 2 | -------------------------------------------------------------------------------- /.vscode/spellright.dict: -------------------------------------------------------------------------------- 1 | printf 2 | Msvcr 3 | matthid 4 | -------------------------------------------------------------------------------- /paket.cmd: -------------------------------------------------------------------------------- 1 | @dotnet tool restore 2 | dotnet paket %* 3 | -------------------------------------------------------------------------------- /paket.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dotnet tool restore 4 | dotnet paket $@ 5 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Tests/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | Expecto 3 | Expecto.FsCheck 4 | FsCheck 5 | -------------------------------------------------------------------------------- /src/BlackFox.VsWhere/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/FoxSharp/HEAD/src/BlackFox.VsWhere/Icon.png -------------------------------------------------------------------------------- /src/BlackFox.CommandLine/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/FoxSharp/HEAD/src/BlackFox.CommandLine/Icon.png -------------------------------------------------------------------------------- /src/BlackFox.VsWhere/Icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/FoxSharp/HEAD/src/BlackFox.VsWhere/Icon.afdesign -------------------------------------------------------------------------------- /src/BlackFox.Fake.BuildTask/Icon.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/FoxSharp/HEAD/src/BlackFox.Fake.BuildTask/Icon.pdn -------------------------------------------------------------------------------- /src/BlackFox.Fake.BuildTask/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/FoxSharp/HEAD/src/BlackFox.Fake.BuildTask/Icon.png -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "5.0.201", 4 | "rollForward": "latestMajor" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine/Icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/FoxSharp/HEAD/src/BlackFox.CommandLine/Icon.afdesign -------------------------------------------------------------------------------- /src/BlackFox.JavaPropertiesFile/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/FoxSharp/HEAD/src/BlackFox.JavaPropertiesFile/Icon.png -------------------------------------------------------------------------------- /src/BlackFox.JavaPropertiesFile/Icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/FoxSharp/HEAD/src/BlackFox.JavaPropertiesFile/Icon.afdesign -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "spellright.language": [ 3 | "en" 4 | ], 5 | "spellright.documentTypes": [ 6 | "markdown" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/BlackFox.JavaPropertiesFile/Types.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.JavaPropertiesFile 2 | 3 | type Entry = 4 | | Comment of text : string 5 | | KeyValue of key : string * value : string 6 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "paket": { 6 | "version": "6.0.0-beta9", 7 | "commands": [ 8 | "paket" 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/BlackFox.CachedFSharpReflection/Release Notes.md: -------------------------------------------------------------------------------- 1 | ### New in 0.1.1 2 | 3 | * Add a class with both value and type cache that can be passed around 4 | 5 | ### New in 0.1.0 6 | 7 | * First version of the Cached F# Reflection lib 8 | -------------------------------------------------------------------------------- /src/BlackFox.PathEnvironment/Release Notes.md: -------------------------------------------------------------------------------- 1 | ### New in 0.3.0 2 | 3 | * Fix bug on windows where files without extensions were considered executable 4 | 5 | ### New in 0.2.0 6 | 7 | * Nicer API 8 | 9 | ### New in 0.1.0 10 | 11 | * First version extracted from the old FoxSharp lib 12 | -------------------------------------------------------------------------------- /src/BlackFox.JavaPropertiesFile/Readme.md: -------------------------------------------------------------------------------- 1 | # Java .properties file parser 2 | 3 | ![Java Coffee Logo](https://raw.githubusercontent.com/vbfox/FoxSharp/master/src/BlackFox.JavaPropertiesFile/Icon.png) 4 | 5 | [![Nuget Package](https://img.shields.io/nuget/v/BlackFox.JavaPropertiesFile.svg)](https://www.nuget.org/packages/BlackFox.JavaPropertiesFile) 6 | -------------------------------------------------------------------------------- /src/BlackFox.VsWhereCmd/Main.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.VsWhereCmd 2 | 3 | open BlackFox.VsWhere 4 | 5 | [] 6 | let main _args = 7 | for i in VsInstances.getAllWithLegacy() do 8 | printfn "%s - %s" i.DisplayName i.InstallationVersion 9 | printfn " %s" i.InstallationPath 10 | printfn "" 11 | 12 | 0 13 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.CommandLine.Tests.Main 2 | 3 | open Expecto 4 | 5 | [] 6 | let main args = 7 | let writeResults = TestResults.writeNUnitSummary "TestResults.xml" 8 | let config = defaultConfig.appendSummaryHandler writeResults 9 | //let config = defaultConfig 10 | runTestsInAssembly config args 11 | -------------------------------------------------------------------------------- /src/BlackFox.JavaPropertiesFile/Release Notes.md: -------------------------------------------------------------------------------- 1 | ### New in 0.3.0 2 | 3 | * Back to F# Lists 4 | 5 | ### New in 0.2.0 6 | 7 | * Cleanup the API and make it a little more C# friendly 8 | * Unit tests 9 | 10 | ### New in 0.1.1 11 | 12 | * Build for net45 13 | * Lower FSharp.Core version requirement 14 | 15 | ### New in 0.1.0 16 | 17 | * First version extracted from FAKE source code 18 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine.TestParsers/BlackFox.CommandLine.TestParsers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | false 7 | false 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Build/Program.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.MasterOfFoo.Build.Program 2 | 3 | open BlackFox.Fake 4 | open Fake.Core 5 | open Fake.BuildServer 6 | 7 | [] 8 | let main argv = 9 | BuildTask.setupContextFromArgv argv 10 | BuildServer.install [ GitHubActions.Installer ] 11 | 12 | let defaultTask = Tasks.createAndGetDefault () 13 | BuildTask.runOrDefaultApp defaultTask 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Default 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | insert_final_newline = true 8 | 9 | # Xml project files 10 | [*.{fsproj,csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 11 | indent_size = 2 12 | 13 | # Xml config files 14 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 15 | indent_size = 2 16 | 17 | [*.sh] 18 | end_of_line = lf 19 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Build/BlackFox.FoxSharp.Build.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | false 6 | net5.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/BlackFox.CachedFSharpReflection/BlackFox.CachedFSharpReflection.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Build/paket.references: -------------------------------------------------------------------------------- 1 | group build 2 | FSharp.Core 3 | System.ValueTuple 4 | BlackFox.Fake.BuildTask 5 | 6 | Fake.Core.Environment 7 | Fake.Core.Process 8 | Fake.Core.Trace 9 | Fake.Core.Target 10 | Fake.Core.ReleaseNotes 11 | Fake.Core.UserInput 12 | Fake.IO.FileSystem 13 | Fake.IO.Zip 14 | Fake.Tools.Git 15 | Fake.DotNet.Cli 16 | Fake.DotNet.AssemblyInfoFile 17 | Fake.DotNet.Testing.Expecto 18 | Fake.DotNet.Paket 19 | Fake.BuildServer.GitHubActions 20 | Fake.Api.GitHub 21 | 22 | -------------------------------------------------------------------------------- /src/BlackFox.CachedFSharpReflection/FSharpReflectionCache.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.CachedFSharpReflection 2 | 3 | type FSharpReflectionCache(valueCache: FSharpValueCache, typeCache: FSharpTypeCache) = 4 | new() = FSharpReflectionCache(FSharpValueCache(), FSharpTypeCache()) 5 | 6 | static member private lazyShared = lazy (FSharpReflectionCache(FSharpValueCache.Shared, FSharpTypeCache.Shared)) 7 | static member Shared with get() = FSharpReflectionCache.lazyShared.Value 8 | 9 | member __.FSharpValue with get() = valueCache 10 | member __.FSharpType with get () = typeCache 11 | -------------------------------------------------------------------------------- /src/BlackFox.VsWhereCmd/BlackFox.VsWhereCmd.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | false 6 | Exe 7 | VsWhere 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/BlackFox.CachedFSharpReflection/DictCache.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.CachedFSharpReflection 2 | 3 | open System.Collections.Concurrent 4 | 5 | type private DictCache<'key, 'dictKey, 'value> = { 6 | Dict : ConcurrentDictionary<'dictKey, 'value> 7 | KeyConverter: 'key -> 'dictKey 8 | Getter: 'key -> 'value 9 | } 10 | 11 | module private DictCache = 12 | let create keyConverter getter = 13 | { 14 | Dict = ConcurrentDictionary<_,_>() 15 | KeyConverter = keyConverter 16 | Getter = getter 17 | } 18 | 19 | let get key cache = 20 | let dictKey = cache.KeyConverter key 21 | cache.Dict.GetOrAdd(dictKey, fun _ -> cache.Getter key) 22 | 23 | let clear cache = 24 | cache.Dict.Clear() 25 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Tests/CmdLineTests.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.FoxSharp.Tests.CmdLineTests 2 | 3 | open BlackFox.CommandLine 4 | open Expecto 5 | 6 | [] 7 | let fullFiles = 8 | testList "CmdLine" [ 9 | testCase "empty concat" <| fun () -> 10 | let r = CmdLine.concat [] |> CmdLine.toString 11 | Expect.equal r "" "toString" 12 | 13 | testCase "empty CmdLine concat" <| fun () -> 14 | let r = CmdLine.concat [CmdLine.empty] |> CmdLine.toString 15 | Expect.equal r "" "toString" 16 | 17 | testCase "Non-empty concat" <| fun () -> 18 | let r = CmdLine.concat [CmdLine.empty.Append("a").Append("b"); CmdLine.empty.Append("c").Append("d")] |> CmdLine.toString 19 | Expect.equal r "a b c d" "toString" 20 | ] 21 | -------------------------------------------------------------------------------- /src/BlackFox.CachedFSharpReflection/Readme.md: -------------------------------------------------------------------------------- 1 | # Cached F# Reflection 2 | 3 | [![Nuget Package](https://img.shields.io/nuget/v/BlackFox.CachedFSharpReflection.svg)](https://www.nuget.org/packages/BlackFox.CachedFSharpReflection) 4 | 5 | Cache the F# reflection API calls results for fast access 6 | 7 | ## Status 8 | 9 | This library is an early preview. 10 | 11 | [Change Log](Release%20Notes.md) 12 | 13 | ## Sample 14 | 15 | ```fsharp 16 | open BlackFox.CachedFSharpReflection 17 | 18 | type Foo = { 19 | Bar: int 20 | } 21 | 22 | // Use the shared cache 23 | FSharpTypeCache.Shared.IsRecord(typeof) // True 24 | FSharpTypeCache.Shared.IsUnion(typeof) // False 25 | 26 | // Create a new cache 27 | let cache = FSharpTypeCache() 28 | cache.IsRecord(typeof) // True 29 | cache.IsUnion(typeof) // False 30 | 31 | ``` 32 | -------------------------------------------------------------------------------- /src/BlackFox.PathEnvironment/BlackFox.PathEnvironment.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | net45;$(TargetFrameworks) 6 | Parse PATH environment variable and find programs on it 7 | FSharp;Path;Environment 8 | https://github.com/vbfox/FoxSharp/tree/master/src/BlackFox.PathEnvironment 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/BlackFox.Fake.BuildTask/BlackFox.Fake.BuildTask.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | net46;$(TargetFrameworks) 6 | Typed `Target` for FAKE 5 7 | build;FAKE 8 | https://github.com/vbfox/FoxSharp/tree/master/src/BlackFox.Fake.BuildTask 9 | Icon.png 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | version 5.245.4 2 | 3 | source https://api.nuget.org/v3/index.json 4 | framework: net5.0,netcoreapp2.0,netstandard2.0,>=net45 5 | storage:none 6 | 7 | nuget FSharp.Core 8 | nuget Expecto 9 | nuget Expecto.FsCheck 10 | nuget FsCheck 11 | 12 | // Build infrastructure 13 | group build 14 | source https://api.nuget.org/v3/index.json 15 | storage: none 16 | framework: net5.0 17 | 18 | nuget FSharp.Core ~> 4 19 | nuget System.ValueTuple ~> 4.5 20 | nuget BlackFox.Fake.BuildTask 21 | 22 | nuget Fake.Core.Target 23 | nuget Fake.Core.Environment 24 | nuget Fake.Core.Process 25 | nuget Fake.Core.Trace 26 | nuget Fake.Core.ReleaseNotes 27 | nuget Fake.Core.UserInput 28 | nuget Fake.IO.FileSystem 29 | nuget Fake.IO.Zip 30 | nuget Fake.Tools.Git 31 | nuget Fake.DotNet.Cli 32 | nuget Fake.DotNet.AssemblyInfoFile 33 | nuget Fake.DotNet.Testing.Expecto 34 | nuget Fake.DotNet.Paket 35 | nuget Fake.BuildServer.GitHubActions 36 | nuget Fake.Api.GitHub 37 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Tests/DotnetCoreUnixCommandLineTests.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.FoxSharp.Tests.DotnetCoreUnixCommandLineTests 2 | 3 | open Expecto 4 | open Expecto.Flip 5 | open BlackFox.CommandLine 6 | open FsCheck 7 | 8 | [] 9 | let escapeRoundtripWithDotnetCore = 10 | testProperty "Escape is the inverse of .Net Core parse method" <| 11 | fun (x: NonNull list) (alwaysQuoteArguments: bool) -> 12 | let input = x |> List.map (fun (NonNull s) -> s) 13 | let settings = 14 | { MsvcrCommandLine.defaultEscapeSettings with 15 | AlwaysQuoteArguments = alwaysQuoteArguments } 16 | let escaped = MsvcrCommandLine.escape settings input 17 | let backAgain = TestParsers.DotnetCoreUnix.Parse escaped |> List.ofArray 18 | Expect.equal "Input and escaped/parsed should equal" input backAgain 19 | 20 | [] 21 | let test = 22 | testList ".NET Core command line" [ 23 | testList "Property Based" [escapeRoundtripWithDotnetCore] 24 | ] 25 | -------------------------------------------------------------------------------- /src/BlackFox.PathEnvironment/Readme.md: -------------------------------------------------------------------------------- 1 | # Path environment 2 | 3 | [![Nuget Package](https://img.shields.io/nuget/v/BlackFox.PathEnvironment.svg)](https://www.nuget.org/packages/BlackFox.PathEnvironment) 4 | 5 | Parse and expose both PATH and PATHEXT environment variables and allow to find an executable with the same rules as the 6 | shell. 7 | 8 | ## API 9 | 10 | ```fsharp 11 | type BlackFox.PathEnvironment = 12 | // Directories in the system PATH. 13 | path: string [] 14 | 15 | // Extensions considered executables by the system. 16 | // Parsed from `PATHEXT` on windows and always return `[""]` on other systems. 17 | pathExt: string [] 18 | 19 | // Find an executable on the PATH 20 | findExecutable: name: string -> includeCurrentDirectory: bool -> string option 21 | 22 | // Find a file on the PATH 23 | findFile: name: string -> includeCurrentDirectory: bool -> string option 24 | ``` 25 | 26 | ## Example 27 | 28 | ```fsharp 29 | open BlackFox 30 | 31 | match PathEnvironment.findExecutable "node" false with 32 | | None -> failwith "nodejs wasn't found" 33 | | nodePath -> // ... 34 | ``` 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018 Julien Roncaglia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | linux: 11 | runs-on: ubuntu-latest 12 | env: 13 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 14 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 15 | DOTNET_NOLOGO: 1 16 | PAKET_SKIP_RESTORE_TARGETS: true 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Restore packages 20 | run: ./paket.sh restore 21 | - name: Compile build script 22 | run: dotnet build src/BlackFox.FoxSharp.Build/BlackFox.FoxSharp.Build.fsproj 23 | - name: Build 24 | run: ./build.sh CI 25 | windows: 26 | runs-on: windows-latest 27 | env: 28 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 29 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 30 | DOTNET_NOLOGO: 1 31 | PAKET_SKIP_RESTORE_TARGETS: true 32 | steps: 33 | - uses: actions/checkout@v2 34 | - name: Restore packages 35 | run: ./paket.cmd restore 36 | - name: Compile build script 37 | run: dotnet build src/BlackFox.FoxSharp.Build/BlackFox.FoxSharp.Build.fsproj 38 | - name: Build 39 | run: ./build.cmd CI 40 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine/BlackFox.CommandLine.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | net45;$(TargetFrameworks) 6 | Parse and escape command lines 7 | FSharp;CommandLine 8 | https://github.com/vbfox/FoxSharp/tree/master/src/BlackFox.CommandLine 9 | Icon.png 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/BlackFox.Fake.BuildTask/Release Notes.md: -------------------------------------------------------------------------------- 1 | ### New in 0.1.3 2 | 3 | * Add `runOrDefaultApp` and `runOrListApp` to use FAKE directly with `dotnet run` and still print the exception nicely 4 | colored and return a stable error code. 5 | * Add an icon (orange version of FAKE one) 6 | 7 | ### New in 0.1.2 8 | 9 | * Change `BuildTask.setupContextFromArgv` to take an array instead of a list 10 | 11 | ### New in 0.1.1 12 | 13 | * `BuildTask.setupContextFromArgv` an API not specific to BuildTask but that make using FAKE from a normal program 14 | started via `dotnet run` easy. 15 | 16 | ### New in 0.1.0 17 | 18 | * First version with this name and for FAKE 5 19 | * Previous versions were maintained in [a Gist][previous_gist] and also in this repository as 20 | [untyped][previous_untyped] and [typed][previous_typed] variants. 21 | 22 | [previous_gist]: https://gist.github.com/vbfox/e3e22d9ffff9b9de7f51 23 | [previous_untyped]: https://github.com/vbfox/FoxSharp/blob/a42b65bbd53666ab51d7e621e9a41c6f8078218c/src/BlackFox.FakeUtils/TaskDefinitionHelper.fs 24 | [previous_typed]: https://github.com/vbfox/FoxSharp/blob/a42b65bbd53666ab51d7e621e9a41c6f8078218c/src/BlackFox.FakeUtils/TypedTaskDefinitionHelper.fs 25 | -------------------------------------------------------------------------------- /src/BlackFox.JavaPropertiesFile/BlackFox.JavaPropertiesFile.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | net45;$(TargetFrameworks) 6 | An F# module to parse Java .properties files 7 | FSharp;F#;Java;properties 8 | https://github.com/vbfox/FoxSharp/tree/master/src/BlackFox.JavaPropertiesFile 9 | Icon.png 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Tests/BlackFox.FoxSharp.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0;net5.0 5 | net461;$(TargetFrameworks) 6 | Exe 7 | false 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine/Release Notes.md: -------------------------------------------------------------------------------- 1 | ### New in 1.0.0 2 | 3 | * Breaking change: Use the same signature for `CmdLine.concat` as the standard modules: `CmdLine seq -> CmdLine` 4 | 5 | ### New in 0.6.0 6 | 7 | * Added support for Mono on Unix specific encoding of `System.Process` arguments in `MonoUnixCommandLine`. 8 | * Auto-detect Mono on Unix and switch to the specific implementation 9 | * Added methods in `MonoUnixCommandLine` and `MsvcrCommandLine` to encode a single argument if needed 10 | * Added tests to confirm that the current Msvcr handling work for .Net Core on Unix 11 | 12 | ### New in 0.5.1 13 | 14 | * Msvcr: Methods now accept a settings record 15 | * Msvcr: Added setting to use double quote escaping of quotes instead of backslash 16 | * Msvcr: Added setting to always quote arguments 17 | 18 | ### New in 0.5.0 19 | 20 | * Changed a big part of the `CmdLine` API, now with a lot more variants including prefixes and printf-style versions. 21 | 22 | ### New in 0.1.1 23 | 24 | * Fixed a bug in `MsvcrCommandLine` where backslash characters not in front of a quote were incorrectly escaped. See [#1](https://github.com/vbfox/FoxSharp/issues/1), thanks @matthid. 25 | 26 | ### New in 0.1.0 27 | 28 | * First version extracted from the old FoxSharp lib 29 | -------------------------------------------------------------------------------- /src/BlackFox.VsWhere/BlackFox.VsWhere.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | net45;$(TargetFrameworks) 6 | Locate Visual Studio 2017 and newer installations 7 | https://github.com/vbfox/FoxSharp/tree/master/src/BlackFox.VsWhere 8 | FSharp;Visual Studio 9 | Icon.png 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/BlackFox.VsWhere/Release Notes.md: -------------------------------------------------------------------------------- 1 | ### New in 1.1.0 2 | 3 | * Add 2 new functions `getLegacy` and `getAllWithLegacy` that support versions before `ISetupInstance` existed 4 | (From Visual Studio .NET 2002 to Visual Studio 2015) via their registry keys 5 | (Thanks [@1354092549](https://github.com/1354092549)) 6 | 7 | ### New in 1.0.0 8 | 9 | * No changes but as FAKE is using the library the API shouldn't change now. 10 | 11 | ### New in 0.3.2 12 | 13 | * Harden against null values in COM API. Fixes [#5](https://github.com/vbfox/FoxSharp/issues/5) 14 | * Ignore instances that fail to be parsed (An error is logged to `System.Diagnostics.Trace`) 15 | 16 | ### New in 0.3.1 17 | 18 | * Fix a bug in VsSetupPackage Version (The value was the ID instead of the version) 19 | 20 | ### New in 0.3.0 21 | 22 | * Use F# lists (again) 23 | 24 | ### New in 0.2.4 25 | 26 | * Package icon 27 | 28 | ### New in 0.2.3 29 | 30 | * Add getCompleted 31 | 32 | ### New in 0.2.2 33 | 34 | * Return an empty array on non-windows 35 | 36 | ### New in 0.2.1 37 | 38 | * Add getWithPackage 39 | 40 | ### New in 0.2 41 | 42 | * Return an array 43 | 44 | ### New in 0.1.1 45 | 46 | * Build for net45 47 | * Lower FSharp.Core version requirement 48 | 49 | ### New in 0.1.0 50 | 51 | * First version with a simple API to enumerate all versions 52 | -------------------------------------------------------------------------------- /src/BlackFox.VsWhere/Types.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.VsWhere 2 | 3 | open System 4 | 5 | type VsSetupPackage = 6 | { Id: string 7 | Version: string 8 | Chip: string 9 | Language: string 10 | Branch: string 11 | Type: string 12 | UniqueId: string 13 | IsExtension: bool } 14 | 15 | type VsSetupErrorInfo = 16 | { HResult: int 17 | ErrorClassName: string 18 | ErrorMessage: string } 19 | 20 | type VsSetupErrorState = 21 | { FailedPackages: VsSetupPackage list 22 | SkippedPackages: VsSetupPackage list 23 | ErrorLogFilePath: string option 24 | LogFilePath: string option 25 | RuntimeError: VsSetupErrorInfo option } 26 | 27 | type VsSetupInstance = 28 | { 29 | InstanceId: string 30 | InstallDate: DateTimeOffset 31 | InstallationName: string 32 | InstallationPath: string 33 | InstallationVersion: string 34 | DisplayName: string 35 | Description: string 36 | State: InstanceState option 37 | Packages: VsSetupPackage list 38 | Product: VsSetupPackage option 39 | ProductPath: string option 40 | Errors: VsSetupErrorState option 41 | IsLaunchable: bool option 42 | IsComplete: bool option 43 | Properties: Map 44 | EnginePath: string option 45 | IsPrerelease: bool option 46 | CatalogInfo: Map 47 | } 48 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)/../artifacts/')) 4 | 5 | 6 | $([System.IO.Path]::GetFullPath('$(ArtifactsDir)/$(MSBuildProjectName)/$(Configuration)/')) 7 | 8 | 9 | https://github.com/vbfox/FoxSharp 10 | MIT 11 | https://github.com/vbfox/FoxSharp.git 12 | Julien Roncaglia 13 | Julien Roncaglia 14 | 15 | 16 | true 17 | FS2003 18 | true 19 | true 20 | true 21 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 🦊 Sharp 2 | ======== 3 | 4 | [![Github Actions Status](https://github.com/vbfox/FoxSharp/workflows/CI/badge.svg?branch=master)](https://github.com/vbfox/FoxSharp/actions?query=workflow%3ACI) 5 | 6 | A collection of .Net NuGet packages that I maintain, developed in F#. 7 | 8 | |Badge|Name|Description| 9 | |-----|----|-----------| 10 | |[![Nuget Package](https://img.shields.io/nuget/v/BlackFox.Fake.BuildTask.svg)](https://www.nuget.org/packages/BlackFox.Fake.BuildTask)|[BlackFox.Fake.BuildTask](src/BlackFox.Fake.BuildTask/Readme.md)|Strongly typed Target alternative for FAKE| 11 | |[![Nuget Package](https://img.shields.io/nuget/v/BlackFox.VsWhere.svg)](https://www.nuget.org/packages/BlackFox.VsWhere)|[BlackFox.VsWhere](src/BlackFox.VsWhere/Readme.md)|Visual Studio 2017+ Locator| 12 | |[![Nuget Package](https://img.shields.io/nuget/v/BlackFox.JavaPropertiesFile.svg)](https://www.nuget.org/packages/BlackFox.JavaPropertiesFile)|[BlackFox.JavaPropertiesFile](src/BlackFox.JavaPropertiesFile/Readme.md)|Java .properties file parsing| 13 | |[![Nuget Package](https://img.shields.io/nuget/v/BlackFox.CommandLine.svg)](https://www.nuget.org/packages/BlackFox.CommandLine)|[BlackFox.CommandLine](src/BlackFox.CommandLine/Readme.md)|Command line parsing from string to array and back again| 14 | |[![Nuget Package](https://img.shields.io/nuget/v/BlackFox.PathEnvironment.svg)](https://www.nuget.org/packages/BlackFox.PathEnvironment)|[BlackFox.PathEnvironment](src/BlackFox.PathEnvironment/Readme.md)|Parse and find things on the PATH environment variable| 15 | |[![Nuget Package](https://img.shields.io/nuget/v/BlackFox.CachedFSharpReflection.svg)](https://www.nuget.org/packages/BlackFox.CachedFSharpReflection)|[BlackFox.CachedFSharpReflection](src/BlackFox.CachedFSharpReflection/Readme.md)|Cache the F# reflection API calls results for fast access| 16 | -------------------------------------------------------------------------------- /src/BlackFox.JavaPropertiesFile/JavaPropertiesFile.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.JavaPropertiesFile.JavaPropertiesFile 2 | 3 | open System.IO 4 | 5 | /// 6 | /// Parse a Java '.properties' file from a returning a list of comments and values. 7 | /// 8 | [] 9 | let parseTextReader (textReader: TextReader) = 10 | let reader = Parser.textReaderToReader textReader 11 | Parser.parseWithReader reader 12 | 13 | /// 14 | /// Parse a Java '.properties' file from a returning a list of comments and values. 15 | /// 16 | [] 17 | let parseString (s: string) = 18 | use reader = new StringReader(s) 19 | 20 | parseTextReader reader 21 | 22 | /// 23 | /// Parse a Java '.properties' file from a file by specifying it's path returning a list of comments and values. 24 | /// 25 | [] 26 | let parseFile (path: string) = 27 | use stream = File.OpenRead(path) 28 | use reader = new StreamReader(stream) 29 | 30 | parseTextReader reader 31 | 32 | /// 33 | /// Convert a list of comments and values extracted from a Java '.properties' file to a map with only the values. 34 | /// 35 | [] 36 | let toMap (properties: Entry []) = 37 | properties 38 | |> Seq.choose (function | Comment _ -> None | KeyValue (k, v) -> Some (k, v)) 39 | |> Map.ofSeq 40 | 41 | /// 42 | /// Convert a list of comments and values extracted from a Java '.properties' file to a dictionary with only the values. 43 | /// 44 | [] 45 | let toDictionary (properties: Entry []) = 46 | properties 47 | |> Seq.choose (function | Comment _ -> None | KeyValue (k, v) -> Some (k, v)) 48 | |> dict 49 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Tests/MonoUnixCommandLineTests.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.FoxSharp.Tests.MonoUnixCommandLineTests 2 | 3 | open Expecto 4 | open Expecto.Flip 5 | open BlackFox.CommandLine 6 | open FsCheck 7 | 8 | type NotZeroChar = | NotZeroChar of string 9 | 10 | type NotZeroCharGenerator = 11 | static member Generator() = 12 | Arb.from 13 | |> Arb.filter (fun (s: string) -> s <> null && not (s.ToCharArray() |> Array.contains '\x00')) 14 | |> Arb.convert NotZeroChar (fun (NotZeroChar s) -> s) 15 | 16 | let config = { FsCheckConfig.defaultConfig with arbitrary = [typeof] } 17 | 18 | let escapeRoundtripWithMono = 19 | testPropertyWithConfig config "Escape is the inverse of Mono parse method" <| 20 | fun (x: NotZeroChar list) (alwaysQuoteArguments: bool) -> 21 | let input = x |> List.map (fun (NotZeroChar s) -> s) 22 | let settings = 23 | { MonoUnixCommandLine.defaultEscapeSettings with 24 | AlwaysQuoteArguments = alwaysQuoteArguments } 25 | let escaped = MonoUnixCommandLine.escape settings input 26 | try 27 | let backAgain = TestParsers.MonoUnix.Parse escaped |> List.ofArray 28 | Expect.equal "Input and escaped/parsed should equal" input backAgain 29 | with 30 | | _ -> 31 | printfn "======" 32 | escaped |> Seq.map (fun c -> (int c).ToString("X2")) |> String.concat " " |> printfn "INPUTBYTES=%s" 33 | printfn "INPUT=%A" input 34 | printfn "ESCAPED==%s==" escaped 35 | reraise () 36 | 37 | let escapeRoundtripWithParse = 38 | testProperty "Escape is the inverse of parse method" <| 39 | fun (x: NonNull list) (alwaysQuoteArguments: bool) -> 40 | let input = x |> List.map (fun (NonNull s) -> s) 41 | let settings = 42 | { MonoUnixCommandLine.defaultEscapeSettings with 43 | AlwaysQuoteArguments = alwaysQuoteArguments } 44 | let escaped = MonoUnixCommandLine.escape settings input 45 | let backAgain = MonoUnixCommandLine.parse escaped 46 | Expect.equal "Input and escaped/parsed should equal" input backAgain 47 | 48 | [] 49 | let test = 50 | testList "Mono unix command line" [ 51 | testList "Property Based" [ 52 | escapeRoundtripWithMono 53 | escapeRoundtripWithParse 54 | ] 55 | ] 56 | -------------------------------------------------------------------------------- /src/BlackFox.VsWhere/Readme.md: -------------------------------------------------------------------------------- 1 | # Visual Studio 2017+ Locator 2 | 3 | ![Logo '?VS'](https://raw.githubusercontent.com/vbfox/FoxSharp/master/src/BlackFox.VsWhere/Icon.png) 4 | 5 | [![Nuget Package](https://img.shields.io/nuget/v/BlackFox.VsWhere.svg)](https://www.nuget.org/packages/BlackFox.VsWhere) 6 | 7 | A NuGet package to detect Visual Studio 2017+ installs from F# code similar to the 8 | [vswhere](https://github.com/Microsoft/vswhere) binary. 9 | 10 | *Note: The API is also usable in other .NET languages but exposes some F# specific types* 11 | 12 | ## API 13 | 14 | ```fsharp 15 | module BlackFox.VsWhere.VsInstances = 16 | /// Get all VS2017+ instances (Visual Studio stable, preview, Build tools, ...) 17 | /// This method return instances that have installation errors and pre-releases. 18 | let getAll (): VsSetupInstance list = 19 | 20 | /// Get VS2017+ instances that are completely installed 21 | let getCompleted (includePrerelease: bool): VsSetupInstance list = 22 | 23 | /// Get VS2017+ instances that are completely installed and have a specific package ID installed 24 | let getWithPackage (packageId: string) (includePrerelease: bool): VsSetupInstance list = 25 | 26 | /// Get legacy VS instances (before VS2017: VS .NET 2002 to VS2015). 27 | /// Note that the information for legacy ones is limited. 28 | let getLegacy(): VsSetupInstance list = 29 | 30 | /// Get all Visual Studio instances including legacy VS instances (before VS2017: VS .NET 2002 to VS2015). 31 | /// Note that the information for legacy ones is limited. 32 | let getAllWithLegacy (): VsSetupInstance list = 33 | ``` 34 | 35 | Package IDs for components and workloads [are documented on docs.microsoft.com](https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=vs-2017). 36 | 37 | ## Sample usage 38 | 39 | Find MSBuild: 40 | 41 | ```fsharp 42 | let instance = 43 | VsInstances.getWithPackage "Microsoft.Component.MSBuild" false 44 | |> List.tryHead 45 | 46 | match instance with 47 | | None -> printfn "No MSBuild" 48 | | Some vs -> 49 | let msbuild = Path.Combine(vs.InstallationPath, "MSBuild\\15.0\\Bin\\MSBuild.exe") 50 | printfn "MSBuild: %s" msbuild 51 | ``` 52 | 53 | Start Developer Command Prompt: 54 | 55 | ```fsharp 56 | match VsInstances.getCompleted false |> List.tryHead with 57 | | None -> printfn "No VS" 58 | | Some vs -> 59 | let vsdevcmd = Path.Combine(vs.InstallationPath, "Common7\\Tools\\vsdevcmd.bat") 60 | let comspec = Environment.GetEnvironmentVariable("COMSPEC") 61 | Process.Start(comspec, sprintf "/k \"%s\"" vsdevcmd) |> ignore 62 | ``` 63 | -------------------------------------------------------------------------------- /src/BlackFox.PathEnvironment/PathEnvironment.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox 2 | 3 | open System 4 | open System.IO 5 | 6 | module private PathEnvironmentUtils = 7 | let private noExtensionsExecutable = 8 | match Environment.OSVersion.Platform with 9 | | PlatformID.Win32NT 10 | | PlatformID.Win32S 11 | | PlatformID.Win32Windows 12 | | PlatformID.WinCE 13 | | PlatformID.Xbox -> false 14 | | _ -> true 15 | 16 | let findFileInDirs dirs names = 17 | dirs 18 | |> Seq.collect (fun dir -> names |> List.map (fun name -> Path.Combine(dir, name))) 19 | |> Seq.tryFind(File.Exists) 20 | 21 | let findProgramInDirs dirs programExts name = 22 | let namesWithExt = programExts |> List.map ((+) name) 23 | let names = if noExtensionsExecutable then name :: namesWithExt else namesWithExt 24 | findFileInDirs dirs names 25 | 26 | let envVarOrEmpty name = 27 | let value = Environment.GetEnvironmentVariable(name) 28 | if isNull name then "" else value 29 | 30 | let getPath () = 31 | (envVarOrEmpty "PATH").Split(Path.PathSeparator) 32 | 33 | let getPathExt () = 34 | if Environment.OSVersion.Platform = PlatformID.Win32NT then 35 | (envVarOrEmpty "PATHEXT").Split(Path.PathSeparator) 36 | else 37 | [|""|] 38 | 39 | let addCwd (includeCurrentDirectory: bool) (arr: string []) = 40 | if includeCurrentDirectory then 41 | Array.concat [ [|Environment.CurrentDirectory |]; arr ] 42 | else 43 | arr 44 | 45 | open PathEnvironmentUtils 46 | 47 | type PathEnvironment = 48 | /// Directories in the system PATH 49 | [] 50 | static member path 51 | with get() = getPath() 52 | 53 | /// Extensions considered executables by the system. 54 | /// Parsed from `PATHEXT` on windows and always return `[|""|]` on other systems 55 | [] 56 | static member pathExt 57 | with get() = getPathExt() 58 | 59 | /// Find an executable on the PATH 60 | [] 61 | static member findExecutable (name: string) (includeCurrentDirectory: bool) = 62 | let dirs = addCwd includeCurrentDirectory PathEnvironment.path 63 | findProgramInDirs dirs (PathEnvironment.pathExt |> List.ofArray) name 64 | 65 | /// Find a file on the PATH 66 | [] 67 | static member findFile (name: string) (includeCurrentDirectory: bool) = 68 | let dirs = addCwd includeCurrentDirectory PathEnvironment.path 69 | findFileInDirs dirs [name] 70 | -------------------------------------------------------------------------------- /src/BlackFox.Fake.BuildTask/Readme.md: -------------------------------------------------------------------------------- 1 | # FAKE BuildTask 2 | 3 | ![Orange FAKE Logo](https://raw.githubusercontent.com/vbfox/FoxSharp/master/src/BlackFox.Fake.BuildTask/Icon.png) 4 | 5 | A strongly typed `Target` alternative for [FAKE](https://fake.build/) 6 | 7 | [![Nuget Package](https://img.shields.io/nuget/v/BlackFox.Fake.BuildTask.svg)](https://www.nuget.org/packages/BlackFox.Fake.BuildTask) 8 | 9 | ## Examples 10 | 11 | ```fsharp 12 | open BlackFox.Fake 13 | 14 | // A task with no dependencies 15 | let paketRestore = BuildTask.create "PaketRestore" [] { 16 | // ... 17 | () 18 | } 19 | 20 | // A task that need the restore to be done and should run after Clean 21 | // if it is in the build chain 22 | let build = BuildTask.create "Build" [clean.IfNeeded; paketRestore] { 23 | // ... 24 | () 25 | } 26 | 27 | // A task without any action, only dependencies here specifying what should 28 | // run in CI 29 | let _ci = BuildTask.createEmpty "CI" [clean; build] 30 | 31 | BuildTask.runOrDefault build 32 | ``` 33 | 34 | You can find a more explanatory blog [here](https://blog.vbfox.net/2018/09/12/fake-typed-targets.html) 35 | 36 | ## API 37 | 38 | ```fsharp 39 | module BlackFox.Fake.BuildTask 40 | /// What FAKE name a target 41 | type TaskMetadata = { 42 | Name: string 43 | Dependencies: TaskInfo list 44 | } 45 | 46 | /// Dependency (Soft or Hard) to a target (That can be a null object) 47 | type TaskInfo = { 48 | Metadata: TaskMetadata option 49 | IsSoft: bool 50 | } 51 | with 52 | static member NoTask 53 | member this.Always with get() 54 | member this.IfNeeded with get() 55 | member this.If(condition: bool) 56 | 57 | /// Define a Task with it's dependencies 58 | let createFn (name: string) (dependencies: TaskInfo list) (body: TargetParameter -> unit): TaskInfo 59 | 60 | /// Define a Task with it's dependencies 61 | let create (name: string) (dependencies: TaskInfo list): TaskBuilder 62 | 63 | /// Define a Task without any body, only dependencies 64 | let createEmpty (name: string) (dependencies: TaskInfo list): TaskInfo 65 | 66 | /// Run the task specified on the command line if there was one or the 67 | /// default one otherwise. 68 | let runOrDefault (defaultTask: TaskInfo): unit 69 | 70 | /// Run the task specified on the command line if there was one or the 71 | /// default one otherwise. 72 | let runOrDefaultWithArguments (defaultTask: TaskInfo): unit 73 | 74 | /// Runs the task given by the target parameter or lists the available targets 75 | let runOrList (): unit 76 | 77 | /// List all tasks available. 78 | let listAvailable (): unit 79 | 80 | /// Writes a dependency graph. 81 | let printDependencyGraph (verbose: bool) (taskInfo: TaskInfo): unit 82 | 83 | /// Setup the FAKE context from a program argument 84 | /// 85 | /// Arguments are the same as the ones comming after "run" when running via FAKE. 86 | /// The only difference is that "--target" is apended if the first argument doesn't start with "-". 87 | /// 88 | /// Examples: 89 | /// 90 | /// * `foo` -> `run --target foo` 91 | /// * `--target bar --baz` -> `run --target bar --baz` 92 | let setupContextFromArgv (argv: string []): unit 93 | 94 | /// Run the task specified on the command line if there was one or the 95 | /// default one otherwise. Return 0 on success and 1 on error, printing 96 | /// the exception on the console. 97 | let runOrDefaultApp (defaultTask: TaskInfo): int 98 | 99 | /// Runs the task given by the target parameter or lists the available targets. 100 | /// Return 0 on success and 1 on error, printing the exception on the console. 101 | let runOrListApp (): int 102 | ``` 103 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine.TestParsers/MonoUnix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using gboolean = System.Boolean; 5 | using gchar = System.Char; 6 | using GString = System.String; 7 | 8 | namespace BlackFox.CommandLine.TestParsers 9 | { 10 | /// 11 | /// Mono command line parsing ported to C# 12 | /// 13 | public static class MonoUnix 14 | { 15 | // From https://github.com/mono/mono/blob/d4952011ea889848ea8b9285b42582d238ccecf8/mono/eglib/gshell.c#L33 16 | 17 | public static unsafe string[] Parse(string cmdLine) 18 | { 19 | var list = new List(); 20 | fixed (char* c = cmdLine) 21 | { 22 | split_cmdline(c, list); 23 | } 24 | return list.ToArray(); 25 | } 26 | 27 | private static GString g_string_new(string s) => s; 28 | private static void g_string_append_c(ref string s, char c) => s = s + c; 29 | private static bool g_ascii_isspace(char c) => c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; 30 | private static string g_string_free(string s, bool ignored) => s; 31 | private static void g_ptr_array_add(List lst, string s) => lst.Add(s); 32 | private static bool FALSE = false; 33 | private static bool TRUE = true; 34 | 35 | private static unsafe void split_cmdline(gchar* cmdline, List array) 36 | { 37 | gchar *ptr; 38 | gchar c; 39 | gboolean escaped = FALSE, fresh = TRUE; 40 | gchar quote_char = '\0'; 41 | GString str; 42 | 43 | str = g_string_new(""); 44 | ptr = (gchar*)cmdline; 45 | while ((c = *ptr++) != '\0') 46 | { 47 | if (escaped) 48 | { 49 | /* 50 | * \CHAR is only special inside a double quote if CHAR is 51 | * one of: $`"\ and newline 52 | */ 53 | if (quote_char == '\"') 54 | { 55 | if (!(c == '$' || c == '`' || c == '"' || c == '\\')) 56 | g_string_append_c(ref str, '\\'); 57 | g_string_append_c(ref str, c); 58 | } 59 | else 60 | { 61 | if (!g_ascii_isspace(c)) 62 | g_string_append_c(ref str, c); 63 | } 64 | escaped = FALSE; 65 | } 66 | else if (quote_char != '\0') 67 | { 68 | if (c == quote_char) 69 | { 70 | quote_char = '\0'; 71 | if (fresh && (g_ascii_isspace(*ptr) || *ptr == '\0')) 72 | { 73 | g_ptr_array_add(array, g_string_free(str, FALSE)); 74 | str = g_string_new(""); 75 | } 76 | } 77 | else if (c == '\\') 78 | { 79 | escaped = TRUE; 80 | } 81 | else 82 | g_string_append_c(ref str, c); 83 | } 84 | else if (g_ascii_isspace(c)) 85 | { 86 | if (str.Length > 0) 87 | { 88 | g_ptr_array_add(array, g_string_free(str, FALSE)); 89 | str = g_string_new(""); 90 | } 91 | } 92 | else if (c == '\\') 93 | { 94 | escaped = TRUE; 95 | } 96 | else if (c == '\'' || c == '"') 97 | { 98 | fresh = str.Length == 0; 99 | quote_char = c; 100 | } 101 | else 102 | { 103 | g_string_append_c(ref str, c); 104 | } 105 | } 106 | 107 | if (escaped) 108 | { 109 | throw new InvalidOperationException("Unfinished escape."); 110 | } 111 | 112 | if (quote_char != '\0') 113 | { 114 | throw new InvalidOperationException("Unfinished quote."); 115 | } 116 | 117 | if (str.Length > 0) 118 | { 119 | g_ptr_array_add(array, g_string_free(str, FALSE)); 120 | } 121 | else 122 | { 123 | g_string_free(str, TRUE); 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine/MonoUnixCommandLine.fs: -------------------------------------------------------------------------------- 1 | /// Parse and escape arguments as Mono does it on Unix platforms in the System.Process type 2 | module BlackFox.CommandLine.MonoUnixCommandLine 3 | 4 | open System.Text 5 | 6 | /// Settings for the escape method 7 | type EscapeSettings = { 8 | /// Specify that arguments should always be quoted, even simple values 9 | AlwaysQuoteArguments: bool 10 | } 11 | 12 | /// Default escape settings 13 | let defaultEscapeSettings = { 14 | AlwaysQuoteArguments = false } 15 | 16 | let private escapeArgCore (arg : string) (builder : StringBuilder) = 17 | for c in arg do 18 | if c = ''' || c = '\\' then 19 | builder.Append('\\') |> ignore 20 | builder.Append(c) |> ignore 21 | 22 | let escapeArgumentToBuilder (settings: EscapeSettings) (arg : string) (builder : StringBuilder): unit = 23 | builder.EnsureCapacity(arg.Length + builder.Length) |> ignore 24 | 25 | if arg.Length = 0 then 26 | // Empty arg 27 | builder.Append(@"''") |> ignore 28 | else 29 | let mutable needQuote = settings.AlwaysQuoteArguments 30 | 31 | if not settings.AlwaysQuoteArguments then 32 | for c in arg do 33 | needQuote <- needQuote || (c = ' ') 34 | needQuote <- needQuote || (c = '\t') 35 | needQuote <- needQuote || (c = '\n') 36 | needQuote <- needQuote || (c = '\v') 37 | needQuote <- needQuote || (c = '\f') 38 | needQuote <- needQuote || (c = '\r') 39 | needQuote <- needQuote || (c = '"') 40 | needQuote <- needQuote || (c = ''') 41 | needQuote <- needQuote || (c = '\\') 42 | 43 | if not needQuote then 44 | // No special characters are present, early exit 45 | builder.Append(arg) |> ignore 46 | else 47 | // Complex case, we really need to escape 48 | builder.Append(''') |> ignore 49 | escapeArgCore arg builder 50 | builder.Append(''') |> ignore 51 | 52 | let escapeArgument (settings: EscapeSettings) (arg : string): string = 53 | let builder = StringBuilder() 54 | escapeArgumentToBuilder settings arg builder 55 | builder.ToString() 56 | 57 | let escapeToBuilder (settings: EscapeSettings) (cmdLine: seq) (builder: StringBuilder): unit = 58 | cmdLine |> Seq.iteri (fun i arg -> 59 | if (i <> 0) then builder.Append(' ') |> ignore 60 | escapeArgumentToBuilder settings arg builder) 61 | 62 | let escape (settings: EscapeSettings) (cmdLine: seq): string = 63 | let builder = StringBuilder() 64 | escapeToBuilder settings cmdLine builder 65 | builder.ToString() 66 | 67 | let inline private isAsciiSpace (c: char) = 68 | c = ' ' || c = '\t' || c = '\n' || c = '\v' || c = '\f' || c = '\r' 69 | 70 | let parse (args: string) = 71 | let mutable escaped = false 72 | let mutable fresh = true 73 | let mutable quoteChar = '\x00' 74 | let str = StringBuilder() 75 | let mutable result = List.empty 76 | for i in [0 .. (args.Length-1)] do 77 | let c = args.[i] 78 | if escaped then 79 | if quoteChar = '"' then 80 | if not (c = '$' || c = '`' || c = '"' || c = '\\') then 81 | str.Append('\\') |> ignore 82 | str.Append(c) |> ignore 83 | else 84 | if not (isAsciiSpace c) then 85 | str.Append(c) |> ignore 86 | escaped <- false 87 | else if quoteChar <> '\x00' then 88 | if c = quoteChar then 89 | quoteChar <- '\x00' 90 | let nextIsSpace = (i <> args.Length - 1) && (isAsciiSpace args.[i + 1]) 91 | if fresh && (nextIsSpace || i = args.Length - 1) then 92 | result <- str.ToString() :: result 93 | str.Clear() |> ignore 94 | else if c = '\\' then 95 | escaped <- true 96 | else 97 | str.Append(c) |> ignore 98 | else if isAsciiSpace c then 99 | if str.Length > 0 then 100 | result <- str.ToString() :: result 101 | str.Clear() |> ignore 102 | else if c = '\\' then 103 | escaped <- true 104 | else if (c = '\'' || c = '"') then 105 | fresh <- str.Length = 0 106 | quoteChar <- c 107 | else 108 | str.Append(c) |> ignore 109 | 110 | if escaped then 111 | failwith "Unfinished escape." 112 | else if quoteChar <> '\x00' then 113 | failwith "Unfinished quote." 114 | else 115 | if str.Length <> 0 then 116 | result <- str.ToString() :: result 117 | 118 | result |> List.rev 119 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine.TestParsers/DotnetCoreUnix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BlackFox.CommandLine.TestParsers 6 | { 7 | /// 8 | /// .Net Core Unix argument parsing 9 | /// 10 | public static class DotnetCoreUnix 11 | { 12 | // From https://github.com/dotnet/corefx/blob/0bfc4f3cf62f6fc29162ad5b1b89acc14124a375/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs 13 | public static string[] Parse(string cmdLine) 14 | { 15 | var list = new List(); 16 | ParseArgumentsIntoList(cmdLine, list); 17 | return list.ToArray(); 18 | } 19 | 20 | /// Parses a command-line argument string into a list of arguments. 21 | /// The argument string. 22 | /// The list into which the component arguments should be stored. 23 | /// 24 | /// This follows the rules outlined in "Parsing C++ Command-Line Arguments" at 25 | /// https://msdn.microsoft.com/en-us/library/17w5ykft.aspx. 26 | /// 27 | private static void ParseArgumentsIntoList(string arguments, List results) 28 | { 29 | // Iterate through all of the characters in the argument string. 30 | for (int i = 0; i < arguments.Length; i++) 31 | { 32 | while (i < arguments.Length && (arguments[i] == ' ' || arguments[i] == '\t')) 33 | i++; 34 | 35 | if (i == arguments.Length) 36 | break; 37 | 38 | results.Add(GetNextArgument(arguments, ref i)); 39 | } 40 | } 41 | 42 | private static string GetNextArgument(string arguments, ref int i) 43 | { 44 | var currentArgument = new StringBuilder(); 45 | bool inQuotes = false; 46 | 47 | while (i < arguments.Length) 48 | { 49 | // From the current position, iterate through contiguous backslashes. 50 | int backslashCount = 0; 51 | while (i < arguments.Length && arguments[i] == '\\') 52 | { 53 | i++; 54 | backslashCount++; 55 | } 56 | 57 | if (backslashCount > 0) 58 | { 59 | if (i >= arguments.Length || arguments[i] != '"') 60 | { 61 | // Backslashes not followed by a double quote: 62 | // they should all be treated as literal backslashes. 63 | currentArgument.Append('\\', backslashCount); 64 | } 65 | else 66 | { 67 | // Backslashes followed by a double quote: 68 | // - Output a literal slash for each complete pair of slashes 69 | // - If one remains, use it to make the subsequent quote a literal. 70 | currentArgument.Append('\\', backslashCount / 2); 71 | if (backslashCount % 2 != 0) 72 | { 73 | currentArgument.Append('"'); 74 | i++; 75 | } 76 | } 77 | 78 | continue; 79 | } 80 | 81 | char c = arguments[i]; 82 | 83 | // If this is a double quote, track whether we're inside of quotes or not. 84 | // Anything within quotes will be treated as a single argument, even if 85 | // it contains spaces. 86 | if (c == '"') 87 | { 88 | if (inQuotes && i < arguments.Length - 1 && arguments[i + 1] == '"') 89 | { 90 | // Two consecutive double quotes inside an inQuotes region should result in a literal double quote 91 | // (the parser is left in the inQuotes region). 92 | // This behavior is not part of the spec of code:ParseArgumentsIntoList, but is compatible with CRT 93 | // and .NET Framework. 94 | currentArgument.Append('"'); 95 | i++; 96 | } 97 | else 98 | { 99 | inQuotes = !inQuotes; 100 | } 101 | 102 | i++; 103 | continue; 104 | } 105 | 106 | // If this is a space/tab and we're not in quotes, we're done with the current 107 | // argument, it should be added to the results and then reset for the next one. 108 | if ((c == ' ' || c == '\t') && !inQuotes) 109 | { 110 | break; 111 | } 112 | 113 | // Nothing special; add the character to the current argument. 114 | currentArgument.Append(c); 115 | i++; 116 | } 117 | 118 | return currentArgument.ToString(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Tests/MsvcrCommandLineTests.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.FoxSharp.Tests.MsvcrCommandLineEscapeTests 2 | 3 | open Expecto 4 | open Expecto.Flip 5 | open BlackFox.CommandLine 6 | 7 | let verifyEscape expected argv = 8 | let result = MsvcrCommandLine.escape MsvcrCommandLine.defaultEscapeSettings argv 9 | Expect.equal (sprintf "%A" argv) expected result 10 | 11 | let escapeTests = [ 12 | test "Multiple_parameters_are_separated_by_spaces" { 13 | verifyEscape "Hello World" ["Hello"; "World"] 14 | } 15 | 16 | test "No_quotes_are_added_when_not_needed" { 17 | verifyEscape "Hello_World" ["Hello_World"] 18 | } 19 | 20 | test "Quotes_are_added_when_arg_contains_space" { 21 | verifyEscape @"""Hello World""" ["Hello World"] 22 | } 23 | 24 | test "Quote_is_escaped_inside" { 25 | verifyEscape @"Hello\""World" [@"Hello""World"] 26 | } 27 | 28 | test "Quote_is_escaped_at_start" { 29 | verifyEscape @"\""HelloWorld" [@"""HelloWorld"] 30 | } 31 | 32 | test "Quote_is_escaped_at_end" { 33 | verifyEscape @"HelloWorld\""" [@"HelloWorld"""] 34 | } 35 | 36 | test "Backslash_alone_not_escaped" { 37 | verifyEscape @"Hello\World" [@"Hello\World"] 38 | } 39 | 40 | test "Backslash_escaped_if_at_end_and_need_quote" { 41 | verifyEscape @"""Hello World\\""" [@"Hello World\"] 42 | } 43 | 44 | test "Backslash_not_escaped_if_at_end_and_no_need_to_need_quote" { 45 | verifyEscape @"Hello_World\" [@"Hello_World\"] 46 | } 47 | 48 | test "Backslash_before_quote_escaped" { 49 | verifyEscape @"Hello\\\""World" [@"Hello\""World"] 50 | } 51 | 52 | test "Microsoft_sample_1" { 53 | verifyEscape @"""a b c"" d e" ["a b c" ;"d" ;"e"] 54 | } 55 | 56 | test "Microsoft_sample_2" { 57 | verifyEscape @"ab\""c \ d" ["ab\"c" ;"\\" ;"d"] 58 | } 59 | 60 | test "Microsoft_sample_3_modified" { 61 | verifyEscape @"a\\\b ""de fg"" h" [@"a\\\b" ;"de fg" ;"h"] 62 | } 63 | 64 | test "Microsoft_sample_4" { 65 | verifyEscape @"a\\\""b c d" [@"a\""b" ;"c" ;"d"] 66 | } 67 | 68 | test "Microsoft_sample_5_modified" { 69 | verifyEscape @"""a\\b c"" d e" [@"a\\b c" ;"d" ;"e"] 70 | } 71 | 72 | test "Pass_empty_arguments" { 73 | verifyEscape @"a """" b" ["a"; ""; "b"] 74 | } 75 | ] 76 | 77 | let verifyParse args expected = 78 | let result = MsvcrCommandLine.parse args 79 | Expect.equal args expected result 80 | 81 | let parseTests = [ 82 | test "Plain_parameter" { 83 | verifyParse @"CallMeIshmael" ["CallMeIshmael"] 84 | } 85 | 86 | test "Space_in_double_quoted" { 87 | verifyParse @"""Call Me Ishmael""" ["Call Me Ishmael"] 88 | } 89 | 90 | test "Double_quoted_anywhere" { 91 | verifyParse @"Cal""l Me I""shmael" ["Call Me Ishmael"] 92 | } 93 | 94 | test "Escape_quotes" { 95 | verifyParse @"CallMe\""Ishmael " [@"CallMe""Ishmael"] 96 | } 97 | 98 | test "Escape_backslash_end" { 99 | verifyParse @"""Call Me Ishmael\\""" [@"Call Me Ishmael\"] 100 | } 101 | 102 | test "Escape_backslash_middle" { 103 | verifyParse @"""CallMe\\\""Ishmael""" [@"CallMe\""Ishmael"] 104 | } 105 | 106 | test "Backslash_literal_without_quote" { 107 | verifyParse @"a\\\b" [@"a\\\b"] 108 | } 109 | 110 | test "Backslash_literal_without_quote_in_quoted" { 111 | verifyParse @"""a\\\b""" [@"a\\\b"] 112 | } 113 | 114 | test "Microsoft_sample_1" { 115 | verifyParse @"""a b c"" d e" ["a b c" ;"d" ;"e"] 116 | } 117 | 118 | test "Microsoft_sample_2" { 119 | verifyParse @"""ab\""c"" ""\\"" d" ["ab\"c" ;"\\" ;"d"] 120 | } 121 | 122 | test "Microsoft_sample_3" { 123 | verifyParse @"a\\\b d""e f""g h" [@"a\\\b" ;"de fg" ;"h"] 124 | } 125 | 126 | test "Microsoft_sample_4" { 127 | verifyParse @"a\\\""b c d" [@"a\""b" ;"c" ;"d"] 128 | } 129 | 130 | test "Microsoft_sample_5" { 131 | verifyParse @"a\\\\""b c"" d e" [@"a\\b c" ;"d" ;"e"] 132 | } 133 | 134 | test "Double_double_quotes_sample_1" { 135 | verifyParse @"""a b c""""" [@"a b c"""] 136 | } 137 | 138 | test "Double_double_quotes_sample_2" { 139 | verifyParse @"""""""CallMeIshmael"""""" b c " [@"""CallMeIshmael""" ;"b" ;"c"] 140 | } 141 | 142 | test "Double_double_quotes_sample_4" { 143 | verifyParse @"""""""""Call Me Ishmael"""" b c " [@"""Call" ;"Me" ;@"Ishmael" ;"b" ;"c"] 144 | } 145 | 146 | test "Triple_double_quotes" { 147 | verifyParse @"""""""Call Me Ishmael""""""" [@"""Call Me Ishmael"""] 148 | } 149 | 150 | test "Quadruple_double_quotes" { 151 | verifyParse @"""""""""Call me Ishmael""""""""" [@"""Call" ;"me" ;@"Ishmael"""] 152 | } 153 | ] 154 | 155 | open FsCheck 156 | 157 | let escapeRoundtripWithParse = 158 | testProperty "Escape is the inverse of Parse" <| 159 | fun (x: NonNull list) (alwaysQuoteArguments: bool) (doubleQuoteEscapeQuote: bool) -> 160 | let input = x |> List.map (fun (NonNull s) -> s) 161 | let settings = 162 | { MsvcrCommandLine.defaultEscapeSettings with 163 | AlwaysQuoteArguments = alwaysQuoteArguments 164 | DoubleQuoteEscapeQuote = doubleQuoteEscapeQuote} 165 | let escaped = MsvcrCommandLine.escape settings input 166 | let backAgain = MsvcrCommandLine.parse escaped 167 | Expect.equal "Input and escaped/parsed should equal" input backAgain 168 | 169 | 170 | [] 171 | let test = 172 | testList "MSVCR command line" [ 173 | testList "escape" escapeTests 174 | testList "parse" parseTests 175 | testList "Property Based" [escapeRoundtripWithParse] 176 | ] 177 | -------------------------------------------------------------------------------- /src/BlackFox.JavaPropertiesFile/Parser.fs: -------------------------------------------------------------------------------- 1 | module private BlackFox.JavaPropertiesFile.Parser 2 | 3 | open System.Text 4 | open System.IO 5 | open System.Globalization 6 | 7 | type CharReader = unit -> char option 8 | 9 | let inline (|IsWhitespace|_|) c = 10 | match c with 11 | | Some c -> if c = ' ' || c = '\t' || c = '\u00ff' then Some c else None 12 | | None -> None 13 | 14 | type IsEof = 15 | | Yes = 1y 16 | | No = 0y 17 | 18 | let rec readToFirstChar (c: char option) (reader: CharReader) = 19 | match c with 20 | | IsWhitespace _ -> 21 | readToFirstChar (reader ()) reader 22 | | Some '\r' 23 | | Some '\n' -> 24 | None, IsEof.No 25 | | Some _ -> c, IsEof.No 26 | | None -> None, IsEof.Yes 27 | 28 | let inline (|EscapeSequence|_|) c = 29 | match c with 30 | | Some c -> 31 | if c = 'r' || c = 'n' || c = 'u' || c = 'f' || c = 't' || c = '"' || c = ''' || c = '\\' then 32 | Some c 33 | else 34 | None 35 | | None -> None 36 | 37 | let inline isHex c = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') 38 | 39 | let readEscapeSequence (c: char) (reader: CharReader) = 40 | match c with 41 | | 'r' -> '\r' 42 | | 'n' -> '\n' 43 | | 'f' -> '\f' 44 | | 't' -> '\t' 45 | | 'u' -> 46 | match reader(), reader(), reader(), reader() with 47 | | Some c1, Some c2, Some c3, Some c4 when isHex c1 && isHex c2 && isHex c3 && isHex c4 -> 48 | let hex = System.String([|c1;c2;c3;c4|]) 49 | let value = System.UInt16.Parse(hex, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture) 50 | char value 51 | | _ -> 52 | failwith "Invalid unicode escape" 53 | | _ -> c 54 | 55 | let inline readKey (c: char option) (reader: CharReader) (buffer: StringBuilder) = 56 | let rec recurseEnd (result: string) = 57 | match reader () with 58 | | Some ':' 59 | | Some '=' 60 | | IsWhitespace _ -> recurseEnd result 61 | | Some '\r' 62 | | Some '\n' -> result, false, None, IsEof.No 63 | | None -> result, false, None, IsEof.Yes 64 | | Some c -> result, true, Some c, IsEof.No 65 | let rec recurse (c: char option) (buffer: StringBuilder) (escaping: bool) = 66 | match c with 67 | | EscapeSequence c when escaping -> 68 | let realChar = readEscapeSequence c reader 69 | recurse (reader()) (buffer.Append(realChar)) false 70 | | Some ' ' -> recurseEnd (buffer.ToString()) 71 | | Some ':' 72 | | Some '=' when not escaping -> recurseEnd (buffer.ToString()) 73 | | Some '\r' 74 | | Some '\n' -> buffer.ToString(), false, None, IsEof.No 75 | | None -> buffer.ToString(), false, None, IsEof.Yes 76 | | Some '\\' -> recurse (reader ()) buffer true 77 | | Some c -> recurse (reader ()) (buffer.Append(c)) false 78 | 79 | recurse c buffer false 80 | 81 | let rec readComment (reader: CharReader) (buffer: StringBuilder) = 82 | match reader () with 83 | | Some '\r' 84 | | Some '\n' -> 85 | Some (Comment (buffer.ToString())), IsEof.No 86 | | None -> 87 | Some(Comment (buffer.ToString())), IsEof.Yes 88 | | Some c -> 89 | readComment reader (buffer.Append(c)) 90 | 91 | let inline readValue (c: char option) (reader: CharReader) (buffer: StringBuilder) = 92 | let rec recurse (c: char option) (buffer: StringBuilder) (escaping: bool) (cr: bool) (lineStart: bool) = 93 | match c with 94 | | EscapeSequence c when escaping -> 95 | let realChar = readEscapeSequence c reader 96 | recurse (reader()) (buffer.Append(realChar)) false false false 97 | | Some '\r' 98 | | Some '\n' -> 99 | if escaping || (cr && c = Some '\n') then 100 | recurse (reader ()) buffer false (c = Some '\r') true 101 | else 102 | buffer.ToString(), IsEof.No 103 | | None -> 104 | buffer.ToString(), IsEof.Yes 105 | | Some _ when lineStart -> 106 | let firstChar, _ = readToFirstChar c reader 107 | recurse firstChar buffer false false false 108 | | Some '\\' -> recurse (reader ()) buffer true false false 109 | | Some c -> 110 | recurse (reader()) (buffer.Append(c)) false false false 111 | 112 | recurse c buffer false false true 113 | 114 | let rec readLine (reader: CharReader) (buffer: StringBuilder) = 115 | match readToFirstChar (reader ()) reader with 116 | | Some '#', _ 117 | | Some '!', _ -> 118 | readComment reader (buffer.Clear()) 119 | | Some firstChar, _ -> 120 | let key, hasValue, c, isEof = readKey (Some firstChar) reader (buffer.Clear()) 121 | let value, isEof = 122 | if hasValue then 123 | // We know that we aren't at the end of the buffer, but readKey can return None if it didn't need the next char 124 | let firstChar = match c with | Some c -> Some c | None -> reader () 125 | readValue firstChar reader (buffer.Clear()) 126 | else 127 | "", isEof 128 | Some (KeyValue(key, value)), isEof 129 | | None, isEof -> None, isEof 130 | 131 | let inline textReaderToReader (reader: TextReader) = 132 | let buffer = [| '\u0000' |] 133 | fun () -> 134 | let eof = reader.Read(buffer, 0, 1) = 0 135 | if eof then None else Some (buffer.[0]) 136 | 137 | let parseWithReader reader = 138 | let buffer = StringBuilder(255) 139 | let mutable isEof = IsEof.No 140 | 141 | [ 142 | while isEof <> IsEof.Yes do 143 | let line, isEofAfterLine = readLine reader buffer 144 | match line with 145 | | Some line -> yield line 146 | | None -> () 147 | isEof <- isEofAfterLine 148 | ] 149 | -------------------------------------------------------------------------------- /src/BlackFox.Fake.BuildTask/BuildTask.fs: -------------------------------------------------------------------------------- 1 | /// Allow to define FAKE tasks with a syntax similar to Gulp tasks 2 | [] 3 | module BlackFox.Fake.BuildTask 4 | 5 | open Fake.Core 6 | open Fake.Core.TargetOperators 7 | open System 8 | 9 | /// What FAKE name a target 10 | type TaskMetadata = { 11 | Name: string 12 | Dependencies: TaskInfo list 13 | } 14 | /// Dependency (Soft or Hard) to a target (That can be a null object) 15 | and TaskInfo = { 16 | Metadata: TaskMetadata option 17 | IsSoft: bool 18 | } 19 | with 20 | static member NoTask = 21 | { Metadata = None; IsSoft = false } 22 | 23 | member this.Always 24 | with get() = { this with IsSoft = false } 25 | 26 | member this.IfNeeded 27 | with get() = { this with IsSoft = true } 28 | 29 | member this.If(condition: bool) = 30 | if condition then this else TaskInfo.NoTask 31 | 32 | /// Register dependencies of the passed Task in FAKE 33 | let inline private applyTaskDependecies meta = 34 | for dependency in meta.Dependencies do 35 | match dependency.Metadata with 36 | | Some dependencyMetadata -> 37 | if dependency.IsSoft then 38 | dependencyMetadata.Name ?=> meta.Name |> ignore 39 | else 40 | dependencyMetadata.Name ==> meta.Name |> ignore 41 | | None -> () 42 | 43 | /// Register the Task for FAKE with all it's dependencies 44 | let inline private registerTask meta body = 45 | Target.create meta.Name body 46 | applyTaskDependecies meta 47 | 48 | let inline private infoFromMeta meta = 49 | { Metadata = Some meta; IsSoft = false } 50 | 51 | type TaskBuilder(metadata: TaskMetadata) = 52 | member __.TryFinally(f, compensation) = 53 | try 54 | f() 55 | finally 56 | compensation() 57 | member __.TryWith(f, catchHandler) = 58 | try 59 | f() 60 | with e -> catchHandler e 61 | member __.Using(disposable: #IDisposable, f) = 62 | try 63 | f disposable 64 | finally 65 | match disposable with 66 | | null -> () 67 | | disp -> disp.Dispose() 68 | member __.For(sequence, f) = 69 | for i in sequence do f i 70 | member __.Combine(f1, f2) = f2(); f1 71 | member __.Zero() = () 72 | member __.Delay f = f 73 | member __.Run f = 74 | registerTask metadata f 75 | infoFromMeta metadata 76 | 77 | /// Define a Task with it's dependencies 78 | let createFn (name: string) (dependencies: TaskInfo list) (body: TargetParameter -> unit): TaskInfo = 79 | let metadata = {Name = name; Dependencies = dependencies } 80 | registerTask metadata body 81 | infoFromMeta metadata 82 | 83 | /// Define a Task with it's dependencies 84 | let create (name: string) (dependencies: TaskInfo list): TaskBuilder = 85 | let metadata = {Name = name; Dependencies = dependencies } 86 | TaskBuilder(metadata) 87 | 88 | /// Define a Task without any body, only dependencies 89 | let createEmpty (name: string) (dependencies: TaskInfo list): TaskInfo = 90 | let metadata = {Name = name; Dependencies = dependencies } 91 | registerTask metadata ignore 92 | infoFromMeta metadata 93 | 94 | /// Run the task specified on the command line if there was one or the 95 | /// default one otherwise. 96 | let runOrDefault (defaultTask: TaskInfo): unit = 97 | match defaultTask.Metadata with 98 | | Some metadata -> Target.runOrDefault metadata.Name 99 | | None -> failwith "No default task specified." 100 | 101 | /// Run the task specified on the command line if there was one or the 102 | /// default one otherwise. 103 | let runOrDefaultWithArguments (defaultTask: TaskInfo): unit = 104 | match defaultTask.Metadata with 105 | | Some metadata -> Target.runOrDefaultWithArguments metadata.Name 106 | | None -> failwith "No default task specified." 107 | 108 | /// Runs the task given by the target parameter or lists the available targets 109 | let runOrList (): unit = 110 | Target.runOrList () 111 | 112 | /// List all tasks available. 113 | let listAvailable (): unit = 114 | Target.listAvailable () 115 | 116 | /// Writes a dependency graph. 117 | /// Whether to print verbose output or not. 118 | /// The target for which the dependencies should be printed. 119 | let printDependencyGraph (verbose: bool) (taskInfo: TaskInfo): unit = 120 | match taskInfo.Metadata with 121 | | Some metadata -> 122 | Target.printDependencyGraph verbose metadata.Name 123 | | None -> 124 | failwith "No default task specified." 125 | 126 | /// Setup the FAKE context from a program argument 127 | /// 128 | /// Arguments are the same as the ones comming after "run" when running via FAKE. 129 | /// The only difference is that "--target" is apended if the first argument doesn't start with "-". 130 | /// 131 | /// Examples: 132 | /// 133 | /// * `foo` -> `run --target foo` 134 | /// * `--target bar --baz` -> `run --target bar --baz` 135 | let setupContextFromArgv (argv: string []): unit = 136 | let argvTweaked = 137 | match List.ofArray argv with 138 | | firstArg :: rest when not (firstArg.StartsWith("-")) -> 139 | [ "--target"; firstArg ] @ rest 140 | | argv -> argv 141 | let execContext = Context.FakeExecutionContext.Create false "build.fsx" argvTweaked 142 | Context.setExecutionContext (Fake.Core.Context.RuntimeContext.Fake execContext) 143 | 144 | let private temporaryColor (color: ConsoleColor) : IDisposable = 145 | let colorBefore = Console.ForegroundColor 146 | Console.ForegroundColor <- color 147 | { new IDisposable with member __.Dispose() = Console.ForegroundColor <- colorBefore } 148 | 149 | let private appWrap (app: unit -> unit): int = 150 | try 151 | app () 152 | 0 153 | with 154 | | ex -> 155 | use __ = temporaryColor ConsoleColor.Red 156 | printfn "%O" ex 157 | 1 158 | 159 | /// Run the task specified on the command line if there was one or the 160 | /// default one otherwise. Return 0 on success and 1 on error, printing 161 | /// the exception on the console. 162 | let runOrDefaultApp (defaultTask: TaskInfo): int = 163 | appWrap (fun _ -> runOrDefault defaultTask) 164 | 165 | /// Runs the task given by the target parameter or lists the available targets. 166 | /// Return 0 on success and 1 on error, printing the exception on the console. 167 | let runOrListApp (): int = 168 | appWrap (fun _ -> runOrList ()) 169 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.pch 68 | *.pdb 69 | *.pgc 70 | *.pgd 71 | *.rsp 72 | *.sbr 73 | *.tlb 74 | *.tli 75 | *.tlh 76 | *.tmp 77 | *.tmp_proj 78 | *.log 79 | *.vspscc 80 | *.vssscc 81 | .builds 82 | *.pidb 83 | *.svclog 84 | *.scc 85 | 86 | # Chutzpah Test files 87 | _Chutzpah* 88 | 89 | # Visual C++ cache files 90 | ipch/ 91 | *.aps 92 | *.ncb 93 | *.opendb 94 | *.opensdf 95 | *.sdf 96 | *.cachefile 97 | *.VC.db 98 | *.VC.VC.opendb 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | *.sap 105 | 106 | # Visual Studio Trace Files 107 | *.e2e 108 | 109 | # TFS 2012 Local Workspace 110 | $tf/ 111 | 112 | # Guidance Automation Toolkit 113 | *.gpState 114 | 115 | # ReSharper is a .NET coding add-in 116 | _ReSharper*/ 117 | *.[Rr]e[Ss]harper 118 | *.DotSettings.user 119 | 120 | # JustCode is a .NET coding add-in 121 | .JustCode 122 | 123 | # TeamCity is a build add-in 124 | _TeamCity* 125 | 126 | # DotCover is a Code Coverage Tool 127 | *.dotCover 128 | 129 | # AxoCover is a Code Coverage Tool 130 | .axoCover/* 131 | !.axoCover/settings.json 132 | 133 | # Visual Studio code coverage results 134 | *.coverage 135 | *.coveragexml 136 | 137 | # NCrunch 138 | _NCrunch_* 139 | .*crunch*.local.xml 140 | nCrunchTemp_* 141 | 142 | # MightyMoose 143 | *.mm.* 144 | AutoTest.Net/ 145 | 146 | # Web workbench (sass) 147 | .sass-cache/ 148 | 149 | # Installshield output folder 150 | [Ee]xpress/ 151 | 152 | # DocProject is a documentation generator add-in 153 | DocProject/buildhelp/ 154 | DocProject/Help/*.HxT 155 | DocProject/Help/*.HxC 156 | DocProject/Help/*.hhc 157 | DocProject/Help/*.hhk 158 | DocProject/Help/*.hhp 159 | DocProject/Help/Html2 160 | DocProject/Help/html 161 | 162 | # Click-Once directory 163 | publish/ 164 | 165 | # Publish Web Output 166 | *.[Pp]ublish.xml 167 | *.azurePubxml 168 | # Note: Comment the next line if you want to checkin your web deploy settings, 169 | # but database connection strings (with potential passwords) will be unencrypted 170 | *.pubxml 171 | *.publishproj 172 | 173 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 174 | # checkin your Azure Web App publish settings, but sensitive information contained 175 | # in these scripts will be unencrypted 176 | PublishScripts/ 177 | 178 | # NuGet Packages 179 | *.nupkg 180 | # The packages folder can be ignored because of Package Restore 181 | **/[Pp]ackages/* 182 | # Uncomment if necessary however generally it will be regenerated when needed 183 | #!**/[Pp]ackages/repositories.config 184 | # NuGet v3's project.json files produces more ignorable files 185 | *.nuget.props 186 | *.nuget.targets 187 | 188 | # Microsoft Azure Build Output 189 | csx/ 190 | *.build.csdef 191 | 192 | # Microsoft Azure Emulator 193 | ecf/ 194 | rcf/ 195 | 196 | # Windows Store app package directories and files 197 | AppPackages/ 198 | BundleArtifacts/ 199 | Package.StoreAssociation.xml 200 | _pkginfo.txt 201 | *.appx 202 | 203 | # Visual Studio cache files 204 | # files ending in .cache can be ignored 205 | *.[Cc]ache 206 | # but keep track of directories ending in .cache 207 | !*.[Cc]ache/ 208 | 209 | # Others 210 | ClientBin/ 211 | ~$* 212 | *~ 213 | *.dbmdl 214 | *.dbproj.schemaview 215 | *.jfm 216 | *.pfx 217 | *.publishsettings 218 | orleans.codegen.cs 219 | 220 | # Including strong name files can present a security risk 221 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 222 | #*.snk 223 | 224 | # Since there are multiple workflows, uncomment next line to ignore bower_components 225 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 226 | #bower_components/ 227 | 228 | # RIA/Silverlight projects 229 | Generated_Code/ 230 | 231 | # Backup & report files from converting an old project file 232 | # to a newer Visual Studio version. Backup files are not needed, 233 | # because we have git ;-) 234 | _UpgradeReport_Files/ 235 | Backup*/ 236 | UpgradeLog*.XML 237 | UpgradeLog*.htm 238 | ServiceFabricBackup/ 239 | 240 | # SQL Server files 241 | *.mdf 242 | *.ldf 243 | *.ndf 244 | 245 | # Business Intelligence projects 246 | *.rdl.data 247 | *.bim.layout 248 | *.bim_*.settings 249 | 250 | # Microsoft Fakes 251 | FakesAssemblies/ 252 | 253 | # GhostDoc plugin setting file 254 | *.GhostDoc.xml 255 | 256 | # Node.js Tools for Visual Studio 257 | .ntvs_analysis.dat 258 | node_modules/ 259 | 260 | # TypeScript v1 declaration files 261 | typings/ 262 | 263 | # Visual Studio 6 build log 264 | *.plg 265 | 266 | # Visual Studio 6 workspace options file 267 | *.opt 268 | 269 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 270 | *.vbw 271 | 272 | # Visual Studio LightSwitch build output 273 | **/*.HTMLClient/GeneratedArtifacts 274 | **/*.DesktopClient/GeneratedArtifacts 275 | **/*.DesktopClient/ModelManifest.xml 276 | **/*.Server/GeneratedArtifacts 277 | **/*.Server/ModelManifest.xml 278 | _Pvt_Extensions 279 | 280 | # Paket dependency manager 281 | .paket/paket.exe 282 | paket-files/ 283 | 284 | # FAKE - F# Make 285 | .fake/ 286 | 287 | # JetBrains Rider 288 | .idea/ 289 | *.sln.iml 290 | 291 | # CodeRush 292 | .cr/ 293 | 294 | # Python Tools for Visual Studio (PTVS) 295 | __pycache__/ 296 | *.pyc 297 | 298 | # Cake - Uncomment if you are using it 299 | # tools/** 300 | # !tools/packages.config 301 | 302 | # Tabs Studio 303 | *.tss 304 | 305 | # Telerik's JustMock configuration file 306 | *.jmconfig 307 | 308 | # BizTalk build output 309 | *.btp.cs 310 | *.btm.cs 311 | *.odx.cs 312 | *.xsd.cs 313 | 314 | # OpenCover UI analysis results 315 | OpenCover/ 316 | 317 | # Azure Stream Analytics local run output 318 | ASALocalRun/ 319 | 320 | # MSBuild Binary and Structured Log 321 | *.binlog 322 | 323 | TestResults.xml 324 | .ionide/ 325 | -------------------------------------------------------------------------------- /src/BlackFox.CachedFSharpReflection/FSharpTypeCache.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.CachedFSharpReflection 2 | 3 | open System 4 | open Microsoft.FSharp.Reflection 5 | open System.Reflection 6 | 7 | type FSharpTypeCache() = 8 | let typeKey (t: Type) = 9 | t.FullName 10 | 11 | let isFunction = 12 | DictCache.create 13 | typeKey 14 | FSharpType.IsFunction 15 | 16 | let isModule = 17 | DictCache.create 18 | typeKey 19 | FSharpType.IsModule 20 | 21 | let isTuple = 22 | DictCache.create 23 | typeKey 24 | FSharpType.IsTuple 25 | 26 | let isRecord = 27 | DictCache.create 28 | typeKey 29 | FSharpType.IsRecord 30 | 31 | let isUnion = 32 | DictCache.create 33 | typeKey 34 | FSharpType.IsUnion 35 | 36 | let isExceptionRepresentation = 37 | DictCache.create 38 | typeKey 39 | FSharpType.IsExceptionRepresentation 40 | 41 | let getTupleElements = 42 | DictCache.create 43 | typeKey 44 | FSharpType.GetTupleElements 45 | 46 | let getFunctionElements = 47 | DictCache.create 48 | typeKey 49 | FSharpType.GetFunctionElements 50 | 51 | let getRecordFields = 52 | DictCache.create 53 | typeKey 54 | FSharpType.GetRecordFields 55 | 56 | let getUnionCases = 57 | DictCache.create 58 | typeKey 59 | FSharpType.GetUnionCases 60 | 61 | let getExceptionFields = 62 | DictCache.create 63 | typeKey 64 | FSharpType.GetExceptionFields 65 | 66 | static member private lazyShared = lazy (FSharpTypeCache()) 67 | 68 | static member Shared with get() = FSharpTypeCache.lazyShared.Value 69 | 70 | /// Return true if the typ is a representation of an F# function type or the runtime type of a closure implementing an F# function type 71 | /// The type to check. 72 | /// True if the type check succeeds. 73 | member __.IsFunction (typ: Type): bool = 74 | isFunction |> DictCache.get typ 75 | 76 | /// Return true if the typ is a System.Type value corresponding to the compiled form of an F# module 77 | /// The type to check. 78 | /// True if the type check succeeds. 79 | member __.IsModule (typ: Type): bool = 80 | isModule |> DictCache.get typ 81 | 82 | /// Return true if the typ is a representation of an F# tuple type 83 | /// The type to check. 84 | /// True if the type check succeeds. 85 | member __.IsTuple (typ: Type): bool = 86 | isTuple |> DictCache.get typ 87 | 88 | /// Return true if the typ is a representation of an F# record type 89 | /// The type to check. 90 | /// True if the type check succeeds. 91 | member __.IsRecord (typ: Type): bool = 92 | isRecord |> DictCache.get typ 93 | 94 | /// Returns true if the typ is a representation of an F# union type or the runtime type of a value of that type 95 | /// The type to check. 96 | /// True if the type check succeeds. 97 | member __.IsUnion (typ: Type): bool = 98 | isUnion |> DictCache.get typ 99 | 100 | /// Returns true if the typ is a representation of an F# exception declaration 101 | /// The type to check. 102 | /// True if the type check is an F# exception. 103 | member __.IsExceptionRepresentation (exceptionType: Type): bool = 104 | isExceptionRepresentation |> DictCache.get exceptionType 105 | 106 | /// Gets the tuple elements from the representation of an F# tuple type. 107 | /// The input tuple type. 108 | /// An array of the types contained in the given tuple type. 109 | member __.GetTupleElements(tupleType: Type): Type[] = 110 | getTupleElements |> DictCache.get tupleType 111 | 112 | /// Gets the domain and range types from an F# function type or from the runtime type of a closure implementing an F# type 113 | /// The input function type. 114 | /// A tuple of the domain and range types of the input function. 115 | member __.GetFunctionElements(functionType: Type): Type * Type = 116 | getFunctionElements |> DictCache.get functionType 117 | 118 | /// Reads all the fields from a record value, in declaration order 119 | /// Assumes the given input is a record value. If not, ArgumentException is raised. 120 | /// The input record type. 121 | /// An array of descriptions of the properties of the record type. 122 | member __.GetRecordFields(recordType: Type): PropertyInfo[] = 123 | getRecordFields |> DictCache.get recordType 124 | 125 | /// Gets the cases of a union type. 126 | /// Assumes the given type is a union type. If not, ArgumentException is raised during pre-computation. 127 | /// The input union type. 128 | /// Thrown when the input type is not a union type. 129 | /// An array of descriptions of the cases of the given union type. 130 | member __.GetUnionCases(unionType: Type): UnionCaseInfo[] = 131 | getUnionCases |> DictCache.get unionType 132 | 133 | /// Reads all the fields from an F# exception declaration, in declaration order 134 | /// Assumes exceptionType is an exception representation type. If not, ArgumentException is raised. 135 | /// The exception type to read. 136 | /// Thrown if the given type is not an exception. 137 | /// An array containing the PropertyInfo of each field in the exception. 138 | member __.GetExceptionFields(exceptionType: Type): PropertyInfo[] = 139 | getExceptionFields |> DictCache.get exceptionType 140 | 141 | member __.Clear() = 142 | DictCache.clear isFunction 143 | DictCache.clear isModule 144 | DictCache.clear isTuple 145 | DictCache.clear isRecord 146 | DictCache.clear isUnion 147 | DictCache.clear isExceptionRepresentation 148 | DictCache.clear getTupleElements 149 | DictCache.clear getFunctionElements 150 | DictCache.clear getRecordFields 151 | DictCache.clear getUnionCases 152 | DictCache.clear getExceptionFields 153 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Build/Tasks.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.MasterOfFoo.Build.Tasks 2 | 3 | open Fake.Core 4 | open Fake.DotNet 5 | open Fake.DotNet.Testing 6 | open Fake.IO 7 | open Fake.IO.Globbing.Operators 8 | open Fake.IO.FileSystemOperators 9 | 10 | open BlackFox.Fake 11 | open System.Xml.Linq 12 | 13 | type ProjectToBuild = { 14 | Name: string 15 | BinDir: string 16 | ProjectFile: string 17 | NupkgFile: string 18 | ReleaseNotes: ReleaseNotes.ReleaseNotes 19 | } 20 | 21 | let rootDir = System.IO.Path.GetFullPath(__SOURCE_DIRECTORY__ ".." "..") 22 | let srcDir = rootDir "src" 23 | let artifactsDir = rootDir "artifacts" 24 | let testProjectName = "BlackFox.FoxSharp.Tests" 25 | let testProjectFile = srcDir testProjectName (testProjectName + ".fsproj") 26 | 27 | let createAndGetDefault () = 28 | let configuration = DotNet.BuildConfiguration.fromEnvironVarOrDefault "configuration" DotNet.BuildConfiguration.Release 29 | 30 | let inline versionPartOrZero x = if x < 0 then 0 else x 31 | 32 | let getUnionCaseName (x:'a) = 33 | match Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(x, typeof<'a>) with | case, _ -> case.Name 34 | 35 | let getReleaseNotes (projectName: string) = 36 | let fromFile = ReleaseNotes.load (srcDir projectName "Release Notes.md") 37 | if BuildServer.buildServer <> BuildServer.LocalBuild then 38 | let buildServerName = (getUnionCaseName BuildServer.buildServer).ToLowerInvariant() 39 | let nugetVer = sprintf "%s-%s.%s" fromFile.NugetVersion buildServerName BuildServer.buildVersion 40 | ReleaseNotes.ReleaseNotes.New(fromFile.AssemblyVersion, nugetVer, fromFile.Date, fromFile.Notes) 41 | else 42 | fromFile 43 | 44 | let createProjectInfo name = 45 | let bindir = artifactsDir name (string configuration) 46 | let releaseNotes = getReleaseNotes name 47 | let nupkgFile = bindir (sprintf "%s.%s.nupkg" name releaseNotes.NugetVersion) 48 | { 49 | Name = name 50 | BinDir = bindir 51 | ProjectFile = srcDir name (name + ".fsproj") 52 | NupkgFile = nupkgFile 53 | ReleaseNotes = releaseNotes 54 | } 55 | 56 | let projects = 57 | [ 58 | createProjectInfo "BlackFox.Fake.BuildTask" 59 | createProjectInfo "BlackFox.VsWhere" 60 | createProjectInfo "BlackFox.JavaPropertiesFile" 61 | createProjectInfo "BlackFox.CommandLine" 62 | createProjectInfo "BlackFox.PathEnvironment" 63 | createProjectInfo "BlackFox.CachedFSharpReflection" 64 | ] 65 | 66 | let writeVersionProps (p: ProjectToBuild) = 67 | let projectRelease = getReleaseNotes p.Name 68 | let doc = 69 | XDocument( 70 | XElement(XName.Get("Project"), 71 | XElement(XName.Get("PropertyGroup"), 72 | XElement(XName.Get "Version", projectRelease.NugetVersion), 73 | XElement(XName.Get "PackageReleaseNotes", String.toLines projectRelease.Notes)))) 74 | let path = artifactsDir p.Name "Version.props" 75 | 76 | Directory.create (Path.getDirectory path) 77 | System.IO.File.WriteAllText(path, doc.ToString()) 78 | 79 | let init = BuildTask.create "Init" [] { 80 | Directory.create artifactsDir 81 | } 82 | 83 | let clean = BuildTask.create "Clean" [init] { 84 | let objDirs = projects |> List.map(fun p -> System.IO.Path.GetDirectoryName(p.ProjectFile) "obj") 85 | Shell.cleanDirs (artifactsDir :: objDirs) 86 | } 87 | 88 | let generateVersionInfo = BuildTask.create "GenerateVersionInfo" [init; clean.IfNeeded] { 89 | for p in projects do 90 | writeVersionProps p 91 | } 92 | 93 | let buildLibraries = BuildTask.create "BuildLibraries" [generateVersionInfo; clean.IfNeeded] { 94 | for p in projects do 95 | DotNet.build 96 | (fun o -> { o with Configuration = configuration }) 97 | p.ProjectFile 98 | } 99 | 100 | let buildTests = BuildTask.create "BuildTests" [generateVersionInfo; clean.IfNeeded] { 101 | DotNet.build 102 | (fun o -> { o with Configuration = configuration }) 103 | testProjectFile 104 | } 105 | 106 | let build = BuildTask.createEmpty "Build" [buildLibraries; buildTests] 107 | 108 | let runTests = BuildTask.create "Test" [buildTests] { 109 | let baseTestDir = artifactsDir testProjectName (string configuration) 110 | let testConfs = ["netcoreapp2.0", ".dll"; "net5.0", ".dll"] 111 | let testConfs = 112 | if Environment.isWindows then 113 | ("net461", ".exe") :: testConfs 114 | else 115 | testConfs 116 | 117 | testConfs 118 | |> List.map (fun (fw, ext) -> baseTestDir fw (testProjectName + ext)) 119 | |> Expecto.run (fun p -> 120 | { p with 121 | PrintVersion = false 122 | FailOnFocusedTests = true 123 | }) 124 | 125 | for (fw, _) in testConfs do 126 | let dir = baseTestDir fw 127 | let outFile = sprintf "TestResults_%s.xml" (fw.Replace('.', '_')) 128 | File.delete (dir outFile) 129 | (dir "TestResults.xml") |> Shell.rename (dir outFile) 130 | Trace.publish (ImportData.Nunit NunitDataVersion.Nunit) (dir outFile) 131 | } 132 | 133 | let nuget = BuildTask.create "NuGet" [build] { 134 | for p in projects do 135 | DotNet.pack 136 | (fun o -> { o with Configuration = configuration }) 137 | p.ProjectFile 138 | 139 | Trace.publish ImportData.BuildArtifact p.NupkgFile 140 | } 141 | 142 | let publishNuget = BuildTask.create "PublishNuget" [nuget] { 143 | let key = 144 | match Environment.environVarOrNone "nuget-key" with 145 | | Some(key) -> key 146 | | None -> UserInput.getUserPassword "NuGet key: " 147 | 148 | Paket.pushFiles 149 | (fun o -> { o with ApiKey = key }) 150 | (projects |> List.map (fun p -> p.NupkgFile)) 151 | } 152 | 153 | let zip = BuildTask.create "Zip" [build] { 154 | for p in projects do 155 | let zipFile = artifactsDir (sprintf "%s-%s.zip" p.Name p.ReleaseNotes.NugetVersion) 156 | let comment = sprintf "%s v%s" p.Name p.ReleaseNotes.NugetVersion 157 | GlobbingPattern.createFrom p.BinDir 158 | ++ "**/*.dll" 159 | ++ "**/*.xml" 160 | -- "**/FSharp.Core.*" 161 | |> Zip.createZip p.BinDir zipFile comment 9 false 162 | 163 | Trace.publish ImportData.BuildArtifact zipFile 164 | } 165 | 166 | let _releaseTask = BuildTask.createEmpty "Release" [clean; publishNuget] 167 | let _ciTask = BuildTask.createEmpty "CI" [clean; build; runTests; zip; nuget] 168 | 169 | BuildTask.createEmpty "Default" [build; runTests] 170 | -------------------------------------------------------------------------------- /src/BlackFox.FoxSharp.Tests/JavaPropertiesFileTests.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.FoxSharp.Tests.JavaPropertiesFileTests 2 | 3 | open Expecto 4 | open Expecto.Flip 5 | open BlackFox.JavaPropertiesFile 6 | 7 | [] 8 | let empty = test "Parse empty" { 9 | let expected = List.empty 10 | let actual = JavaPropertiesFile.parseString "" 11 | Expect.equal "" expected actual 12 | } 13 | 14 | [] 15 | let simple = test "Parse simple" { 16 | let expected = [ 17 | Comment "Hello world" 18 | KeyValue ("key", "value") 19 | ] 20 | let actual = JavaPropertiesFile.parseString "#Hello world\nkey=value" 21 | Expect.equal "" expected actual 22 | } 23 | 24 | [] 25 | let twoPoints = test "Parse :" { 26 | let expected = [ 27 | KeyValue ("key", "value") 28 | ] 29 | let actual = JavaPropertiesFile.parseString "key:value" 30 | Expect.equal "" expected actual 31 | } 32 | 33 | [] 34 | let ignoreWhiteSpace = test "Ignore whitespace in the middle" { 35 | let expected = [ 36 | KeyValue ("key", "value") 37 | ] 38 | let actual = JavaPropertiesFile.parseString "key : value" 39 | Expect.equal "" expected actual 40 | } 41 | 42 | [] 43 | let endWhiteSpace = test "End whitespace is kept as part of the value" { 44 | let expected = [ 45 | KeyValue ("key", "value ") 46 | ] 47 | let actual = JavaPropertiesFile.parseString "key:value " 48 | Expect.equal "" expected actual 49 | } 50 | 51 | [] 52 | let multipleLines = test "Property can span multiple lines" { 53 | let expected = [ 54 | KeyValue ("key", "Hello World") 55 | ] 56 | let actual = JavaPropertiesFile.parseString "key=\\\n Hello \\\n World" 57 | Expect.equal "" expected actual 58 | } 59 | 60 | [] 61 | let specialChars = test "Special characters can be present" { 62 | let expected = [ 63 | KeyValue ("key", "Hello\nWorld\t!\r") 64 | ] 65 | let actual = JavaPropertiesFile.parseString "key=Hello\\nWorld\\t!\\r" 66 | Expect.equal "" expected actual 67 | } 68 | 69 | [] 70 | let escapedBackslash = test "Escaped backslash works" { 71 | let expected = [ 72 | KeyValue ("key", "Hello\\World") 73 | ] 74 | let actual = JavaPropertiesFile.parseString "key=Hello\\\\World" 75 | Expect.equal "" expected actual 76 | } 77 | 78 | [] 79 | let unicode = test "Unicode can be escaped" { 80 | let expected = [ 81 | KeyValue ("key", "Hello\u0001World") 82 | ] 83 | let actual = JavaPropertiesFile.parseString "key=Hello\\u0001World" 84 | Expect.equal "" expected actual 85 | } 86 | 87 | [] 88 | let fromDoc = 89 | testList "From doc" [ 90 | testCase "Fruits" <| fun () -> 91 | let file = @" 92 | fruits apple, banana, pear, \ 93 | cantaloupe, watermelon, \ 94 | kiwi, mango" 95 | let parsed = JavaPropertiesFile.parseString file 96 | let expected = 97 | [ 98 | KeyValue("fruits", "apple, banana, pear, cantaloupe, watermelon, kiwi, mango") 99 | ] 100 | 101 | Expect.equal "eq" expected parsed 102 | 103 | testCase "Truth" <| fun () -> 104 | let file = @" 105 | Truth = Beauty 106 | Truth:Beauty 107 | Truth :Beauty" 108 | let parsed = JavaPropertiesFile.parseString file 109 | let expected = 110 | [ 111 | KeyValue("Truth", "Beauty") 112 | KeyValue("Truth", "Beauty") 113 | KeyValue("Truth", "Beauty") 114 | ] 115 | 116 | Expect.equal "eq" expected parsed 117 | 118 | testCase "Cheeses" <| fun () -> 119 | let file = @" 120 | cheeses" 121 | let parsed = JavaPropertiesFile.parseString file 122 | let expected = 123 | [ 124 | KeyValue("cheeses", "") 125 | ] 126 | 127 | Expect.equal "eq" expected parsed 128 | 129 | testCase "all escaped" <| fun () -> 130 | let file = @" 131 | \:\= \:\=\r\nfo\o\u0020bar\t\f" 132 | let parsed = JavaPropertiesFile.parseString file 133 | let expected = 134 | [ 135 | KeyValue(":=", ":=\r\nfoo bar\t\u000c") 136 | ] 137 | 138 | Expect.equal "eq" expected parsed 139 | 140 | testCase "comments" <| fun () -> 141 | let file = @" 142 | #Hello 143 | !World\ 144 | key=value" 145 | let parsed = JavaPropertiesFile.parseString file 146 | let expected = 147 | [ 148 | Comment("Hello") 149 | Comment("World\\") 150 | KeyValue("key", "value") 151 | ] 152 | 153 | Expect.equal "eq" expected parsed 154 | ] 155 | 156 | [] 157 | let specialCases = 158 | testList "Special cases" [ 159 | testCase "Space separator" <| fun () -> 160 | let parsed = JavaPropertiesFile.parseString "foo bar" 161 | let expected = [ KeyValue("foo", "bar") ] 162 | 163 | Expect.equal "eq" expected parsed 164 | 165 | testCase "Equal separator" <| fun () -> 166 | let parsed = JavaPropertiesFile.parseString "foo=bar" 167 | let expected = [ KeyValue("foo", "bar") ] 168 | 169 | Expect.equal "eq" expected parsed 170 | 171 | testCase "Two points separator" <| fun () -> 172 | Expect.equal 173 | "eq" 174 | ([ KeyValue("foo", "bar") ]) 175 | (JavaPropertiesFile.parseString "foo:bar") 176 | 177 | testCase "Ends with escape" <| fun () -> 178 | Expect.equal 179 | "eq" 180 | ([ KeyValue("foo", "bar") ]) 181 | (JavaPropertiesFile.parseString "foo:bar\\\r\n") 182 | 183 | testCase "Escaped tab in key" <| fun () -> 184 | Expect.equal 185 | "eq" 186 | ([ KeyValue("fo\to", "bar") ]) 187 | (JavaPropertiesFile.parseString "fo\\to:bar") 188 | 189 | testCase "Escaped tab in value" <| fun () -> 190 | Expect.equal 191 | "eq" 192 | ([ KeyValue("foo", "ba\tr") ]) 193 | (JavaPropertiesFile.parseString "foo:ba\\tr") 194 | 195 | testCase "Unicode in key" <| fun () -> 196 | Expect.equal 197 | "eq" 198 | ([ KeyValue("\uD83D\udc0e", "") ]) 199 | (JavaPropertiesFile.parseString "\\uD83D\\udc0e") 200 | 201 | testCase "Unicode in value" <| fun () -> 202 | Expect.equal 203 | "eq" 204 | ([ KeyValue("foo", "\uD83D\udc0e") ]) 205 | (JavaPropertiesFile.parseString "foo:\\uD83D\\udc0e") 206 | ] 207 | 208 | [] 209 | let fullFiles = 210 | testList "Fullfiles" [ 211 | testCase "1" <| fun () -> 212 | let parsed = JavaPropertiesFile.parseString @"#Fri Jan 17 22:37:45 MYT 2014 213 | dbpassword=password 214 | database=localhost 215 | dbuser=vbfox" 216 | let expected = 217 | [ 218 | Comment("Fri Jan 17 22:37:45 MYT 2014") 219 | KeyValue("dbpassword", "password") 220 | KeyValue("database", "localhost") 221 | KeyValue("dbuser", "vbfox") 222 | ] 223 | 224 | Expect.equal "eq" expected parsed 225 | ] 226 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine/MsvcrCommandLine.fs: -------------------------------------------------------------------------------- 1 | /// Parse and escape arguments in a form that programs parsing it as Microsoft C Runtime will successfuly understand 2 | module BlackFox.CommandLine.MsvcrCommandLine 3 | 4 | // The best explanation for the rule and especially their history can be found in 5 | // http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV 6 | // The current version of the behavior is also documented by microsoft at 7 | // https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw 8 | 9 | open System.Text 10 | 11 | /// Settings for the escape method 12 | type EscapeSettings = { 13 | /// Specify that arguments should always be quoted, even simple values 14 | AlwaysQuoteArguments: bool 15 | 16 | /// Use `""` to escape a quote, otherwise `\"` is used 17 | /// Notes: 18 | /// 19 | /// * Forces all arguments containing quotes to be surrounded by quotes 20 | /// * This isn't compatible with pre-2008 msvcrt 21 | DoubleQuoteEscapeQuote: bool } 22 | 23 | /// Default escape settings 24 | let defaultEscapeSettings = { 25 | AlwaysQuoteArguments = false 26 | DoubleQuoteEscapeQuote = false } 27 | 28 | let private addBackslashes (builder: StringBuilder) (backslashes: int) (beforeQuote: bool) = 29 | if backslashes <> 0 then 30 | // Always using 'backslashes * 2' would work it would just produce needless '\' 31 | let count = 32 | if not beforeQuote then 33 | // backslashes not followed immediately by a double quotation mark are interpreted literally 34 | backslashes 35 | else 36 | backslashes * 2 37 | 38 | for _ in [0..count-1] do 39 | builder.Append('\\') |> ignore 40 | 41 | let private escapeArgCore (settings: EscapeSettings) (arg : string) (builder : StringBuilder) needQuote = 42 | let rec escape pos backslashes = 43 | if pos >= arg.Length then 44 | addBackslashes builder backslashes needQuote 45 | () 46 | else 47 | let c = arg.[pos] 48 | match c with 49 | | '\\' -> 50 | escape (pos+1) (backslashes+1) 51 | | '"' -> 52 | addBackslashes builder backslashes true 53 | if settings.DoubleQuoteEscapeQuote then 54 | builder.Append("\"\"") |> ignore 55 | else 56 | builder.Append("\\\"") |> ignore 57 | escape (pos+1) 0 58 | | _ -> 59 | addBackslashes builder backslashes false 60 | builder.Append(c) |> ignore 61 | escape (pos+1) 0 62 | 63 | escape 0 0 64 | 65 | let escapeArgumentToBuilder (settings: EscapeSettings) (arg : string) (builder : StringBuilder): unit = 66 | builder.EnsureCapacity(arg.Length + builder.Length) |> ignore 67 | 68 | if arg.Length = 0 then 69 | // Empty arg 70 | builder.Append(@"""""") |> ignore 71 | else 72 | let mutable needQuote = settings.AlwaysQuoteArguments 73 | let mutable containsQuoteOrBackslash = false 74 | 75 | if not settings.AlwaysQuoteArguments then 76 | for c in arg do 77 | needQuote <- needQuote || (c = ' ') 78 | needQuote <- needQuote || (c = '\t') 79 | if settings.DoubleQuoteEscapeQuote then 80 | // Double quote escaping can only work inside quoted blocks 81 | // (Also it's specific to post-2008 msvcrt) 82 | needQuote <- needQuote || (c = '"') 83 | containsQuoteOrBackslash <- containsQuoteOrBackslash || (c = '"') 84 | containsQuoteOrBackslash <- containsQuoteOrBackslash || (c = '\\') 85 | 86 | if (not containsQuoteOrBackslash) && (not needQuote) then 87 | // No special characters are present, early exit 88 | builder.Append(arg) |> ignore 89 | else 90 | // Complex case, we really need to escape 91 | if needQuote then builder.Append('"') |> ignore 92 | escapeArgCore settings arg builder needQuote 93 | if needQuote then builder.Append('"') |> ignore 94 | 95 | let escapeArgument (settings: EscapeSettings) (arg : string): string = 96 | let builder = StringBuilder() 97 | escapeArgumentToBuilder settings arg builder 98 | builder.ToString() 99 | 100 | let escapeToBuilder (settings: EscapeSettings) (cmdLine: seq) (builder: StringBuilder): unit = 101 | cmdLine |> Seq.iteri (fun i arg -> 102 | if (i <> 0) then builder.Append(' ') |> ignore 103 | escapeArgumentToBuilder settings arg builder) 104 | 105 | let escape (settings: EscapeSettings) (cmdLine: seq): string = 106 | let builder = StringBuilder() 107 | escapeToBuilder settings cmdLine builder 108 | builder.ToString() 109 | 110 | let private parseBackslashes (backslashes: Ref) (buffer: StringBuilder) (c: char) = 111 | if c = '\\' then 112 | backslashes := !backslashes + 1 113 | true 114 | else if !backslashes <= 0 then 115 | false 116 | else if c = '"' then 117 | if !backslashes % 2 = 0 then 118 | // Even number of backslashes, the backslashes are considered escaped but not the quote 119 | buffer.Append('\\', !backslashes/2) |> ignore 120 | backslashes := 0 121 | false 122 | else 123 | // Odd number of backslashes, the backslashes are considered escaped and the quote too 124 | buffer.Append('\\', (!backslashes-1)/2) |> ignore 125 | buffer.Append(c) |> ignore 126 | backslashes := 0 127 | true 128 | else 129 | // Backslashes not followed by a quote are interpreted literally 130 | buffer.Append('\\', !backslashes) |> ignore 131 | backslashes := 0 132 | false 133 | 134 | let parse (args: string) = 135 | if isNull args || args.Length = 0 then 136 | [] 137 | else 138 | let buffer = StringBuilder(args.Length) 139 | let backslashes = ref 0 140 | let rec f pos result inParameter quoted = 141 | if pos >= args.Length then 142 | if inParameter then 143 | buffer.Append('\\', !backslashes) |> ignore 144 | let parsedArg = buffer.ToString() 145 | parsedArg :: result 146 | else 147 | result 148 | else 149 | let c = args.[pos] 150 | 151 | let inParameter = inParameter || ((c <> ' ') && (c <> '\t')) 152 | if not inParameter then 153 | // Whitespace between parameters 154 | f (pos+1) result false false 155 | else if parseBackslashes backslashes buffer c then 156 | // Some '\' where handled 157 | f (pos+1) result inParameter quoted 158 | else if quoted then 159 | if c = '"' then 160 | if pos + 1 < args.Length && args.[pos + 1] = '"' then 161 | // Special double quote case, insert only one quote and continue the double quoted part 162 | // "post 2008" behavior in http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV 163 | buffer.Append(c) |> ignore 164 | f (pos+2) result true true 165 | else 166 | // End of the quoted part 167 | f (pos+1) result true false 168 | else 169 | // Normal character in quoted part 170 | buffer.Append(c) |> ignore 171 | f (pos+1) result true true 172 | else 173 | match c with 174 | | ' ' 175 | | '\t' -> 176 | // End of parameter 177 | let parsedArg = buffer.ToString() 178 | buffer.Clear() |> ignore 179 | f (pos+1) (parsedArg::result) false false 180 | | '"' -> 181 | // All escapes have been handled so it start a quoted part 182 | f (pos+1) result true true 183 | | _ -> 184 | // Normal character 185 | buffer.Append(c) |> ignore 186 | f (pos+1) result true false 187 | 188 | f 0 [] false false |> List.rev 189 | -------------------------------------------------------------------------------- /src/BlackFox.CachedFSharpReflection/FSharpValueCache.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.CachedFSharpReflection 2 | 3 | open System 4 | open Microsoft.FSharp.Reflection 5 | open System.Reflection 6 | 7 | type FSharpValueCache() = 8 | let typeKey (t: Type) = 9 | t.FullName 10 | 11 | let propertyKey (p: PropertyInfo) = 12 | struct(p.DeclaringType.FullName, p.Name) 13 | 14 | let unionCaseKey (c: UnionCaseInfo) = 15 | struct(c.DeclaringType.FullName, c.Name) 16 | 17 | let recordFieldReader = 18 | DictCache.create 19 | propertyKey 20 | FSharpValue.PreComputeRecordFieldReader 21 | 22 | let recordReader = 23 | DictCache.create 24 | typeKey 25 | FSharpValue.PreComputeRecordReader 26 | 27 | let recordConstructor = 28 | DictCache.create 29 | typeKey 30 | FSharpValue.PreComputeRecordConstructor 31 | 32 | let recordConstructorInfo = 33 | DictCache.create 34 | typeKey 35 | FSharpValue.PreComputeRecordConstructorInfo 36 | 37 | let unionTagReader = 38 | DictCache.create 39 | typeKey 40 | FSharpValue.PreComputeUnionTagReader 41 | 42 | let unionTagMemberInfo = 43 | DictCache.create 44 | typeKey 45 | FSharpValue.PreComputeUnionTagMemberInfo 46 | 47 | let unionConstructorInfo = 48 | DictCache.create 49 | unionCaseKey 50 | FSharpValue.PreComputeUnionConstructorInfo 51 | 52 | let unionConstructor = 53 | DictCache.create 54 | unionCaseKey 55 | FSharpValue.PreComputeUnionConstructor 56 | 57 | let unionReader = 58 | DictCache.create 59 | unionCaseKey 60 | FSharpValue.PreComputeUnionReader 61 | 62 | let tupleReader = 63 | DictCache.create 64 | typeKey 65 | FSharpValue.PreComputeTupleReader 66 | 67 | let tuplePropertyInfo = 68 | DictCache.create 69 | (fun struct(tupleType: Type, index: int) -> sprintf "%s.%i" tupleType.FullName index) 70 | (fun struct(tupleType: Type, index: int) -> FSharpValue.PreComputeTuplePropertyInfo(tupleType, index)) 71 | 72 | let tupleConstructor = 73 | DictCache.create 74 | typeKey 75 | FSharpValue.PreComputeTupleConstructor 76 | 77 | let tupleConstructorInfo = 78 | DictCache.create 79 | typeKey 80 | FSharpValue.PreComputeTupleConstructorInfo 81 | 82 | static member private lazyShared = lazy (FSharpValueCache()) 83 | 84 | static member Shared with get() = FSharpValueCache.lazyShared.Value 85 | 86 | /// Precompute a function for reading a particular field from a record. 87 | /// Assumes the given type is a RecordType with a field of the given name. 88 | /// If not, ArgumentException is raised during pre-computation. 89 | /// 90 | /// Using the computed function will typically be faster than executing a corresponding call to Value.GetInfo 91 | /// because the path executed by the computed function is optimized given the knowledge that it will be 92 | /// used to read values of the given type. 93 | /// The PropertyInfo of the field to read. 94 | /// Thrown when the input type is not a record type. 95 | /// A function to read the specified field from the record. 96 | member __.GetRecordFieldReader (info: PropertyInfo) : (obj -> obj) = 97 | recordFieldReader |> DictCache.get info 98 | 99 | /// Precompute a function for reading all the fields from a record. The fields are returned in the 100 | /// same order as the fields reported by a call to Microsoft.FSharp.Reflection.Type.GetInfo for 101 | /// this type. 102 | /// 103 | /// Assumes the given type is a RecordType. 104 | /// If not, ArgumentException is raised during pre-computation. 105 | /// 106 | /// Using the computed function will typically be faster than executing a corresponding call to Value.GetInfo 107 | /// because the path executed by the computed function is optimized given the knowledge that it will be 108 | /// used to read values of the given type. 109 | /// The type of record to read. 110 | /// Thrown when the input type is not a record type. 111 | /// An optimized reader for the given record type. 112 | member __.GetRecordReader (recordType: Type) : (obj -> obj[]) = 113 | recordReader |> DictCache.get recordType 114 | 115 | /// Precompute a function for constructing a record value. 116 | /// 117 | /// Assumes the given type is a RecordType. 118 | /// If not, ArgumentException is raised during pre-computation. 119 | /// The type of record to construct. 120 | /// Thrown when the input type is not a record type. 121 | /// A function to construct records of the given type. 122 | member __.GetRecordConstructor (recordType:Type) : (obj[] -> obj) = 123 | recordConstructor |> DictCache.get recordType 124 | 125 | /// Get a ConstructorInfo for a record type 126 | /// The record type. 127 | /// A ConstructorInfo for the given record type. 128 | member __.GetRecordConstructorInfo (recordType:Type) : ConstructorInfo = 129 | recordConstructorInfo |> DictCache.get recordType 130 | 131 | /// Assumes the given type is a union type. 132 | /// If not, ArgumentException is raised during pre-computation. 133 | /// 134 | /// Using the computed function is more efficient than calling GetUnionCase 135 | /// because the path executed by the computed function is optimized given the knowledge that it will be 136 | /// used to read values of the given type. 137 | /// The type of union to optimize reading. 138 | /// An optimized function to read the tags of the given union type. 139 | member __.GetUnionTagReader (unionType: Type) : (obj -> int) = 140 | unionTagReader |> DictCache.get unionType 141 | 142 | /// Precompute a property or static method for reading an integer representing the case tag of a union type. 143 | /// The type of union to read. 144 | /// The description of the union case reader. 145 | member __.GetUnionTagMemberInfo (unionType: Type) : MemberInfo = 146 | unionTagMemberInfo |> DictCache.get unionType 147 | 148 | /// Precompute a function for reading all the fields for a particular discriminator case of a union type 149 | /// 150 | /// Using the computed function will typically be faster than executing a corresponding call to GetFields 151 | /// The description of the union case to read. 152 | /// A function to for reading the fields of the given union case. 153 | member __.GetUnionReader (unionCase: UnionCaseInfo): (obj -> obj[]) = 154 | unionReader |> DictCache.get unionCase 155 | 156 | /// Precompute a function for constructing a discriminated union value for a particular union case. 157 | /// The description of the union case. 158 | /// A function for constructing values of the given union case. 159 | member __.GetUnionConstructor (unionCase:UnionCaseInfo): (obj[] -> obj) = 160 | unionConstructor |> DictCache.get unionCase 161 | 162 | /// A method that constructs objects of the given case 163 | /// The description of the union case. 164 | /// The description of the constructor of the given union case. 165 | member __.GetUnionConstructorInfo (unionCase:UnionCaseInfo): MethodInfo = 166 | unionConstructorInfo |> DictCache.get unionCase 167 | 168 | /// Precompute a function for reading the values of a particular tuple type 169 | /// 170 | /// Assumes the given type is a TupleType. 171 | /// If not, ArgumentException is raised during pre-computation. 172 | /// The tuple type to read. 173 | /// Thrown when the given type is not a tuple type. 174 | /// A function to read values of the given tuple type. 175 | member __.GetTupleReader(tupleType:Type): (obj -> obj[]) = 176 | tupleReader |> DictCache.get tupleType 177 | 178 | /// Gets information that indicates how to read a field of a tuple 179 | /// The input tuple type. 180 | /// The index of the tuple element to describe. 181 | /// The description of the tuple element and an optional type and index if the tuple is big. 182 | member __.GetTuplePropertyInfo(tupleType:Type, index:int): PropertyInfo * (Type * int) option = 183 | tuplePropertyInfo |> DictCache.get struct(tupleType, index) 184 | 185 | /// Precompute a function for reading the values of a particular tuple type 186 | /// 187 | /// Assumes the given type is a TupleType. 188 | /// If not, ArgumentException is raised during pre-computation. 189 | /// The type of tuple to read. 190 | /// Thrown when the given type is not a tuple type. 191 | /// A function to read a particular tuple type. 192 | member __.GetTupleConstructor(tupleType:Type): (obj[] -> obj) = 193 | tupleConstructor |> DictCache.get tupleType 194 | 195 | /// Gets a method that constructs objects of the given tuple type. 196 | /// For small tuples, no additional type will be returned. 197 | /// 198 | /// For large tuples, an additional type is returned indicating that 199 | /// a nested encoding has been used for the tuple type. In this case 200 | /// the suffix portion of the tuple type has the given type and an 201 | /// object of this type must be created and passed as the last argument 202 | /// to the ConstructorInfo. A recursive call to PreComputeTupleConstructorInfo 203 | /// can be used to determine the constructor for that the suffix type. 204 | /// The input tuple type. 205 | /// The description of the tuple type constructor and an optional extra type 206 | /// for large tuples. 207 | member __.GetTupleConstructorInfo(tupleType:Type): ConstructorInfo * Type option = 208 | tupleConstructorInfo |> DictCache.get tupleType 209 | 210 | member __.Clear() = 211 | DictCache.clear recordFieldReader 212 | DictCache.clear recordReader 213 | DictCache.clear recordConstructor 214 | DictCache.clear recordConstructorInfo 215 | DictCache.clear unionTagReader 216 | DictCache.clear unionTagMemberInfo 217 | DictCache.clear unionConstructorInfo 218 | DictCache.clear unionConstructor 219 | DictCache.clear unionReader 220 | DictCache.clear tupleReader 221 | DictCache.clear tuplePropertyInfo 222 | DictCache.clear tupleConstructor 223 | DictCache.clear tupleConstructorInfo 224 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine/Readme.md: -------------------------------------------------------------------------------- 1 | # Command line 2 | 3 | ![Command prompt Logo](https://raw.githubusercontent.com/vbfox/FoxSharp/master/src/BlackFox.CommandLine/Icon.png) 4 | 5 | [![Nuget Package](https://img.shields.io/nuget/v/BlackFox.CommandLine.svg)](https://www.nuget.org/packages/BlackFox.CommandLine) 6 | 7 | Generate, parse and escape command lines. 8 | 9 | ## API 10 | 11 | ```fsharp 12 | open BlackFox.CommandLine 13 | 14 | type Configuration = | Release | Debug 15 | let noRestore = true 16 | let framework = Some "netstandard2.0" 17 | let configuration = Release 18 | 19 | let cmd = 20 | CmdLine.empty 21 | |> CmdLine.append "build" 22 | |> CmdLine.appendIf noRestore "--no-restore" 23 | |> CmdLine.appendPrefixIfSome "--framework" framework 24 | |> CmdLine.appendPrefixf "--configuration" "%A" configuration 25 | |> CmdLine.toString 26 | 27 | // dotnet build --no-restore --framework netstandard2.0 --configuration Release 28 | printfn "dotnet %s" cmd 29 | ``` 30 | 31 | ### CmdLine 32 | 33 | The `CmdLine` record and module implement a simple, pipeable API to generate command lines. 34 | 35 | #### empty `CmdLine` 36 | 37 | Create an empty command line. 38 | 39 | ```fsharp 40 | CmdLine.empty 41 | |> CmdLine.toString // (empty string) 42 | ``` 43 | 44 | #### concat `CmdLine seq -> CmdLine` 45 | 46 | Concatenate command lines 47 | 48 | ```fsharp 49 | let first = CmdLine.empty |> CmdLine.append "foo" 50 | let second = CmdLine.empty |> CmdLine.append "--bar" 51 | 52 | CmdLine.concat [first; second] 53 | |> CmdLine.toString // foo --bar 54 | ``` 55 | 56 | #### appendRaw `string -> CmdLine` 57 | 58 | Append a raw (Non escaped) argument to a command line. 59 | 60 | ```fsharp 61 | CmdLine.empty 62 | |> CmdLine.appendRaw "foo bar" 63 | |> CmdLine.appendRaw "baz" 64 | |> CmdLine.toString // foo bar baz 65 | ``` 66 | 67 | #### append `string -> CmdLine` 68 | 69 | Append an argument to a command line. 70 | 71 | ```fsharp 72 | CmdLine.empty 73 | |> CmdLine.append "foo bar" 74 | |> CmdLine.append "" 75 | |> CmdLine.append "baz" 76 | |> CmdLine.toString // "foo bar" "" baz 77 | ``` 78 | 79 | #### appendf `StringFormat<'T, CmdLine -> CmdLine> -> 'T` 80 | 81 | Append an argument to a command line using printf-like syntax. 82 | 83 | ```fsharp 84 | CmdLine.empty 85 | |> CmdLine.appendf "--values=%s" "foo bar" 86 | |> CmdLine.toString // "--values=foo bar" 87 | ``` 88 | 89 | #### appendPrefix `string -> string -> CmdLine` 90 | 91 | Append an argument prefixed by another. 92 | 93 | ```fsharp 94 | CmdLine.empty 95 | |> CmdLine.appendPrefix "--foo" "bar baz" 96 | |> CmdLine.toString // --foo "bar baz" 97 | ``` 98 | 99 | #### appendPrefixf `string -> StringFormat<'T, CmdLine -> CmdLine> -> 'T` 100 | 101 | Append an argument prefixed by another using printf-like syntax. 102 | 103 | ```fsharp 104 | CmdLine.empty 105 | |> CmdLine.appendPrefixf "--foo" "+:%s" "bar" 106 | |> CmdLine.toString // --foo +:bar 107 | ``` 108 | 109 | #### appendIf `bool -> string -> CmdLine -> CmdLine` 110 | 111 | Append an argument to a command line if a condition is true. 112 | 113 | ```fsharp 114 | CmdLine.empty 115 | |> CmdLine.appendIf true "--foo" 116 | |> CmdLine.appendIf false "--bar" 117 | |> CmdLine.toString // --foo 118 | ``` 119 | 120 | #### appendIff `bool -> StringFormat<'T, CmdLine -> CmdLine> -> 'T` 121 | 122 | Append an argument to a command line if a condition is true using printf-like syntax. 123 | 124 | ```fsharp 125 | CmdLine.empty 126 | |> CmdLine.appendIff true "--foo=%s" "baz" 127 | |> CmdLine.appendIff false "--bar=%s" "baz" 128 | |> CmdLine.toString // --foo=baz 129 | ``` 130 | 131 | #### appendPrefixIf `bool -> string -> string -> CmdLine -> CmdLine` 132 | 133 | Append an argument to a command line if a condition is true. 134 | 135 | ```fsharp 136 | CmdLine.empty 137 | |> CmdLine.appendPrefixIf true "--foo" "baz" 138 | |> CmdLine.appendPrefixIf false "--bar" "baz" 139 | |> CmdLine.toString // --foo baz 140 | ``` 141 | 142 | #### appendPrefixIff `bool -> string -> StringFormat<'T, CmdLine -> CmdLine> -> 'T` 143 | 144 | Append an argument to a command line if a condition is true using printf-like syntax. 145 | 146 | ```fsharp 147 | CmdLine.empty 148 | |> CmdLine.appendPrefixIff "--foo" true "+:%s" "baz" 149 | |> CmdLine.appendPrefixIff "--bar" false "+:%s" "baz" 150 | |> CmdLine.toString // --foo +:baz 151 | ``` 152 | 153 | #### appendIfSome `string option -> CmdLine -> CmdLine` 154 | 155 | Append an argument to a command line if the value is Some. 156 | 157 | ```fsharp 158 | CmdLine.empty 159 | |> CmdLine.appendIfSome (Some "--foo") 160 | |> CmdLine.appendIfSome None 161 | |> CmdLine.toString // --foo 162 | ``` 163 | 164 | #### appendIfSomef `StringFormat<'TArg -> string> -> 'TArg option -> CmdLine -> CmdLine` 165 | 166 | Append an argument to a command line if the value is Some using printf-like syntax (With a single argument). 167 | 168 | ```fsharp 169 | CmdLine.empty 170 | |> CmdLine.appendIfSomef "--foo=%s" (Some "baz") 171 | |> CmdLine.appendIfSomef "--bar=" None 172 | |> CmdLine.toString // --foo=baz 173 | ``` 174 | 175 | #### appendPrefixIfSome `string -> string option -> CmdLine -> CmdLine` 176 | 177 | Append an argument prefixed by another if the value is Some. 178 | 179 | ```fsharp 180 | CmdLine.empty 181 | |> CmdLine.appendPrefixIfSome "--foo" (Some "baz") 182 | |> CmdLine.appendPrefixIfSome "--bar" None 183 | |> CmdLine.toString // --foo baz 184 | ``` 185 | 186 | #### appendPrefixIfSomef `string -> StringFormat<'TArg -> string> -> 'TArg option -> CmdLine -> CmdLine` 187 | 188 | Append an argument prefixed by another if the value is Some using printf-like syntax (With a single argument). 189 | 190 | ```fsharp 191 | CmdLine.empty 192 | |> CmdLine.appendPrefixIfSomef "--foo" "+:%s" (Some "baz") 193 | |> CmdLine.appendPrefixIfSomef "--bar" "+:%s" None 194 | |> CmdLine.toString // --foo +:baz 195 | ``` 196 | 197 | #### appendSeq `string seq -> CmdLine -> CmdLine` 198 | 199 | Append a sequence of arguments. 200 | 201 | ```fsharp 202 | CmdLine.empty 203 | |> CmdLine.appendSeq ["--foo"; "bar"] 204 | |> CmdLine.toString // --foo bar 205 | ``` 206 | 207 | #### appendSeqf `StringFormat<'TArg -> string> -> string seq -> CmdLine -> CmdLine` 208 | 209 | Append a sequence of arguments using printf-like syntax (With a single argument). 210 | 211 | ```fsharp 212 | CmdLine.empty 213 | |> CmdLine.appendSeqf "--foo=%s" ["bar"; "baz"] 214 | |> CmdLine.toString // --foo=bar --foo=baz 215 | ``` 216 | 217 | #### appendPrefixSeq `string -> string seq -> CmdLine -> CmdLine` 218 | 219 | Append a sequence of arguments each being prefixed. 220 | 221 | ```fsharp 222 | CmdLine.empty 223 | |> CmdLine.appendPrefixSeq "--add" ["foo"; "bar"; "baz"] 224 | |> CmdLine.toString // --add foo --add bar --add baz 225 | ``` 226 | 227 | #### appendPrefixSeqf `string -> StringFormat<'TArg -> string> -> string seq -> CmdLine -> CmdLine` 228 | 229 | Append a sequence of arguments each being prefixed using printf-like syntax (With a single argument). 230 | 231 | ```fsharp 232 | CmdLine.empty 233 | |> CmdLine.appendPrefixSeqf "--add" "./%s" ["bar"; "baz"] 234 | |> CmdLine.toString // --add ./bar ./baz 235 | ``` 236 | 237 | #### appendIfNotNullOrEmpty `string -> CmdLine -> CmdLine` 238 | 239 | Append an argument if the value isn't null or empty. 240 | 241 | ```fsharp 242 | CmdLine.empty 243 | |> CmdLine.appendIfNotNullOrEmpty "" 244 | |> CmdLine.appendIfNotNullOrEmpty "--foo" 245 | |> CmdLine.appendIfNotNullOrEmpty null 246 | |> CmdLine.toString // --foo 247 | ``` 248 | 249 | #### appendIfNotNullOrEmptyf `StringFormat string> -> string -> CmdLine -> CmdLine` 250 | 251 | Append an argument if the value isn't null or empty using printf-like syntax (With a single string argument). 252 | 253 | ```fsharp 254 | CmdLine.empty 255 | |> CmdLine.appendIfNotNullOrEmptyf "--foo=%s" "" 256 | |> CmdLine.appendIfNotNullOrEmptyf "--foo=%s" "bar" 257 | |> CmdLine.appendIfNotNullOrEmptyf "--foo=%s" null 258 | |> CmdLine.toString // --foo=bar 259 | ``` 260 | 261 | #### appendPrefixIfNotNullOrEmpty `string -> string -> CmdLine -> CmdLine` 262 | 263 | Append an argument if the value isn't null or empty. 264 | 265 | ```fsharp 266 | CmdLine.empty 267 | |> CmdLine.appendPrefixIfNotNullOrEmpty "--foo" "" 268 | |> CmdLine.appendPrefixIfNotNullOrEmpty "--foo" "bar" 269 | |> CmdLine.appendPrefixIfNotNullOrEmpty "--foo" null 270 | |> CmdLine.toString // --foo bar 271 | ``` 272 | 273 | #### appendPrefixIfNotNullOrEmptyf `string -> StringFormat string> -> string -> CmdLine -> CmdLine` 274 | 275 | Append an argument prefixed by another if the value isn't null or empty using printf-like syntax (With a single string argument). 276 | 277 | ```fsharp 278 | CmdLine.empty 279 | |> CmdLine.appendPrefixIfNotNullOrEmptyf "--foo" "./%s" "" 280 | |> CmdLine.appendPrefixIfNotNullOrEmptyf "--foo" "./%s" "bar" 281 | |> CmdLine.appendPrefixIfNotNullOrEmptyf "--foo" "./%s" null 282 | |> CmdLine.toString // --foo ./bar 283 | ``` 284 | 285 | #### fromSeq `string seq -> CmdLine` 286 | 287 | Create a command line from a sequence of arguments. 288 | 289 | ```fsharp 290 | seq { yield "foo bar"; yield "baz" } 291 | |> CmdLine.fromSeq 292 | |> CmdLine.toString // "foo bar" baz 293 | ``` 294 | 295 | #### fromList `string list -> CmdLine` 296 | 297 | Create a command line from a list of arguments. 298 | 299 | ```fsharp 300 | ["foo bar"; "baz"] 301 | |> CmdLine.fromList 302 | |> CmdLine.toString // "foo bar" baz 303 | ``` 304 | 305 | #### fromArray `string [] -> CmdLine` 306 | 307 | Create a command line from a list of arguments. 308 | 309 | ```fsharp 310 | [|"foo bar"; "baz"|] 311 | |> CmdLine.fromArray 312 | |> CmdLine.toString // "foo bar" baz 313 | ``` 314 | 315 | #### toList `CmdLine -> string list` 316 | 317 | Get a list of arguments from a command line (No escaping is applied). 318 | 319 | ```fsharp 320 | CmdLine.empty 321 | |> CmdLine.append "foo bar" 322 | |> CmdLine.append "baz" 323 | |> CmdLine.toList // ["foo bar"; "baz"] 324 | ``` 325 | 326 | #### toArray `CmdLine -> string []` 327 | 328 | Get an array of arguments from a command line (No escaping is applied). 329 | 330 | ```fsharp 331 | CmdLine.empty 332 | |> CmdLine.append "foo bar" 333 | |> CmdLine.append "baz" 334 | |> CmdLine.toArray // [|"foo bar"; "baz"|] 335 | ``` 336 | 337 | #### toStringForMsvcr `MsvcrCommandLine.EscapeSettings -> CmdLine -> string` 338 | 339 | Convert a command line to string using the [Microsoft C Runtime][MsvcrtParsing] (Windows default) rules. 340 | 341 | #### toString `CmdLine -> string` 342 | 343 | Convert a command line to string as expected by `System.Diagnostics.Process`. 344 | 345 | ### MsvcrCommandLine 346 | 347 | The `MsvcrCommandLine` module is specific to the way the [Microsoft C Runtime algorithm][MsvcrtParsing] works on Windows. It's how the vast majority of arguments are parsed on the Windows platform. 348 | 349 | #### EscapeSettings 350 | 351 | Record type of settings for `escape`: 352 | 353 | * `AlwaysQuoteArguments: bool`: Specify that arguments should always be quoted, even simple values 354 | * `DoubleQuoteEscapeQuote`: Use `""` to escape a quote, otherwise `\"` is used 355 | * Forces all arguments containing quotes to be surrounded by quotes 356 | * This isn't compatible with pre-2008 msvcr 357 | 358 | #### escape `EscapeSettings -> seq -> string` 359 | 360 | Escape arguments in a form that programs parsing it as Microsoft C Runtime will successfully understand. 361 | 362 | #### parse `string -> string list` 363 | 364 | Parse a string representing arguments as the Microsoft C Runtime does. 365 | 366 | [MsvcrtParsing]: http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV 367 | 368 | ## Thanks 369 | 370 | * [Newaita icon pack](https://github.com/cbrnix/Newaita) for the base of the icon (License: [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/)) 371 | * [@matthid](https://github.com/matthid) for [finding a bug](https://github.com/vbfox/FoxSharp/issues/1) when comparing this implementation to the one in FAKE 5 372 | -------------------------------------------------------------------------------- /src/BlackFox.VsWhere/ComTypes.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.VsWhere 2 | 3 | open System 4 | open System.Runtime.InteropServices 5 | 6 | [] 7 | [] 8 | [] 9 | [] 10 | type private ISetupInstance = 11 | abstract member GetInstanceId: unit -> [] string 12 | abstract member GetInstallDate: unit -> [] System.Runtime.InteropServices.ComTypes.FILETIME 13 | abstract member GetInstallationName : unit -> [] string 14 | abstract member GetInstallationPath : unit -> [] string 15 | abstract member GetInstallationVersion : unit -> [] string 16 | abstract member GetDisplayName : [][] lcid: int -> [] string 17 | abstract member GetDescription : [][] lcid: int -> [] string 18 | abstract member ResolvePath : [][] pwszRelativePath: string -> [] string 19 | 20 | [] 21 | type InstanceState = 22 | | None = 0u 23 | | Local = 1u 24 | | Registered = 2u 25 | | NoRebootRequired = 4u 26 | | NoErrors = 8u 27 | | Complete = 0xFFFFFFFFu 28 | 29 | [] 30 | [] 31 | [] 32 | [] 33 | type private ISetupPackageReference = 34 | abstract member GetId : unit -> [] string 35 | abstract member GetVersion : unit -> [] string 36 | abstract member GetChip : unit -> [] string 37 | abstract member GetLanguage : unit -> [] string 38 | abstract member GetBranch : unit -> [] string 39 | abstract member GetType : unit -> [] string 40 | abstract member GetUniqueId : unit -> [] string 41 | abstract member GetIsExtension : unit -> [] bool 42 | 43 | [] 44 | [] 45 | [] 46 | [] 47 | type private ISetupFailedPackageReference = 48 | inherit ISetupPackageReference 49 | abstract member GetId : unit -> [] string 50 | abstract member GetVersion : unit -> [] string 51 | abstract member GetChip : unit -> [] string 52 | abstract member GetLanguage : unit -> [] string 53 | abstract member GetBranch : unit -> [] string 54 | abstract member GetType : unit -> [] string 55 | abstract member GetUniqueId : unit -> [] string 56 | abstract member GetIsExtension : unit -> [] bool 57 | 58 | [] 59 | [] 60 | [] 61 | [] 62 | type private ISetupFailedPackageReference2 = 63 | inherit ISetupFailedPackageReference 64 | abstract member GetId : unit -> [] string 65 | abstract member GetVersion : unit -> [] string 66 | abstract member GetChip : unit -> [] string 67 | abstract member GetLanguage : unit -> [] string 68 | abstract member GetBranch : unit -> [] string 69 | abstract member GetType : unit -> [] string 70 | abstract member GetUniqueId : unit -> [] string 71 | abstract member GetIsExtension : unit -> [] bool 72 | abstract member GetLogFilePath : unit -> [] string 73 | abstract member GetDescription : unit -> [] string 74 | abstract member GetSignature : unit -> [] string 75 | abstract member GetDetails: unit -> [] string[] 76 | abstract member GetAffectedPackages: unit -> [] ISetupPackageReference[] 77 | 78 | [] 79 | [] 80 | [] 81 | [] 82 | type private ISetupErrorInfo = 83 | abstract member GetErrorHResult: unit -> int 84 | abstract member GetErrorClassName: unit -> [] string 85 | abstract member GetErrorMessage: unit -> [] string 86 | 87 | [] 88 | [] 89 | [] 90 | [] 91 | type private ISetupErrorState = 92 | abstract member GetFailedPackages: unit -> [] ISetupFailedPackageReference[] 93 | abstract member GetSkippedPackages: unit -> [] ISetupPackageReference[] 94 | 95 | [] 96 | [] 97 | [] 98 | [] 99 | type private ISetupErrorState2 = 100 | inherit ISetupErrorState 101 | abstract member GetFailedPackages: unit -> [] ISetupFailedPackageReference[] 102 | abstract member GetSkippedPackages: unit -> [] ISetupPackageReference[] 103 | abstract member GetErrorLogFilePath : unit -> [] string 104 | abstract member GetLogFilePath : unit -> [] string 105 | 106 | [] 107 | [] 108 | [] 109 | [] 110 | type private ISetupErrorState3 = 111 | inherit ISetupErrorState2 112 | abstract member GetFailedPackages: unit -> [] ISetupFailedPackageReference[] 113 | abstract member GetSkippedPackages: unit -> [] ISetupPackageReference[] 114 | abstract member GetErrorLogFilePath : unit -> [] string 115 | abstract member GetLogFilePath : unit -> [] string 116 | abstract member GetRuntimeError : unit -> ISetupErrorInfo 117 | 118 | [] 119 | [] 120 | [] 121 | [] 122 | type private ISetupPropertyStore = 123 | abstract member GetNames : unit -> [] string[] 124 | abstract member GetValue: [][] pwszName: string -> obj 125 | 126 | [] 127 | [] 128 | [] 129 | [] 130 | type private ISetupInstance2 = 131 | inherit ISetupInstance 132 | abstract member GetInstanceId: unit -> [] string 133 | abstract member GetInstallDate: unit -> [] System.Runtime.InteropServices.ComTypes.FILETIME 134 | abstract member GetInstallationName : unit -> [] string 135 | abstract member GetInstallationPath : unit -> [] string 136 | abstract member GetInstallationVersion : unit -> [] string 137 | abstract member GetDisplayName : [][] lcid: int -> [] string 138 | abstract member GetDescription : [][] lcid: int -> [] string 139 | abstract member ResolvePath : [][] pwszRelativePath: string -> [] string 140 | abstract member GetState: unit -> [] InstanceState 141 | abstract member GetPackages: unit -> [] ISetupPackageReference[] 142 | abstract member GetProduct: unit -> ISetupPackageReference 143 | abstract member GetProductPath: unit -> [] string 144 | abstract member GetErrors: unit -> ISetupErrorState 145 | abstract member IsLaunchable: unit -> [] bool 146 | abstract member IsComplete: unit -> [] bool 147 | abstract member GetProperties: unit -> ISetupPropertyStore 148 | abstract member GetEnginePath: unit -> [] string 149 | 150 | [] 151 | [] 152 | [] 153 | [] 154 | type private IEnumSetupInstances = 155 | abstract member Next: 156 | [][] celt: int 157 | * [][] rgelt: ISetupInstance[] 158 | * [][] pceltFetched: byref 159 | -> unit 160 | abstract member Skip: [][] celt: int -> unit 161 | abstract member Reset: unit -> unit 162 | abstract member Clone: unit -> [] IEnumSetupInstances 163 | 164 | [] 165 | [] 166 | [] 167 | [] 168 | type private ISetupConfiguration = 169 | abstract member EnumInstances: unit -> [] IEnumSetupInstances 170 | abstract member GetInstanceForCurrentProcess: unit -> [] ISetupInstance 171 | abstract member GetInstanceForPath: [][] path: string -> [] ISetupInstance 172 | 173 | [] 174 | [] 175 | [] 176 | [] 177 | type private ISetupConfiguration2 = 178 | inherit ISetupConfiguration 179 | abstract member EnumInstances: unit -> [] IEnumSetupInstances 180 | abstract member GetInstanceForCurrentProcess: unit -> [] ISetupInstance 181 | abstract member GetInstanceForPath: [][] path: string -> [] ISetupInstance 182 | abstract member EnumAllInstances: unit -> [] IEnumSetupInstances 183 | 184 | [] 185 | [] 186 | [] 187 | type private ISetupInstanceCatalog = 188 | abstract member GetCatalogInfo: unit -> ISetupPropertyStore 189 | abstract member IsPrerelease: unit -> [] bool 190 | -------------------------------------------------------------------------------- /src/BlackFox.VsWhere/VsInstances.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.VsWhere.VsInstances 2 | 3 | open System 4 | open System.Diagnostics 5 | open System.Runtime.InteropServices 6 | open Microsoft.Win32 7 | 8 | let inline private emptySeqIfNull (s: _ seq) = 9 | if isNull s then 10 | Seq.empty 11 | else 12 | s 13 | 14 | let private setupConfiguration = lazy( 15 | let configType = System.Type.GetTypeFromCLSID (System.Guid "177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D") 16 | Activator.CreateInstance configType :?> ISetupConfiguration 17 | ) 18 | 19 | let private enumAllInstances () = 20 | let instancesEnumerator = 21 | let v1 = setupConfiguration.Value 22 | match v1 with 23 | | :? ISetupConfiguration2 as v2 -> v2.EnumAllInstances() 24 | | _ -> v1.EnumInstances() 25 | let instances = Array.zeroCreate 1 26 | let fetched = ref 1 27 | seq { 28 | while !fetched = 1 do 29 | instancesEnumerator.Next(1, instances, fetched) 30 | if !fetched = 1 then 31 | yield instances.[0] 32 | } 33 | 34 | let private parseErrorInfo (error: ISetupErrorInfo) = 35 | if isNull error then 36 | None 37 | else 38 | { 39 | HResult = error.GetErrorHResult() 40 | ErrorClassName = error.GetErrorClassName() 41 | ErrorMessage = error.GetErrorMessage() 42 | } 43 | |> Some 44 | 45 | let private parsePackageReference (instance: ISetupPackageReference) = 46 | { 47 | Id = instance.GetId() 48 | Version = instance.GetVersion() 49 | Chip = instance.GetChip() 50 | Language = instance.GetLanguage() 51 | Branch = instance.GetBranch() 52 | Type = instance.GetType() 53 | UniqueId = instance.GetUniqueId() 54 | IsExtension = instance.GetIsExtension() 55 | } 56 | 57 | let private parseErrorState (state: ISetupErrorState) = 58 | let result = 59 | { 60 | FailedPackages = state.GetFailedPackages() |> emptySeqIfNull |> Seq.map parsePackageReference |> List.ofSeq 61 | SkippedPackages = state.GetSkippedPackages() |> emptySeqIfNull |> Seq.map parsePackageReference |> List.ofSeq 62 | ErrorLogFilePath = None 63 | LogFilePath = None 64 | RuntimeError = None 65 | } 66 | 67 | match state with 68 | | :? ISetupErrorState2 as state2 -> 69 | let result2 = 70 | { result with 71 | ErrorLogFilePath = state2.GetErrorLogFilePath() |> Option.ofObj 72 | LogFilePath = state2.GetLogFilePath() |> Option.ofObj 73 | } 74 | match state2 with 75 | | :? ISetupErrorState3 as state3 -> 76 | { result2 with 77 | RuntimeError = state3.GetRuntimeError() |> parseErrorInfo 78 | } 79 | | _-> result2 80 | | _ -> result 81 | 82 | let private parseDate (date: System.Runtime.InteropServices.ComTypes.FILETIME) = 83 | let high = uint64 (uint32 date.dwHighDateTime) 84 | let low = uint64 (uint32 date.dwLowDateTime) 85 | let composed = (high <<< 32) ||| low 86 | DateTimeOffset.FromFileTime(int64 composed) 87 | 88 | let private parseProperties (store: ISetupPropertyStore) = 89 | if isNull store then 90 | Map.empty 91 | else 92 | store.GetNames() 93 | |> emptySeqIfNull 94 | |> Seq.map(fun name -> 95 | let value = store.GetValue(name) 96 | name, value) 97 | |> Map.ofSeq 98 | 99 | let private parseInstance (instance: ISetupInstance) = 100 | let mutable result = 101 | { InstanceId = instance.GetInstanceId() 102 | InstallDate = parseDate (instance.GetInstallDate()) 103 | InstallationName = instance.GetInstallationName() 104 | InstallationPath = instance.GetInstallationPath() 105 | InstallationVersion = instance.GetInstallationVersion() 106 | DisplayName = instance.GetDisplayName(0) 107 | Description = instance.GetDescription(0) 108 | State = None 109 | Packages = List.empty 110 | Product = None 111 | ProductPath = None 112 | Errors = None 113 | IsLaunchable = None 114 | IsComplete = None 115 | Properties = Map.empty 116 | EnginePath = None 117 | IsPrerelease = None 118 | CatalogInfo = Map.empty } 119 | 120 | match instance with 121 | | :? ISetupInstanceCatalog as catalog -> 122 | result <- { result with 123 | IsPrerelease = catalog.IsPrerelease() |> Some 124 | CatalogInfo = catalog.GetCatalogInfo() |> parseProperties } 125 | | _ -> () 126 | 127 | match instance with 128 | | :? ISetupInstance2 as v2 -> 129 | { result with 130 | State = v2.GetState() |> Some 131 | Packages = v2.GetPackages() |> emptySeqIfNull |> Seq.map parsePackageReference |> List.ofSeq 132 | Product = parsePackageReference (v2.GetProduct()) |> Some 133 | ProductPath = v2.GetProductPath() |> Some 134 | Errors = v2.GetErrors() |> Option.ofObj |> Option.map parseErrorState 135 | IsLaunchable = v2.IsLaunchable() |> Some 136 | IsComplete = v2.IsComplete() |> Some 137 | Properties = parseProperties (v2.GetProperties()) 138 | EnginePath = v2.GetEnginePath() |> Some } 139 | | _ -> result 140 | 141 | let private parseInstanceOrNone (instance: ISetupInstance) = 142 | try 143 | parseInstance instance 144 | |> Some 145 | with 146 | | exn -> 147 | Trace.TraceError("Failed to parse Visual Studio ISetupInstance: {0}", exn) 148 | None 149 | 150 | module private Legacy = 151 | open System.IO 152 | 153 | let private legacyVsNames = Map.ofArray [| 154 | ("7.0", "Visual Studio .NET 2002") 155 | ("7.1", "Visual Studio .NET 2003") 156 | ("8.0", "Visual Studio 2005") 157 | ("9.0", "Visual Studio 2008") 158 | ("10.0", "Visual Studio 2010") 159 | ("11.0", "Visual Studio 2012") 160 | ("12.0", "Visual Studio 2013") 161 | ("14.0", "Visual Studio 2015") 162 | |] 163 | 164 | let private legacyProductPath = "Common7\\IDE\\devenv.exe" 165 | 166 | let getAll() = 167 | if Environment.OSVersion.Platform <> PlatformID.Win32NT then 168 | List.empty 169 | else 170 | try 171 | use hklm32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32) 172 | use vs7Root = hklm32.OpenSubKey("SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") 173 | let detectedInstance = 174 | match vs7Root with 175 | | null -> 176 | Seq.empty 177 | | _ -> 178 | vs7Root.GetValueNames() 179 | |> Seq.where (fun valueName -> Map.containsKey valueName legacyVsNames) 180 | detectedInstance 181 | |> Seq.choose (fun (version) -> 182 | try 183 | let installationPath = vs7Root.GetValue(version) |> string 184 | let productPathFull = Path.Combine(installationPath, legacyProductPath) 185 | let installDate = 186 | try 187 | DateTimeOffset(Directory.GetCreationTimeUtc(installationPath)) 188 | with 189 | _ -> DateTimeOffset.MinValue 190 | let productPathExists = 191 | try 192 | File.Exists productPathFull 193 | with 194 | _ -> false 195 | let fullVersion = 196 | if productPathExists then 197 | try 198 | defaultArg 199 | (FileVersionInfo.GetVersionInfo(productPathFull).ProductVersion |> Option.ofObj) 200 | version 201 | with 202 | _ -> version 203 | else 204 | version 205 | 206 | { 207 | InstanceId = "VisualStudio." + version 208 | InstallDate = installDate 209 | InstallationName = "VisualStudio/" + fullVersion 210 | InstallationPath = installationPath 211 | InstallationVersion = fullVersion 212 | DisplayName = legacyVsNames |> Map.find version 213 | Description = "" 214 | State = None 215 | Packages = List.empty 216 | Product = None 217 | ProductPath = if productPathExists then Some legacyProductPath else None 218 | Errors = None 219 | IsLaunchable = None 220 | IsComplete = None 221 | Properties = Map.empty 222 | EnginePath = None 223 | IsPrerelease = None 224 | CatalogInfo = Map.empty 225 | } 226 | |> Some 227 | with 228 | | exn -> 229 | Trace.TraceError("Failed to parse legacy Visual Studio: {0}", exn) 230 | None) 231 | |> List.ofSeq 232 | with 233 | | _ -> List.empty 234 | 235 | /// 236 | /// Get legacy VS instances (before VS2017: VS .NET 2002 to VS2015). 237 | /// Note that the information for legacy ones is limited. 238 | /// 239 | [] 240 | let getLegacy(): VsSetupInstance list = 241 | Legacy.getAll() 242 | 243 | /// 244 | /// Get all VS2017+ instances (Visual Studio stable, preview, Build tools, ...) 245 | /// This method return instances that have installation errors and pre-releases. 246 | /// 247 | [] 248 | let getAll (): VsSetupInstance list = 249 | if Environment.OSVersion.Platform <> PlatformID.Win32NT then 250 | // No Visual Studio outside of windows 251 | List.empty 252 | else 253 | try 254 | enumAllInstances () 255 | |> Seq.choose parseInstanceOrNone 256 | |> List.ofSeq 257 | with 258 | | :? COMException -> 259 | List.empty 260 | 261 | /// 262 | /// Get all Visual Studio instances including legacy VS instances (before VS2017: VS .NET 2002 to VS2015). 263 | /// Note that the information for legacy ones is limited. 264 | /// 265 | [] 266 | let getAllWithLegacy (): VsSetupInstance list = 267 | getAll() @ getLegacy() 268 | 269 | /// Get VS2017+ instances that are completely installed 270 | [] 271 | let getCompleted (includePrerelease: bool): VsSetupInstance list = 272 | getAll () 273 | |> List.filter (fun vs -> 274 | (vs.IsComplete = None || vs.IsComplete = Some true) 275 | && (includePrerelease || vs.IsPrerelease <> Some true) 276 | ) 277 | 278 | /// Get VS2017+ instances that are completely installed and have a specific package ID installed 279 | [] 280 | let getWithPackage (packageId: string) (includePrerelease: bool): VsSetupInstance list = 281 | getAll () 282 | |> List.filter (fun vs -> 283 | (vs.IsComplete = None || vs.IsComplete = Some true) 284 | && (includePrerelease || vs.IsPrerelease <> Some true) 285 | && vs.Packages |> List.exists (fun p -> p.Id = packageId) 286 | ) 287 | -------------------------------------------------------------------------------- /FoxSharp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D359FCCF-4A69-4357-AB84-D9F93C5A5D5D}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.CommandLine", "src\BlackFox.CommandLine\BlackFox.CommandLine.fsproj", "{AEBF6413-3C9A-42F0-B5AC-97F2748471EB}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlackFox.CommandLine.TestParsers", "src\BlackFox.CommandLine.TestParsers\BlackFox.CommandLine.TestParsers.csproj", "{A88B93E4-C328-4310-A58F-00719BBF2867}" 11 | EndProject 12 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.Fake.BuildTask", "src\BlackFox.Fake.BuildTask\BlackFox.Fake.BuildTask.fsproj", "{6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}" 13 | EndProject 14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.FoxSharp.Build", "src\BlackFox.FoxSharp.Build\BlackFox.FoxSharp.Build.fsproj", "{A267BDEB-3D4F-4395-871B-4E2B906C368D}" 15 | EndProject 16 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.FoxSharp.Tests", "src\BlackFox.FoxSharp.Tests\BlackFox.FoxSharp.Tests.fsproj", "{EC62CE07-818B-4520-A214-39E03A474485}" 17 | EndProject 18 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.JavaPropertiesFile", "src\BlackFox.JavaPropertiesFile\BlackFox.JavaPropertiesFile.fsproj", "{892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}" 19 | EndProject 20 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.PathEnvironment", "src\BlackFox.PathEnvironment\BlackFox.PathEnvironment.fsproj", "{77E2AED0-A9CE-4E85-B461-0DFCA5B77194}" 21 | EndProject 22 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.VsWhere", "src\BlackFox.VsWhere\BlackFox.VsWhere.fsproj", "{540EDBC6-17EE-4E66-8917-BD278BCA579E}" 23 | EndProject 24 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.CachedFSharpReflection", "src\BlackFox.CachedFSharpReflection\BlackFox.CachedFSharpReflection.fsproj", "{F9A9A486-D307-4361-ABE4-7CD21ED4F562}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Debug|x64 = Debug|x64 30 | Debug|x86 = Debug|x86 31 | Release|Any CPU = Release|Any CPU 32 | Release|x64 = Release|x64 33 | Release|x86 = Release|x86 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 39 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Debug|x64.ActiveCfg = Debug|Any CPU 42 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Debug|x64.Build.0 = Debug|Any CPU 43 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Debug|x86.ActiveCfg = Debug|Any CPU 44 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Debug|x86.Build.0 = Debug|Any CPU 45 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Release|x64.ActiveCfg = Release|Any CPU 48 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Release|x64.Build.0 = Release|Any CPU 49 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Release|x86.ActiveCfg = Release|Any CPU 50 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB}.Release|x86.Build.0 = Release|Any CPU 51 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Debug|x64.ActiveCfg = Debug|Any CPU 54 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Debug|x64.Build.0 = Debug|Any CPU 55 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Debug|x86.ActiveCfg = Debug|Any CPU 56 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Debug|x86.Build.0 = Debug|Any CPU 57 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Release|x64.ActiveCfg = Release|Any CPU 60 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Release|x64.Build.0 = Release|Any CPU 61 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Release|x86.ActiveCfg = Release|Any CPU 62 | {A88B93E4-C328-4310-A58F-00719BBF2867}.Release|x86.Build.0 = Release|Any CPU 63 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Debug|x64.ActiveCfg = Debug|Any CPU 66 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Debug|x64.Build.0 = Debug|Any CPU 67 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Debug|x86.ActiveCfg = Debug|Any CPU 68 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Debug|x86.Build.0 = Debug|Any CPU 69 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Release|Any CPU.Build.0 = Release|Any CPU 71 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Release|x64.ActiveCfg = Release|Any CPU 72 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Release|x64.Build.0 = Release|Any CPU 73 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Release|x86.ActiveCfg = Release|Any CPU 74 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F}.Release|x86.Build.0 = Release|Any CPU 75 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 76 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Debug|Any CPU.Build.0 = Debug|Any CPU 77 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Debug|x64.ActiveCfg = Debug|Any CPU 78 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Debug|x64.Build.0 = Debug|Any CPU 79 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Debug|x86.ActiveCfg = Debug|Any CPU 80 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Debug|x86.Build.0 = Debug|Any CPU 81 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Release|Any CPU.ActiveCfg = Release|Any CPU 82 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Release|Any CPU.Build.0 = Release|Any CPU 83 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Release|x64.ActiveCfg = Release|Any CPU 84 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Release|x64.Build.0 = Release|Any CPU 85 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Release|x86.ActiveCfg = Release|Any CPU 86 | {A267BDEB-3D4F-4395-871B-4E2B906C368D}.Release|x86.Build.0 = Release|Any CPU 87 | {EC62CE07-818B-4520-A214-39E03A474485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 88 | {EC62CE07-818B-4520-A214-39E03A474485}.Debug|Any CPU.Build.0 = Debug|Any CPU 89 | {EC62CE07-818B-4520-A214-39E03A474485}.Debug|x64.ActiveCfg = Debug|Any CPU 90 | {EC62CE07-818B-4520-A214-39E03A474485}.Debug|x64.Build.0 = Debug|Any CPU 91 | {EC62CE07-818B-4520-A214-39E03A474485}.Debug|x86.ActiveCfg = Debug|Any CPU 92 | {EC62CE07-818B-4520-A214-39E03A474485}.Debug|x86.Build.0 = Debug|Any CPU 93 | {EC62CE07-818B-4520-A214-39E03A474485}.Release|Any CPU.ActiveCfg = Release|Any CPU 94 | {EC62CE07-818B-4520-A214-39E03A474485}.Release|Any CPU.Build.0 = Release|Any CPU 95 | {EC62CE07-818B-4520-A214-39E03A474485}.Release|x64.ActiveCfg = Release|Any CPU 96 | {EC62CE07-818B-4520-A214-39E03A474485}.Release|x64.Build.0 = Release|Any CPU 97 | {EC62CE07-818B-4520-A214-39E03A474485}.Release|x86.ActiveCfg = Release|Any CPU 98 | {EC62CE07-818B-4520-A214-39E03A474485}.Release|x86.Build.0 = Release|Any CPU 99 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 100 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 101 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Debug|x64.ActiveCfg = Debug|Any CPU 102 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Debug|x64.Build.0 = Debug|Any CPU 103 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Debug|x86.ActiveCfg = Debug|Any CPU 104 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Debug|x86.Build.0 = Debug|Any CPU 105 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 106 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Release|Any CPU.Build.0 = Release|Any CPU 107 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Release|x64.ActiveCfg = Release|Any CPU 108 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Release|x64.Build.0 = Release|Any CPU 109 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Release|x86.ActiveCfg = Release|Any CPU 110 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B}.Release|x86.Build.0 = Release|Any CPU 111 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 112 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Debug|Any CPU.Build.0 = Debug|Any CPU 113 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Debug|x64.ActiveCfg = Debug|Any CPU 114 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Debug|x64.Build.0 = Debug|Any CPU 115 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Debug|x86.ActiveCfg = Debug|Any CPU 116 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Debug|x86.Build.0 = Debug|Any CPU 117 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Release|Any CPU.ActiveCfg = Release|Any CPU 118 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Release|Any CPU.Build.0 = Release|Any CPU 119 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Release|x64.ActiveCfg = Release|Any CPU 120 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Release|x64.Build.0 = Release|Any CPU 121 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Release|x86.ActiveCfg = Release|Any CPU 122 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194}.Release|x86.Build.0 = Release|Any CPU 123 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 124 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Debug|Any CPU.Build.0 = Debug|Any CPU 125 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Debug|x64.ActiveCfg = Debug|Any CPU 126 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Debug|x64.Build.0 = Debug|Any CPU 127 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Debug|x86.ActiveCfg = Debug|Any CPU 128 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Debug|x86.Build.0 = Debug|Any CPU 129 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Release|Any CPU.ActiveCfg = Release|Any CPU 130 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Release|Any CPU.Build.0 = Release|Any CPU 131 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Release|x64.ActiveCfg = Release|Any CPU 132 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Release|x64.Build.0 = Release|Any CPU 133 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Release|x86.ActiveCfg = Release|Any CPU 134 | {540EDBC6-17EE-4E66-8917-BD278BCA579E}.Release|x86.Build.0 = Release|Any CPU 135 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 136 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Debug|Any CPU.Build.0 = Debug|Any CPU 137 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Debug|x64.ActiveCfg = Debug|Any CPU 138 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Debug|x64.Build.0 = Debug|Any CPU 139 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Debug|x86.ActiveCfg = Debug|Any CPU 140 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Debug|x86.Build.0 = Debug|Any CPU 141 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Release|Any CPU.ActiveCfg = Release|Any CPU 142 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Release|Any CPU.Build.0 = Release|Any CPU 143 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Release|x64.ActiveCfg = Release|Any CPU 144 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Release|x64.Build.0 = Release|Any CPU 145 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Release|x86.ActiveCfg = Release|Any CPU 146 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562}.Release|x86.Build.0 = Release|Any CPU 147 | EndGlobalSection 148 | GlobalSection(NestedProjects) = preSolution 149 | {AEBF6413-3C9A-42F0-B5AC-97F2748471EB} = {D359FCCF-4A69-4357-AB84-D9F93C5A5D5D} 150 | {A88B93E4-C328-4310-A58F-00719BBF2867} = {D359FCCF-4A69-4357-AB84-D9F93C5A5D5D} 151 | {6EFC0E74-15E0-46AE-A73C-49AE8A67AC9F} = {D359FCCF-4A69-4357-AB84-D9F93C5A5D5D} 152 | {A267BDEB-3D4F-4395-871B-4E2B906C368D} = {D359FCCF-4A69-4357-AB84-D9F93C5A5D5D} 153 | {EC62CE07-818B-4520-A214-39E03A474485} = {D359FCCF-4A69-4357-AB84-D9F93C5A5D5D} 154 | {892A9DB4-B277-4B88-8BCE-0A245E7F2A2B} = {D359FCCF-4A69-4357-AB84-D9F93C5A5D5D} 155 | {77E2AED0-A9CE-4E85-B461-0DFCA5B77194} = {D359FCCF-4A69-4357-AB84-D9F93C5A5D5D} 156 | {540EDBC6-17EE-4E66-8917-BD278BCA579E} = {D359FCCF-4A69-4357-AB84-D9F93C5A5D5D} 157 | {F9A9A486-D307-4361-ABE4-7CD21ED4F562} = {D359FCCF-4A69-4357-AB84-D9F93C5A5D5D} 158 | EndGlobalSection 159 | EndGlobal 160 | -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | STORAGE: NONE 2 | RESTRICTION: || (== net50) (== netcoreapp2.0) (== netstandard2.0) (>= net45) 3 | NUGET 4 | remote: https://api.nuget.org/v3/index.json 5 | Expecto (9.0.2) 6 | FSharp.Core (>= 4.6) - restriction: || (== net50) (== netcoreapp2.0) (== netstandard2.0) (&& (>= net45) (>= netstandard2.0)) (>= net461) 7 | Mono.Cecil (>= 0.11.2) - restriction: || (== net50) (== netcoreapp2.0) (== netstandard2.0) (&& (>= net45) (>= netstandard2.0)) (>= net461) 8 | Expecto.FsCheck (9.0.2) 9 | Expecto (>= 9.0.2) - restriction: || (== net50) (== netcoreapp2.0) (== netstandard2.0) (&& (>= net45) (>= netstandard2.0)) (>= net461) 10 | FsCheck (>= 2.14.2) - restriction: || (== net50) (== netcoreapp2.0) (== netstandard2.0) (&& (>= net45) (>= netstandard2.0)) (>= net461) 11 | FsCheck (2.15.2) 12 | FSharp.Core (>= 4.0.0.1) - restriction: || (&& (== net50) (< netstandard1.6)) (&& (== netcoreapp2.0) (< netstandard1.6)) (&& (== netstandard2.0) (< netstandard1.6)) (&& (>= net45) (< net452) (< netstandard1.6)) 13 | FSharp.Core (>= 4.2.3) - restriction: || (== net50) (== netcoreapp2.0) (== netstandard2.0) (&& (>= net45) (>= netstandard1.6)) (>= net452) 14 | FSharp.Core (5.0.1) 15 | Mono.Cecil (0.11.3) - restriction: || (== net50) (== netcoreapp2.0) (== netstandard2.0) (&& (>= net45) (>= netstandard2.0)) (>= net461) 16 | 17 | GROUP build 18 | STORAGE: NONE 19 | RESTRICTION: == net50 20 | NUGET 21 | remote: https://api.nuget.org/v3/index.json 22 | BlackFox.Fake.BuildTask (0.1.3) 23 | Fake.Core.Target (>= 5.1) 24 | FSharp.Core (>= 4.3.4) 25 | BlackFox.VsWhere (1.1) 26 | FSharp.Core (>= 4.2.3) 27 | Microsoft.Win32.Registry (>= 4.7) 28 | Fake.Api.GitHub (5.20.4) 29 | FSharp.Core (>= 4.7.2) 30 | Octokit (>= 0.48) 31 | Fake.BuildServer.GitHubActions (5.20.4) 32 | Fake.Core.Environment (>= 5.20.4) 33 | Fake.Core.Trace (>= 5.20.4) 34 | Fake.IO.FileSystem (>= 5.20.4) 35 | FSharp.Core (>= 4.7.2) 36 | Fake.Core.CommandLineParsing (5.20.4) 37 | FParsec (>= 1.1.1) 38 | FSharp.Core (>= 4.7.2) 39 | Fake.Core.Context (5.20.4) 40 | FSharp.Core (>= 4.7.2) 41 | Fake.Core.Environment (5.20.4) 42 | FSharp.Core (>= 4.7.2) 43 | Fake.Core.FakeVar (5.20.4) 44 | Fake.Core.Context (>= 5.20.4) 45 | FSharp.Core (>= 4.7.2) 46 | Fake.Core.Process (5.20.4) 47 | Fake.Core.Environment (>= 5.20.4) 48 | Fake.Core.FakeVar (>= 5.20.4) 49 | Fake.Core.String (>= 5.20.4) 50 | Fake.Core.Trace (>= 5.20.4) 51 | Fake.IO.FileSystem (>= 5.20.4) 52 | FSharp.Core (>= 4.7.2) 53 | System.Collections.Immutable (>= 1.7.1) 54 | Fake.Core.ReleaseNotes (5.20.4) 55 | Fake.Core.SemVer (>= 5.20.4) 56 | Fake.Core.String (>= 5.20.4) 57 | FSharp.Core (>= 4.7.2) 58 | Fake.Core.SemVer (5.20.4) 59 | FSharp.Core (>= 4.7.2) 60 | Fake.Core.String (5.20.4) 61 | FSharp.Core (>= 4.7.2) 62 | Fake.Core.Target (5.20.4) 63 | Fake.Core.CommandLineParsing (>= 5.20.4) 64 | Fake.Core.Context (>= 5.20.4) 65 | Fake.Core.Environment (>= 5.20.4) 66 | Fake.Core.FakeVar (>= 5.20.4) 67 | Fake.Core.Process (>= 5.20.4) 68 | Fake.Core.String (>= 5.20.4) 69 | Fake.Core.Trace (>= 5.20.4) 70 | FSharp.Control.Reactive (>= 4.4.2) 71 | FSharp.Core (>= 4.7.2) 72 | Fake.Core.Tasks (5.20.4) 73 | Fake.Core.Trace (>= 5.20.4) 74 | FSharp.Core (>= 4.7.2) 75 | Fake.Core.Trace (5.20.4) 76 | Fake.Core.Environment (>= 5.20.4) 77 | Fake.Core.FakeVar (>= 5.20.4) 78 | FSharp.Core (>= 4.7.2) 79 | Fake.Core.UserInput (5.20.4) 80 | FSharp.Core (>= 4.7.2) 81 | Fake.Core.Xml (5.20.4) 82 | Fake.Core.String (>= 5.20.4) 83 | FSharp.Core (>= 4.7.2) 84 | Fake.DotNet.AssemblyInfoFile (5.20.4) 85 | Fake.Core.Environment (>= 5.20.4) 86 | Fake.Core.String (>= 5.20.4) 87 | Fake.Core.Trace (>= 5.20.4) 88 | Fake.IO.FileSystem (>= 5.20.4) 89 | FSharp.Core (>= 4.7.2) 90 | Fake.DotNet.Cli (5.20.4) 91 | Fake.Core.Environment (>= 5.20.4) 92 | Fake.Core.Process (>= 5.20.4) 93 | Fake.Core.String (>= 5.20.4) 94 | Fake.Core.Trace (>= 5.20.4) 95 | Fake.DotNet.MSBuild (>= 5.20.4) 96 | Fake.DotNet.NuGet (>= 5.20.4) 97 | Fake.IO.FileSystem (>= 5.20.4) 98 | FSharp.Core (>= 4.7.2) 99 | Mono.Posix.NETStandard (>= 1.0) 100 | Newtonsoft.Json (>= 12.0.3) 101 | Fake.DotNet.MSBuild (5.20.4) 102 | BlackFox.VsWhere (>= 1.1) 103 | Fake.Core.Environment (>= 5.20.4) 104 | Fake.Core.Process (>= 5.20.4) 105 | Fake.Core.String (>= 5.20.4) 106 | Fake.Core.Trace (>= 5.20.4) 107 | Fake.IO.FileSystem (>= 5.20.4) 108 | FSharp.Core (>= 4.7.2) 109 | MSBuild.StructuredLogger (>= 2.1.176) 110 | Fake.DotNet.NuGet (5.20.4) 111 | Fake.Core.Environment (>= 5.20.4) 112 | Fake.Core.Process (>= 5.20.4) 113 | Fake.Core.SemVer (>= 5.20.4) 114 | Fake.Core.String (>= 5.20.4) 115 | Fake.Core.Tasks (>= 5.20.4) 116 | Fake.Core.Trace (>= 5.20.4) 117 | Fake.Core.Xml (>= 5.20.4) 118 | Fake.IO.FileSystem (>= 5.20.4) 119 | Fake.Net.Http (>= 5.20.4) 120 | FSharp.Core (>= 4.7.2) 121 | Newtonsoft.Json (>= 12.0.3) 122 | NuGet.Protocol (>= 5.6) 123 | Fake.DotNet.Paket (5.20.4) 124 | Fake.Core.Process (>= 5.20.4) 125 | Fake.Core.String (>= 5.20.4) 126 | Fake.Core.Trace (>= 5.20.4) 127 | Fake.DotNet.Cli (>= 5.20.4) 128 | Fake.IO.FileSystem (>= 5.20.4) 129 | FSharp.Core (>= 4.7.2) 130 | Fake.DotNet.Testing.Expecto (5.20.4) 131 | Fake.Core.Process (>= 5.20.4) 132 | Fake.Core.String (>= 5.20.4) 133 | Fake.Core.Trace (>= 5.20.4) 134 | Fake.IO.FileSystem (>= 5.20.4) 135 | Fake.Testing.Common (>= 5.20.4) 136 | FSharp.Core (>= 4.7.2) 137 | Fake.IO.FileSystem (5.20.4) 138 | Fake.Core.String (>= 5.20.4) 139 | FSharp.Core (>= 4.7.2) 140 | Fake.IO.Zip (5.20.4) 141 | Fake.Core.String (>= 5.20.4) 142 | Fake.IO.FileSystem (>= 5.20.4) 143 | FSharp.Core (>= 4.7.2) 144 | Fake.Net.Http (5.20.4) 145 | Fake.Core.Trace (>= 5.20.4) 146 | FSharp.Core (>= 4.7.2) 147 | Fake.Testing.Common (5.20.4) 148 | Fake.Core.Trace (>= 5.20.4) 149 | FSharp.Core (>= 4.7.2) 150 | Fake.Tools.Git (5.20.4) 151 | Fake.Core.Environment (>= 5.20.4) 152 | Fake.Core.Process (>= 5.20.4) 153 | Fake.Core.SemVer (>= 5.20.4) 154 | Fake.Core.String (>= 5.20.4) 155 | Fake.Core.Trace (>= 5.20.4) 156 | Fake.IO.FileSystem (>= 5.20.4) 157 | FSharp.Core (>= 4.7.2) 158 | FParsec (1.1.1) 159 | FSharp.Core (>= 4.3.4) 160 | FSharp.Control.Reactive (5.0.2) 161 | FSharp.Core (>= 4.7.2) 162 | System.Reactive (>= 5.0) 163 | FSharp.Core (4.7.2) 164 | Microsoft.Build (16.9) 165 | Microsoft.Build.Framework (>= 16.9) 166 | Microsoft.Win32.Registry (>= 4.3) 167 | System.Collections.Immutable (>= 5.0) 168 | System.Memory (>= 4.5.4) 169 | System.Reflection.Metadata (>= 1.6) 170 | System.Security.Principal.Windows (>= 4.7) 171 | System.Text.Encoding.CodePages (>= 4.0.1) 172 | System.Text.Json (>= 4.7) 173 | System.Threading.Tasks.Dataflow (>= 4.9) 174 | Microsoft.Build.Framework (16.9) 175 | System.Security.Permissions (>= 4.7) 176 | Microsoft.Build.Tasks.Core (16.9) 177 | Microsoft.Build.Framework (>= 16.9) 178 | Microsoft.Build.Utilities.Core (>= 16.9) 179 | Microsoft.Win32.Registry (>= 4.3) 180 | System.CodeDom (>= 4.4) 181 | System.Collections.Immutable (>= 5.0) 182 | System.Reflection.Metadata (>= 1.6) 183 | System.Reflection.TypeExtensions (>= 4.1) 184 | System.Resources.Extensions (>= 4.6) 185 | System.Runtime.InteropServices (>= 4.3) 186 | System.Security.Cryptography.Pkcs (>= 4.7) 187 | System.Security.Cryptography.Xml (>= 4.7) 188 | System.Security.Permissions (>= 4.7) 189 | System.Threading.Tasks.Dataflow (>= 4.9) 190 | Microsoft.Build.Utilities.Core (16.9) 191 | Microsoft.Build.Framework (>= 16.9) 192 | Microsoft.Win32.Registry (>= 4.3) 193 | System.Collections.Immutable (>= 5.0) 194 | System.Security.Permissions (>= 4.7) 195 | System.Text.Encoding.CodePages (>= 4.0.1) 196 | Microsoft.NETCore.Platforms (5.0.2) 197 | Microsoft.NETCore.Targets (2.1) 198 | Microsoft.Win32.Registry (5.0) 199 | System.Security.AccessControl (>= 5.0) 200 | System.Security.Principal.Windows (>= 5.0) 201 | Microsoft.Win32.SystemEvents (5.0) 202 | Microsoft.NETCore.Platforms (>= 5.0) 203 | Mono.Posix.NETStandard (1.0) 204 | MSBuild.StructuredLogger (2.1.404) 205 | Microsoft.Build (>= 16.4) 206 | Microsoft.Build.Framework (>= 16.4) 207 | Microsoft.Build.Tasks.Core (>= 16.4) 208 | Microsoft.Build.Utilities.Core (>= 16.4) 209 | Newtonsoft.Json (13.0.1) 210 | NuGet.Common (5.9.1) 211 | NuGet.Frameworks (>= 5.9.1) 212 | NuGet.Configuration (5.9.1) 213 | NuGet.Common (>= 5.9.1) 214 | System.Security.Cryptography.ProtectedData (>= 4.4) 215 | NuGet.Frameworks (5.9.1) 216 | NuGet.Packaging (5.9.1) 217 | Newtonsoft.Json (>= 9.0.1) 218 | NuGet.Configuration (>= 5.9.1) 219 | NuGet.Versioning (>= 5.9.1) 220 | System.Security.Cryptography.Cng (>= 5.0) 221 | System.Security.Cryptography.Pkcs (>= 5.0) 222 | NuGet.Protocol (5.9.1) 223 | NuGet.Packaging (>= 5.9.1) 224 | NuGet.Versioning (5.9.1) 225 | Octokit (0.50) 226 | System.CodeDom (5.0) 227 | System.Collections.Immutable (5.0) 228 | System.Drawing.Common (5.0.2) 229 | Microsoft.Win32.SystemEvents (>= 5.0) 230 | System.Formats.Asn1 (5.0) 231 | System.IO (4.3) 232 | Microsoft.NETCore.Platforms (>= 1.1) 233 | Microsoft.NETCore.Targets (>= 1.1) 234 | System.Runtime (>= 4.3) 235 | System.Text.Encoding (>= 4.3) 236 | System.Threading.Tasks (>= 4.3) 237 | System.Memory (4.5.4) 238 | System.Reactive (5.0) 239 | System.Reflection (4.3) 240 | Microsoft.NETCore.Platforms (>= 1.1) 241 | Microsoft.NETCore.Targets (>= 1.1) 242 | System.IO (>= 4.3) 243 | System.Reflection.Primitives (>= 4.3) 244 | System.Runtime (>= 4.3) 245 | System.Reflection.Metadata (1.6) 246 | System.Reflection.Primitives (4.3) 247 | Microsoft.NETCore.Platforms (>= 1.1) 248 | Microsoft.NETCore.Targets (>= 1.1) 249 | System.Runtime (>= 4.3) 250 | System.Reflection.TypeExtensions (4.7) 251 | System.Resources.Extensions (5.0) 252 | System.Runtime (4.3) 253 | Microsoft.NETCore.Platforms (>= 1.1) 254 | Microsoft.NETCore.Targets (>= 1.1) 255 | System.Runtime.Handles (4.3) 256 | Microsoft.NETCore.Platforms (>= 1.1) 257 | Microsoft.NETCore.Targets (>= 1.1) 258 | System.Runtime (>= 4.3) 259 | System.Runtime.InteropServices (4.3) 260 | Microsoft.NETCore.Platforms (>= 1.1) 261 | Microsoft.NETCore.Targets (>= 1.1) 262 | System.Reflection (>= 4.3) 263 | System.Reflection.Primitives (>= 4.3) 264 | System.Runtime (>= 4.3) 265 | System.Runtime.Handles (>= 4.3) 266 | System.Security.AccessControl (5.0) 267 | Microsoft.NETCore.Platforms (>= 5.0) 268 | System.Security.Principal.Windows (>= 5.0) 269 | System.Security.Cryptography.Cng (5.0) 270 | System.Formats.Asn1 (>= 5.0) 271 | System.Security.Cryptography.Pkcs (5.0.1) 272 | System.Formats.Asn1 (>= 5.0) 273 | System.Security.Cryptography.Cng (>= 5.0) 274 | System.Security.Cryptography.ProtectedData (5.0) 275 | System.Security.Cryptography.Xml (5.0) 276 | System.Security.Cryptography.Pkcs (>= 5.0) 277 | System.Security.Permissions (>= 5.0) 278 | System.Security.Permissions (5.0) 279 | System.Security.AccessControl (>= 5.0) 280 | System.Windows.Extensions (>= 5.0) 281 | System.Security.Principal.Windows (5.0) 282 | System.Text.Encoding (4.3) 283 | Microsoft.NETCore.Platforms (>= 1.1) 284 | Microsoft.NETCore.Targets (>= 1.1) 285 | System.Runtime (>= 4.3) 286 | System.Text.Encoding.CodePages (5.0) 287 | Microsoft.NETCore.Platforms (>= 5.0) 288 | System.Text.Json (5.0.2) 289 | System.Threading.Tasks (4.3) 290 | Microsoft.NETCore.Platforms (>= 1.1) 291 | Microsoft.NETCore.Targets (>= 1.1) 292 | System.Runtime (>= 4.3) 293 | System.Threading.Tasks.Dataflow (5.0) 294 | System.ValueTuple (4.5) 295 | System.Windows.Extensions (5.0) 296 | System.Drawing.Common (>= 5.0) 297 | -------------------------------------------------------------------------------- /src/BlackFox.CommandLine/CommandLine.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.CommandLine 2 | 3 | open Printf 4 | open System.Text 5 | 6 | module private EnvironmentDetection = 7 | open System 8 | 9 | let isMono = 10 | Type.GetType "Mono.Runtime" <> null 11 | 12 | let isUnixLike = 13 | match System.Environment.OSVersion.Platform with 14 | | PlatformID.Unix 15 | | PlatformID.MacOSX -> 16 | true 17 | | _ -> false 18 | 19 | /// A command line argument 20 | type CmdLineArgType = 21 | /// Normal command line argument that should be escaped 22 | | Normal of string 23 | /// Raw command line argument that won't receive any escaping 24 | | Raw of string 25 | 26 | override this.ToString() = 27 | match this with 28 | | Normal s -> s 29 | | Raw s -> s 30 | 31 | /// A command line 32 | type CmdLine = { 33 | Args: CmdLineArgType list 34 | } with 35 | /// An empty command line 36 | static member Empty: CmdLine = 37 | { Args = List.empty } 38 | 39 | /// Concatenate another command line 40 | member this.Concat (other : CmdLine): CmdLine = 41 | { this with Args = other.Args @ this.Args } 42 | 43 | static member Concat(cmdLines: CmdLine seq): CmdLine = 44 | { Args = List.concat (cmdLines |> Seq.rev |> Seq.map (fun c -> c.Args)) } 45 | 46 | /// Append a raw (Non escaped) argument 47 | member this.AppendRaw (value: string): CmdLine = 48 | { this with Args = Raw(value) :: this.Args } 49 | 50 | //----------------------------------------------------------------------- 51 | // Direct 52 | 53 | /// Append an argument 54 | member this.Append (value: string): CmdLine = 55 | { this with Args = Normal(value) :: this.Args } 56 | 57 | /// Append 2 arguments 58 | member private this.Append2 (value1: string) (value2: string): CmdLine = 59 | { this with Args = Normal(value2) :: Normal(value1) :: this.Args } 60 | 61 | /// Append an argument using printf-like syntax 62 | member this.Appendf<'T> (format: StringFormat<'T, CmdLine>): 'T = 63 | ksprintf (fun (value: string) -> this.Append value) format 64 | 65 | /// Append an argument prefixed by another 66 | member this.AppendPrefix (prefix: string) (value: string): CmdLine = 67 | this.Append2 prefix value 68 | 69 | /// Append an argument prefixed by another using printf-like syntax 70 | member this.AppendPrefixf<'T> (prefix: string) (format: StringFormat<'T, CmdLine>): 'T = 71 | ksprintf (fun (value: string) -> this.Append2 prefix value) format 72 | 73 | //----------------------------------------------------------------------- 74 | // If 75 | 76 | /// Append an argument if a condition is true 77 | member this.AppendIf (cond: bool) (value : string): CmdLine = 78 | match cond with 79 | | true -> this.Append value 80 | | false -> this 81 | 82 | /// Append an argument if a condition is true using printf-like syntax 83 | member this.AppendIff<'T> (cond: bool) (format: StringFormat<'T, CmdLine>): 'T = 84 | ksprintf (fun (value: string) -> this.AppendIf cond value) format 85 | 86 | /// Append an argument prefixed by another if a condition is true 87 | member this.AppendPrefixIf (cond: bool) (prefix: string) (value : string): CmdLine = 88 | match cond with 89 | | true -> this.Append2 prefix value 90 | | false -> this 91 | 92 | /// Append an argument prefixed by another if a condition is true using printf-like syntax 93 | member this.AppendPrefixIff<'T> (cond: bool) (prefix: string) (format: StringFormat<'T, CmdLine>): 'T = 94 | ksprintf (fun (value: string) -> this.AppendPrefixIf cond prefix value) format 95 | 96 | //----------------------------------------------------------------------- 97 | // Option 98 | 99 | /// Append an argument if the value is Some 100 | member this.AppendIfSome (value : string option): CmdLine = 101 | match value with 102 | | Some value -> this.Append value 103 | | None -> this 104 | 105 | /// Append an argument if the value is Some using printf-like syntax (With a single argument) 106 | member this.AppendIfSomef<'TArg> (format: StringFormat<'TArg -> string>) (value: 'TArg option): CmdLine = 107 | match value with 108 | | Some value -> 109 | let s = sprintf format value 110 | this.Append s 111 | | None -> this 112 | 113 | /// Append an argument prefixed by another if the value is Some 114 | member this.AppendPrefixIfSome (prefix: string) (value : string option): CmdLine = 115 | match value with 116 | | Some value -> this.Append2 prefix value 117 | | None -> this 118 | 119 | /// Append an argument prefixed by another if the value is Some using printf-like syntax (With a single argument) 120 | member this.AppendPrefixIfSomef<'TArg> (prefix: string) (format: StringFormat<'TArg -> string>) 121 | (value: 'TArg option): CmdLine = 122 | match value with 123 | | Some value -> 124 | let s = sprintf format value 125 | this.Append2 prefix s 126 | | None -> this 127 | 128 | //----------------------------------------------------------------------- 129 | // Sequence 130 | 131 | /// Append a sequence of arguments 132 | member this.AppendSeq (values: string seq): CmdLine = 133 | Seq.fold (fun (state: CmdLine) value -> state.Append value) this values 134 | 135 | /// Append a sequence of arguments using printf-like syntax (With a single argument) 136 | member this.AppendSeqf<'TArg> (format: StringFormat<'TArg -> string>) (values: 'TArg seq): CmdLine = 137 | Seq.fold (fun (state: CmdLine) value -> state.Append (sprintf format value)) this values 138 | 139 | /// Append a sequence of arguments each being prefixed 140 | member this.AppendPrefixSeq (prefix: string) (values: string seq): CmdLine = 141 | Seq.fold (fun (state: CmdLine) value -> state.AppendPrefix prefix value) this values 142 | 143 | /// Append a sequence of arguments each being prefixed using printf-like syntax (With a single argument) 144 | member this.AppendPrefixSeqf<'TArg> (prefix: string) (format: StringFormat<'TArg -> string>) (values: 'TArg seq) 145 | : CmdLine = 146 | Seq.fold (fun (state: CmdLine) value -> state.AppendPrefix prefix (sprintf format value)) this values 147 | 148 | //----------------------------------------------------------------------- 149 | // Not null or empty 150 | 151 | /// Append an argument if the value isn't null or empty 152 | member this.AppendIfNotNullOrEmpty (value: string): CmdLine = 153 | if System.String.IsNullOrEmpty value then 154 | this 155 | else 156 | this.Append value 157 | 158 | /// Append an argument if the value isn't null or empty using printf-like syntax (With a single string argument) 159 | member this.AppendIfNotNullOrEmptyf (format: StringFormat string>) (value: string): CmdLine = 160 | if System.String.IsNullOrEmpty value then 161 | this 162 | else 163 | this.Append (sprintf format value) 164 | 165 | /// Append an argument prefixed by another if the value isn't null or empty 166 | member this.AppendPrefixIfNotNullOrEmpty (prefix: string) (value: string): CmdLine = 167 | if System.String.IsNullOrEmpty value then 168 | this 169 | else 170 | this.AppendPrefix prefix value 171 | 172 | /// Append an argument prefixed by another if the value isn't null or empty using printf-like syntax 173 | /// (With a single string argument) 174 | member this.AppendPrefixIfNotNullOrEmptyf (prefix: string) (format: StringFormat string>) (value: string) 175 | : CmdLine = 176 | if System.String.IsNullOrEmpty value then 177 | this 178 | else 179 | this.AppendPrefix prefix (sprintf format value) 180 | 181 | //----------------------------------------------------------------------- 182 | // From 183 | 184 | static member FromList (values : string list): CmdLine = 185 | { Args = values |> List.map Normal |> List.rev } 186 | 187 | static member FromSeq (values : string seq): CmdLine = 188 | values |> Seq.toList |> CmdLine.FromList 189 | 190 | static member FromArray (values : string []): CmdLine = 191 | values |> Array.toList |> CmdLine.FromList 192 | 193 | //----------------------------------------------------------------------- 194 | // To 195 | 196 | /// Get a list of arguments from a command line (No escaping is applied) 197 | member this.ToList (): string list = 198 | let mutable result = [] 199 | for arg in this.Args do 200 | result <- (arg.ToString()) :: result 201 | result 202 | 203 | /// Get an array of arguments from a command line (No escaping is applied) 204 | member this.ToArray (): string [] = 205 | let mutable result = Array.zeroCreate this.Args.Length 206 | let mutable i = this.Args.Length - 1 207 | for arg in this.Args do 208 | result.[i] <- arg.ToString() 209 | i <- i - 1 210 | result 211 | 212 | member private this.Escape escapeFun = 213 | let builder = StringBuilder() 214 | this.Args |> List.rev |> Seq.iteri (fun i arg -> 215 | if (i <> 0) then builder.Append(' ') |> ignore 216 | match arg with 217 | | Normal arg -> escapeFun arg builder 218 | | Raw arg -> builder.Append(arg) |> ignore) 219 | 220 | builder.ToString() 221 | 222 | /// Convert a command line to string using the MSVCRT (Windows default) rules 223 | member this.ToStringForMsvcr (settings: MsvcrCommandLine.EscapeSettings): string = 224 | this.Escape (MsvcrCommandLine.escapeArgumentToBuilder settings) 225 | 226 | /// Convert a command line to string using the MSVCRT (Windows default) rules 227 | member this.ToStringForMonoUnix (settings: MonoUnixCommandLine.EscapeSettings): string = 228 | this.Escape (MonoUnixCommandLine.escapeArgumentToBuilder settings) 229 | 230 | /// 231 | /// Convert a command line to string as expected by 232 | /// 233 | override this.ToString (): string = 234 | if EnvironmentDetection.isMono && EnvironmentDetection.isUnixLike then 235 | this.ToStringForMonoUnix MonoUnixCommandLine.defaultEscapeSettings 236 | else 237 | this.ToStringForMsvcr MsvcrCommandLine.defaultEscapeSettings 238 | 239 | /// Handle command line arguments 240 | module CmdLine = 241 | /// An empty command line 242 | [] 243 | let empty: CmdLine = 244 | CmdLine.Empty 245 | 246 | /// Concatenate command lines 247 | [] 248 | let inline concat (cmdLines : CmdLine seq): CmdLine = 249 | CmdLine.Concat cmdLines 250 | 251 | /// Append a raw (Non escaped) argument to a command line 252 | [] 253 | let inline appendRaw value (cmdLine : CmdLine): CmdLine = 254 | cmdLine.AppendRaw value 255 | 256 | //----------------------------------------------------------------------- 257 | // Direct 258 | 259 | /// Append an argument to a command line 260 | [] 261 | let inline append (value : string) (cmdLine : CmdLine): CmdLine = 262 | cmdLine.Append value 263 | 264 | /// Append an argument to a command line using printf-like syntax 265 | [] 266 | let appendf<'T> (format: StringFormat<'T, CmdLine -> CmdLine>): 'T = 267 | ksprintf append format 268 | 269 | /// Append an argument prefixed by another 270 | [] 271 | let appendPrefix (prefix: string) (value: string) (cmdLine : CmdLine): CmdLine = 272 | cmdLine.AppendPrefix prefix value 273 | 274 | /// Append an argument prefixed by another using printf-like syntax 275 | [] 276 | let appendPrefixf<'T> (prefix: string) (format: StringFormat<'T, CmdLine -> CmdLine>): 'T = 277 | ksprintf (appendPrefix prefix) format 278 | 279 | //----------------------------------------------------------------------- 280 | // If 281 | 282 | /// Append an argument to a command line if a condition is true 283 | [] 284 | let inline appendIf (cond: bool) (value : string) (cmdLine : CmdLine): CmdLine = 285 | cmdLine.AppendIf cond value 286 | 287 | /// Append an argument to a command line if a condition is true using printf-like syntax 288 | [] 289 | let appendIff<'T> (cond: bool) (format: StringFormat<'T, CmdLine -> CmdLine>): 'T = 290 | ksprintf (appendIf cond) format 291 | 292 | /// Append an argument prefixed by another if a condition is true 293 | [] 294 | let inline appendPrefixIf (cond: bool) (prefix: string) (value : string) (cmdLine : CmdLine): CmdLine = 295 | cmdLine.AppendPrefixIf cond prefix value 296 | 297 | /// Append an argument prefixed by another if a condition is true using printf-like syntax 298 | [] 299 | let appendPrefixIff<'T> (cond: bool) (prefix: string) (format: StringFormat<'T, CmdLine -> CmdLine>): 'T = 300 | ksprintf (appendPrefixIf cond prefix) format 301 | 302 | //----------------------------------------------------------------------- 303 | // Option 304 | 305 | /// Append an argument to a command line if the value is Some 306 | [] 307 | let inline appendIfSome (value : string option) (cmdLine : CmdLine): CmdLine = 308 | cmdLine.AppendIfSome value 309 | 310 | /// Append an argument to a command line if the value is Some using printf-like syntax (With a single argument) 311 | [] 312 | let inline appendIfSomef<'TArg> (format: StringFormat<'TArg -> string>) (value: 'TArg option) (cmdLine: CmdLine) 313 | : CmdLine = 314 | cmdLine.AppendIfSomef format value 315 | 316 | /// Append an argument prefixed by another if the value is Some 317 | [] 318 | let inline appendPrefixIfSome (prefix: string) (value : string option) (cmdLine : CmdLine): CmdLine = 319 | cmdLine.AppendPrefixIfSome prefix value 320 | 321 | /// Append an argument prefixed by another if the value is Some using printf-like syntax (With a single argument) 322 | [] 323 | let inline appendPrefixIfSomef<'TArg> (prefix: string) (format: StringFormat<'TArg -> string>) (value: 'TArg option) 324 | (cmdLine : CmdLine): CmdLine = 325 | cmdLine.AppendPrefixIfSomef prefix format value 326 | 327 | //----------------------------------------------------------------------- 328 | // Sequence 329 | 330 | /// Append a sequence of arguments 331 | [] 332 | let inline appendSeq (values: string seq) (cmdLine : CmdLine): CmdLine = 333 | cmdLine.AppendSeq values 334 | 335 | /// Append a sequence of arguments using printf-like syntax (With a single argument) 336 | [] 337 | let inline appendSeqf<'TArg> (format: StringFormat<'TArg -> string>) (values: 'TArg seq) (cmdLine : CmdLine) 338 | : CmdLine = 339 | cmdLine.AppendSeqf format values 340 | 341 | /// Append a sequence of arguments each being prefixed 342 | [] 343 | let inline appendPrefixSeq (prefix: string) (values: string seq) (cmdLine : CmdLine): CmdLine = 344 | cmdLine.AppendPrefixSeq prefix values 345 | 346 | /// Append a sequence of arguments each being prefixed using printf-like syntax (With a single argument) 347 | [] 348 | let inline appendPrefixSeqf<'TArg> (prefix: string) (format: StringFormat<'TArg -> string>) (values: 'TArg seq) 349 | (cmdLine : CmdLine): CmdLine = 350 | cmdLine.AppendPrefixSeqf prefix format values 351 | 352 | //----------------------------------------------------------------------- 353 | // Not null or empty 354 | 355 | /// Append an argument if the value isn't null or empty 356 | [] 357 | let inline appendIfNotNullOrEmpty (value: string) (cmdLine : CmdLine): CmdLine = 358 | cmdLine.AppendIfNotNullOrEmpty value 359 | 360 | /// Append an argument if the value isn't null or empty using printf-like syntax (With a single string argument) 361 | [] 362 | let inline appendIfNotNullOrEmptyf (format: StringFormat string>) (value: string) (cmdLine : CmdLine) 363 | : CmdLine = 364 | cmdLine.AppendIfNotNullOrEmptyf format value 365 | 366 | /// Append an argument prefixed by another if the value isn't null or empty 367 | [] 368 | let inline appendPrefixIfNotNullOrEmpty (prefix: string) (value: string) (cmdLine : CmdLine): CmdLine = 369 | cmdLine.AppendPrefixIfNotNullOrEmpty prefix value 370 | 371 | /// Append an argument prefixed by another if the value isn't null or empty using printf-like syntax 372 | /// (With a single string argument) 373 | [] 374 | let inline appendPrefixIfNotNullOrEmptyf (prefix: string) (format: StringFormat string>) (value: string) 375 | (cmdLine : CmdLine): CmdLine = 376 | cmdLine.AppendPrefixIfNotNullOrEmptyf prefix format value 377 | 378 | //----------------------------------------------------------------------- 379 | // From 380 | 381 | /// Create a command line from a sequence of arguments 382 | [] 383 | let inline fromSeq (values : string seq) = 384 | CmdLine.FromSeq values 385 | 386 | /// Create a command line from a list of arguments 387 | [] 388 | let inline fromList (values : string list) = 389 | CmdLine.FromList values 390 | 391 | /// Create a command line from an array of arguments 392 | [] 393 | let inline fromArray (values : string []) = 394 | CmdLine.FromArray values 395 | 396 | //----------------------------------------------------------------------- 397 | // To 398 | 399 | /// Get a list of arguments from a command line (No escaping is applied) 400 | [] 401 | let inline toList (cmdLine : CmdLine): string list = 402 | cmdLine.ToList() 403 | 404 | /// Get an array of arguments from a command line (No escaping is applied) 405 | [] 406 | let inline toArray (cmdLine : CmdLine): string [] = 407 | cmdLine.ToArray() 408 | 409 | /// Convert a command line to string using the MSVCRT (Windows default) rules 410 | [] 411 | let inline toStringForMsvcr (settings: MsvcrCommandLine.EscapeSettings) (cmdLine : CmdLine): string = 412 | cmdLine.ToStringForMsvcr(settings) 413 | 414 | /// 415 | /// Convert a command line to string as expected by 416 | /// 417 | [] 418 | let inline toString (cmdLine : CmdLine): string = 419 | cmdLine.ToString() 420 | --------------------------------------------------------------------------------