├── .github
├── CODEOWNERS
└── workflows
│ └── main.yml
├── src
├── TestApp
│ ├── paket.references
│ ├── App.config
│ ├── BlackFox.MasterOfFoo.TestApp.fsproj
│ ├── AssemblyInfo.fs
│ └── Program.fs
├── BlackFox.MasterOfFoo.Tests
│ ├── paket.references
│ ├── Main.fs
│ ├── BlackFox.MasterOfFoo.Tests.fsproj
│ ├── Test.fs
│ └── InternalRepresentationTests.fs
├── BlackFox.MasterOfFoo
│ ├── Icon.png
│ ├── Icon.afdesign
│ ├── Core
│ │ └── Readme.md
│ ├── sformat.fs
│ ├── BlackFox.MasterOfFoo.fsproj
│ ├── MasterOfFoo.fs
│ ├── FormatSpecification.fs
│ ├── PrintableElement.fs
│ ├── PrintfEnv.fs
│ └── printf.fs
├── BlackFox.MasterOfFoo.Build
│ ├── Program.fs
│ ├── paket.references
│ ├── BlackFox.MasterOfFoo.Build.fsproj
│ └── Tasks.fs
├── Directory.Build.props
└── MasterOfFoo.sln
├── paket.cmd
├── paket.sh
├── global.json
├── .vscode
├── extensions.json
└── settings.json
├── .config
└── dotnet-tools.json
├── .editorconfig
├── paket.dependencies
├── Release Notes.md
├── .gitattributes
├── .gitignore
├── Readme.md
├── LICENSE
├── paket.lock
└── .paket
└── Paket.Restore.targets
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @vbfox
2 |
--------------------------------------------------------------------------------
/src/TestApp/paket.references:
--------------------------------------------------------------------------------
1 | FSharp.Core
2 | System.Data.SqlClient
3 |
--------------------------------------------------------------------------------
/paket.cmd:
--------------------------------------------------------------------------------
1 | @dotnet tool restore --verbosity quiet
2 |
3 | dotnet paket %*
4 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo.Tests/paket.references:
--------------------------------------------------------------------------------
1 | FSharp.Core
2 | Expecto
3 |
--------------------------------------------------------------------------------
/paket.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | dotnet tool restore --verbosity minimal
4 | dotnet paket $@
5 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "8.0.201",
4 | "rollForward": "minor"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbfox/MasterOfFoo/HEAD/src/BlackFox.MasterOfFoo/Icon.png
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/Icon.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbfox/MasterOfFoo/HEAD/src/BlackFox.MasterOfFoo/Icon.afdesign
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "Ionide.Ionide-fsharp",
4 | "Ionide.Ionide-FAKE",
5 | "Ionide.Ionide-Paket",
6 | "EditorConfig.EditorConfig"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/src/TestApp/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "paket": {
6 | "version": "8.0.3",
7 | "commands": [
8 | "paket"
9 | ]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo.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 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | insert_final_newline = true
7 | end_of_line = lf
8 |
9 | # Xml project files
10 | [*.{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 | [*.{yml,yaml}]
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo.Build/paket.references:
--------------------------------------------------------------------------------
1 | group build
2 | FSharp.Core
3 | BlackFox.Fake.BuildTask
4 |
5 | Fake.Core.Environment
6 | Fake.Core.Process
7 | Fake.Core.Trace
8 | Fake.Core.Target
9 | Fake.Core.ReleaseNotes
10 | Fake.Core.UserInput
11 | Fake.IO.FileSystem
12 | Fake.IO.Zip
13 | Fake.Tools.Git
14 | Fake.DotNet.Cli
15 | Fake.DotNet.AssemblyInfoFile
16 | Fake.DotNet.Testing.Expecto
17 | Fake.DotNet.Paket
18 | Fake.BuildServer.GitHubActions
19 | Fake.Api.GitHub
20 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo.Build/BlackFox.MasterOfFoo.Build.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | false
6 | net8.0
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo.Tests/Main.fs:
--------------------------------------------------------------------------------
1 | module Main
2 |
3 | open System.Threading
4 | open System.Globalization
5 |
6 | open Expecto
7 |
8 | []
9 | let main args =
10 | // Some tests use the .Net formating formats like $"{System.Math.PI:N3}" and their representation
11 | // depends on the culture
12 | Thread.CurrentThread.CurrentCulture <- CultureInfo.InvariantCulture
13 |
14 | let writeResults = TestResults.writeNUnitSummary "TestResults.xml"
15 | let config = defaultConfig.appendSummaryHandler writeResults
16 | runTestsInAssembly config args
17 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | // Configure glob patterns for excluding files and folders.
4 | "files.exclude": {
5 | "**/.git": true,
6 | "**/.svn": true,
7 | "**/.hg": true,
8 | "**/.DS_Store": true,
9 | "**/.fake": true,
10 | "**/.paket": true,
11 | "**/.vs": true
12 | },
13 | "search.exclude": {
14 | "**/node_modules": true,
15 | "**/bower_components": true,
16 | "**/packages": true,
17 | "**/paket-files": true,
18 | "**/artifacts": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo.Tests/BlackFox.MasterOfFoo.Tests.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0;net5.0;netcoreapp2.0
4 | Exe
5 | false
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/TestApp/BlackFox.MasterOfFoo.TestApp.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | Exe
6 | false
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/Core/Readme.md:
--------------------------------------------------------------------------------
1 | # Core
2 |
3 | Core is the part of MasterOfFoo that is extracted from the F# Compiler.
4 |
5 | # Extraction Process
6 |
7 | * Copy `printf.fs` from the compiler and fix namespace
8 | * Copy `FormatOptions` into `sformat.fs` and all new functions necessary. Keep `anyToStringForPrintf`.
9 | * Extract `PrintfEnv` into `PrintEnv.fs`
10 | * Move `FormatSpecifier` and `FormatFlags` to `FormatSpecification.fs`
11 | * Replace `PrintfEnv` signature with `abstract Write : PrintableElement -> unit` & fix thigns
12 | * `findNextFormatSpecifier` returns `PrintableElement`
13 | * The cornerstone is that ValueConverter should now return a function generating PrintableElement instances
14 | instead of strings.
15 |
16 | There are quite a few things to fix but the original `printf.fs` and `sformat.fs` are commited to serve as guide by
17 | diffing the current code and new versions before upgrade.
18 |
--------------------------------------------------------------------------------
/paket.dependencies:
--------------------------------------------------------------------------------
1 | source https://api.nuget.org/v3/index.json
2 | framework: net8.0,net5.0,netcoreapp2.0,netstandard2.0,net461
3 | storage:none
4 |
5 | nuget Expecto ~> 9
6 | nuget FSharp.Core
7 | nuget System.Data.SqlClient
8 |
9 | // Build infrastructure
10 | group build
11 | source https://api.nuget.org/v3/index.json
12 | storage: none
13 | framework: net8.0
14 |
15 | nuget FSharp.Core ~> 5
16 | nuget BlackFox.Fake.BuildTask
17 |
18 | nuget Octokit 0.48 // Fake.Api.GitHub 5.20.4 references 0.48+ but 0.50 has some incompatible Api changes
19 | nuget Fake.Core.Target
20 | nuget Fake.Core.Environment
21 | nuget Fake.Core.Process
22 | nuget Fake.Core.Trace
23 | nuget Fake.Core.ReleaseNotes
24 | nuget Fake.Core.UserInput
25 | nuget Fake.IO.FileSystem
26 | nuget Fake.IO.Zip
27 | nuget Fake.Tools.Git
28 | nuget Fake.DotNet.Cli
29 | nuget Fake.DotNet.AssemblyInfoFile
30 | nuget Fake.DotNet.Testing.Expecto
31 | nuget Fake.DotNet.Paket
32 | nuget Fake.BuildServer.GitHubActions
33 | nuget Fake.Api.GitHub
34 |
--------------------------------------------------------------------------------
/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 | An F# library to allow using printf style strings in more places.
10 | https://github.com/vbfox/MasterOfFoo
11 | Apache-2.0
12 | https://github.com/vbfox/MasterOfFoo.git
13 | F#;FSharp;printf
14 | vbfox
15 |
16 |
17 | true
18 | $(NoWarn);FS2003;NU1902;NU1903;NU1904;NETSDK1138
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/sformat.fs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2 |
3 | namespace BlackFox.MasterOfFoo.Core
4 |
5 | open System
6 | open System.Reflection
7 | open System.Globalization
8 |
9 | /// These are a typical set of options used to control structured formatting.
10 | []
11 | type internal FormatOptions =
12 | { FloatingPointFormat: string
13 | AttributeProcessor: (string -> (string * string) list -> bool -> unit)
14 | FormatProvider: IFormatProvider
15 | BindingFlags: BindingFlags
16 | PrintWidth: int
17 | PrintDepth: int
18 | PrintLength: int
19 | PrintSize: int
20 | ShowProperties: bool
21 | ShowIEnumerable: bool
22 | }
23 |
24 | static member Default =
25 | { FormatProvider = (CultureInfo.InvariantCulture :> IFormatProvider)
26 | AttributeProcessor= (fun _ _ _ -> ())
27 | BindingFlags = BindingFlags.Public
28 | FloatingPointFormat = "g10"
29 | PrintWidth = 80
30 | PrintDepth = 100
31 | PrintLength = 100
32 | PrintSize = 10000
33 | ShowProperties = false
34 | ShowIEnumerable = true
35 | }
36 |
37 | module internal Display =
38 | let anyToStringForPrintf (_options: FormatOptions) (_bindingFlags:BindingFlags) (value, _typValue: Type): string =
39 | sprintf "%A" value
40 |
--------------------------------------------------------------------------------
/.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-22.04
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@v6
19 | - uses: actions/setup-dotnet@v5
20 | with:
21 | dotnet-version: |
22 | 2
23 | 5
24 | - name: Restore packages
25 | run: ./paket.sh restore
26 | - name: Compile build script
27 | run: dotnet build src/BlackFox.MasterOfFoo.Build/BlackFox.MasterOfFoo.Build.fsproj
28 | - name: Build
29 | run: ./build.sh CI
30 | - name: Publish artifacts
31 | uses: actions/upload-artifact@v6
32 | with:
33 | path: artifacts/BlackFox.MasterOfFoo/Release/*.nupkg
34 | if-no-files-found: error
35 | compression-level: 0
36 |
37 | windows:
38 | runs-on: windows-latest
39 | env:
40 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
41 | DOTNET_CLI_TELEMETRY_OPTOUT: 1
42 | DOTNET_NOLOGO: 1
43 | PAKET_SKIP_RESTORE_TARGETS: true
44 | steps:
45 | - uses: actions/checkout@v6
46 | - uses: actions/setup-dotnet@v5
47 | with:
48 | dotnet-version: |
49 | 2
50 | 5
51 | - name: Restore packages
52 | run: ./paket.cmd restore
53 | - name: Compile build script
54 | run: dotnet build src/BlackFox.MasterOfFoo.Build/BlackFox.MasterOfFoo.Build.fsproj
55 | - name: Build
56 | run: ./build.cmd CI
57 |
--------------------------------------------------------------------------------
/src/TestApp/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | namespace ConsoleApplication1.AssemblyInfo
2 |
3 | open System.Reflection
4 | open System.Runtime.CompilerServices
5 | open System.Runtime.InteropServices
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | []
11 | []
12 | []
13 | []
14 | []
15 | []
16 | []
17 | []
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | []
23 |
24 | // The following GUID is for the ID of the typelib if this project is exposed to COM
25 | []
26 |
27 | // Version information for an assembly consists of the following four values:
28 | //
29 | // Major Version
30 | // Minor Version
31 | // Build Number
32 | // Revision
33 | //
34 | // You can specify all the values or you can default the Build and Revision Numbers
35 | // by using the '*' as shown below:
36 | // []
37 | []
38 | []
39 |
40 | do
41 | ()
--------------------------------------------------------------------------------
/Release Notes.md:
--------------------------------------------------------------------------------
1 | ### New in 2.1.1
2 |
3 | * Fix FSharp.Core dependency not being specified in the NuGet package for `net5.0` target but the dll being built
4 | against 8.0.0 anyway.
5 |
6 | ### New in 2.1.0
7 |
8 | * Synchronize with the latest FSharp.Core version and support the `%B` format specifier
9 |
10 | ### New in 2.0.0
11 |
12 | * Include the readme in the NuGet package
13 | * Build with 8.0.201 SDK
14 | * The new set of supported platforms is .NET Framework 4.6.1, .NET Standard 2.0 and .NET 5.0
15 | * Add support for interpolated strings
16 |
17 | ### New in 1.0.6
18 |
19 | * Make dependency ranges less strict (For `FSharp.Core` 6.x)
20 | * Build with 5.0.201 SDK
21 |
22 | ### New in 1.0.5
23 |
24 | * Specify the package license using SPDX
25 | * Build with 2.1.500 SDK
26 |
27 | ### New in 1.0.4
28 |
29 | * Include pdb files in package (With SourceLink)
30 | * Include XmlDoc in package
31 |
32 | ### New in 1.0.3
33 |
34 | * Remove ValueTuple from references
35 |
36 | ### New in 1.0.2
37 |
38 | * Larger FSharp.Core version choice
39 |
40 | ### New in 1.0.1
41 |
42 | * Target .Net 4.5 instead
43 |
44 | ### New in 1.0.0
45 |
46 | * The library now targets .Net 4.0, .Net standard 1.6 and .Net standard 2.0
47 |
48 | ### New in 0.2.1
49 |
50 | * NuGet package was accidentally dependent on FSharpLint.MSBuild
51 |
52 | ### New in 0.2.0
53 |
54 | * Fix `%A` text generated by `FormatAsPrintF()`, it's now printing the same thing as `printf` / `sprintf`.
55 |
56 | ### New in 0.1.2
57 |
58 | * Fix xmldoc (Bad name in zip and absent in nuget)
59 |
60 | ### New in 0.1.1
61 |
62 | * NuGet package dependency bugfix
63 |
64 | ### New in 0.1.0
65 |
66 | * First version
67 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/BlackFox.MasterOfFoo.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0;net5.0
4 | net461;netstandard2.0;net5.0
5 | true
6 | true
7 | Icon.png
8 | true
9 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
10 | Readme.md
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/MasterOfFoo.fs:
--------------------------------------------------------------------------------
1 | []
2 | []
3 | module BlackFox.MasterOfFoo.MasterOfFoo
4 |
5 | open System
6 | open System.Reflection
7 | open BlackFox.MasterOfFoo.Core
8 | open System.Collections.Concurrent
9 |
10 | module private FormatReflection =
11 | let capturesCache = ConcurrentDictionary()
12 |
13 | let inline getCaptures (format: Format<'Printer, 'State, 'Residue, 'Result>): obj[] =
14 | let genericType = format.GetType()
15 | let prop = capturesCache.GetOrAdd(genericType, fun t -> t.GetProperty("Captures"))
16 | if prop = null then
17 | null
18 | else
19 | prop.GetValue(format) :?> obj[]
20 |
21 | let captureTypesCache = ConcurrentDictionary()
22 |
23 | let inline getCaptureTypes (format: Format<'Printer, 'State, 'Residue, 'Result>): System.Type[] =
24 | let genericType = format.GetType()
25 | let prop = captureTypesCache.GetOrAdd(genericType, fun t -> t.GetProperty("CaptureTypes"))
26 | if prop = null then
27 | null
28 | else
29 | prop.GetValue(format) :?> Type[]
30 |
31 | open FormatReflection
32 |
33 | /// Take a format and a PrintfEnv builder to create a printf-like function
34 | let doPrintf (format: Format<'Printer, 'State, 'Residue, 'Result>) (envf: int -> #PrintfEnv<'State, 'Residue, 'Result>) =
35 | let cacheItem = Cache<_, _, _, _>.GetParser format
36 |
37 | match getCaptures format with
38 | | null ->
39 | // The ksprintf "...%d ...." arg path, producing a function
40 | let factory = cacheItem.GetCurriedPrinterFactory()
41 | let initial() = (envf cacheItem.BlockCount :> PrintfEnv<_,_,_>)
42 | factory.Invoke([], initial)
43 | | captures ->
44 | // The ksprintf $"...%d{3}...." path, running the steps straight away to produce a string
45 | let steps = cacheItem.GetStepsForCapturedFormat()
46 | let env = envf cacheItem.BlockCount :> PrintfEnv<_,_,_>
47 | let res = env.RunSteps(captures, getCaptureTypes format, steps)
48 | unbox res // prove 'T = 'Result
49 | //continuation res
50 |
51 | /// Take a format and a PrintfEnv to create a printf-like function
52 | let doPrintfFromEnv (format: Format<'Printer, 'State, 'Residue, 'Result>) (env: #PrintfEnv<_,_,_>) =
53 | doPrintf format (fun _ -> env)
54 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/src/MasterOfFoo.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio 15
3 | VisualStudioVersion = 15.0.26403.7
4 | MinimumVisualStudioVersion = 15.0.26403.7
5 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "BlackFox.MasterOfFoo.TestApp", "TestApp\BlackFox.MasterOfFoo.TestApp.fsproj", "{4C628A7D-76EC-4673-A45D-DAE6FC060784}"
6 | EndProject
7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E5E77D3C-F0EE-4EB3-B813-B181020563E0}"
8 | ProjectSection(SolutionItems) = preProject
9 | ..\paket.dependencies = ..\paket.dependencies
10 | ..\Readme.md = ..\Readme.md
11 | EndProjectSection
12 | EndProject
13 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "BlackFox.MasterOfFoo.Tests", "BlackFox.MasterOfFoo.Tests\BlackFox.MasterOfFoo.Tests.fsproj", "{B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}"
14 | EndProject
15 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "BlackFox.MasterOfFoo", "BlackFox.MasterOfFoo\BlackFox.MasterOfFoo.fsproj", "{80E6D22C-3DEC-4929-B4BF-68E3A3F28039}"
16 | EndProject
17 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.MasterOfFoo.Build", "BlackFox.MasterOfFoo.Build\BlackFox.MasterOfFoo.Build.fsproj", "{C1EEC1BE-433F-42C9-A2B2-B86AEB877608}"
18 | EndProject
19 | Global
20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
21 | Debug|Any CPU = Debug|Any CPU
22 | Release|Any CPU = Release|Any CPU
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {4C628A7D-76EC-4673-A45D-DAE6FC060784}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {4C628A7D-76EC-4673-A45D-DAE6FC060784}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {4C628A7D-76EC-4673-A45D-DAE6FC060784}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {4C628A7D-76EC-4673-A45D-DAE6FC060784}.Release|Any CPU.Build.0 = Release|Any CPU
29 | {B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {80E6D22C-3DEC-4929-B4BF-68E3A3F28039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {80E6D22C-3DEC-4929-B4BF-68E3A3F28039}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {80E6D22C-3DEC-4929-B4BF-68E3A3F28039}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {80E6D22C-3DEC-4929-B4BF-68E3A3F28039}.Release|Any CPU.Build.0 = Release|Any CPU
37 | {C1EEC1BE-433F-42C9-A2B2-B86AEB877608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {C1EEC1BE-433F-42C9-A2B2-B86AEB877608}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {C1EEC1BE-433F-42C9-A2B2-B86AEB877608}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {C1EEC1BE-433F-42C9-A2B2-B86AEB877608}.Release|Any CPU.Build.0 = Release|Any CPU
41 | EndGlobalSection
42 | GlobalSection(SolutionProperties) = preSolution
43 | HideSolutionNode = FALSE
44 | EndGlobalSection
45 | GlobalSection(ExtensibilityGlobals) = postSolution
46 | SolutionGuid = {D7E9F6C5-DE27-456B-BAFD-3A445D56D74A}
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/FormatSpecification.fs:
--------------------------------------------------------------------------------
1 | namespace BlackFox.MasterOfFoo
2 |
3 | open System
4 |
5 | []
6 | type FormatFlags =
7 | | None = 0
8 | | LeftJustify = 1
9 | | PadWithZeros = 2
10 | | PlusForPositives = 4
11 | | SpaceForPositives = 8
12 |
13 | module internal FormatFlagsHelpers =
14 | let inline hasFlag flags (expected: FormatFlags) = (flags &&& expected) = expected
15 | let inline isLeftJustify flags = hasFlag flags FormatFlags.LeftJustify
16 | let inline isPadWithZeros flags = hasFlag flags FormatFlags.PadWithZeros
17 | let inline isPlusForPositives flags = hasFlag flags FormatFlags.PlusForPositives
18 | let inline isSpaceForPositives flags = hasFlag flags FormatFlags.SpaceForPositives
19 |
20 | open FormatFlagsHelpers
21 |
22 | module internal FormatSpecifierConstants =
23 | /// Used for width and precision to denote that user has specified '*' flag
24 | []
25 | let StarValue = -1
26 |
27 | /// Used for width and precision to denote that corresponding value was omitted in format string
28 | []
29 | let NotSpecifiedValue = -2
30 |
31 | open FormatSpecifierConstants
32 |
33 | []
34 | []
35 | type FormatSpecifier =
36 | {
37 | TypeChar: char
38 | Precision: int
39 | Width: int
40 | Flags: FormatFlags
41 | InteropHoleDotNetFormat: string voption
42 | }
43 | member spec.IsStarPrecision = (spec.Precision = StarValue)
44 |
45 | member spec.IsPrecisionSpecified = (spec.Precision <> NotSpecifiedValue)
46 |
47 | member spec.IsStarWidth = (spec.Width = StarValue)
48 |
49 | member spec.IsWidthSpecified = (spec.Width <> NotSpecifiedValue)
50 |
51 | member spec.ArgCount =
52 | let n =
53 | if spec.TypeChar = 'a' then 2
54 | elif spec.IsStarWidth || spec.IsStarPrecision then
55 | if spec.IsStarWidth = spec.IsStarPrecision then 3
56 | else 2
57 | else 1
58 |
59 | let n = if spec.TypeChar = '%' then n - 1 else n
60 |
61 | assert (n <> 0)
62 |
63 | n
64 |
65 | override spec.ToString() =
66 | let valueOf n = match n with StarValue -> "*" | NotSpecifiedValue -> "-" | n -> n.ToString()
67 | System.String.Format
68 | (
69 | "'{0}', Precision={1}, Width={2}, Flags={3}",
70 | spec.TypeChar,
71 | (valueOf spec.Precision),
72 | (valueOf spec.Width),
73 | spec.Flags
74 | )
75 |
76 | member spec.IsDecimalFormat =
77 | spec.TypeChar = 'M'
78 |
79 | member spec.GetPadAndPrefix allowZeroPadding =
80 | let padChar = if allowZeroPadding && isPadWithZeros spec.Flags then '0' else ' ';
81 | let prefix =
82 | if isPlusForPositives spec.Flags then "+"
83 | elif isSpaceForPositives spec.Flags then " "
84 | else ""
85 | padChar, prefix
86 |
87 | member spec.IsGFormat =
88 | spec.IsDecimalFormat || System.Char.ToLower(spec.TypeChar) = 'g'
89 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/PrintableElement.fs:
--------------------------------------------------------------------------------
1 | namespace BlackFox.MasterOfFoo
2 |
3 | open System
4 | open FormatSpecifierConstants
5 |
6 | /// The type of an element passed to a PrintfEnv.
7 | type PrintableElementType =
8 | /// A string created by the engine, only used in a few edge cases
9 | | MadeByEngine = 0uy
10 | /// A string coming directly from the format string
11 | | Direct = 1uy
12 | /// A format specifier and his corresponding value(s)
13 | | FromFormatSpecifier = 2uy
14 |
15 | /// An element passed to PrintfEnv for writing.
16 | type PrintableElement
17 | (
18 | printer: unit -> string,
19 | value: obj,
20 | type': PrintableElementType,
21 | valueType: Type,
22 | spec: FormatSpecifier,
23 | starWidth: int,
24 | starPrecision: int
25 | ) =
26 |
27 | new(s: string, type': PrintableElementType) =
28 | PrintableElement(
29 | Unchecked.defaultof string>,
30 | s,
31 | type',
32 | Unchecked.defaultof,
33 | Unchecked.defaultof,
34 | NotSpecifiedValue,
35 | NotSpecifiedValue)
36 |
37 | /// The type of element (From a format specifier or directly from the string)
38 | member _.ElementType with get() = type'
39 |
40 | /// The value passed as parameter, of type ValueType
41 | member _.Value with get() = value
42 |
43 | /// The type of the value
44 | member _.ValueType
45 | with get() =
46 | match type' with
47 | | PrintableElementType.FromFormatSpecifier -> valueType
48 | | _ -> typeof
49 |
50 | /// The format specification for format specifiers
51 | member _.Specifier with get() = if Object.ReferenceEquals(spec, null) then None else Some(spec)
52 |
53 | // The .Net format string for format specifiers
54 |
55 | /// The width if specified via another parameter as in "%*i"
56 | member _.StarWidth with get() = match starWidth with NotSpecifiedValue -> None | x -> Some(x)
57 |
58 | /// The precision if specified via another parameter as in "%.*f"
59 | member _.StarPrecision with get() = match starPrecision with NotSpecifiedValue -> None | x -> Some(x)
60 |
61 | override x.ToString () =
62 | // The .Net format is handled differently as it's stored in the Specifier but not present
63 | // in it's ToString() representation
64 | let dotnetFormat =
65 | match x.Specifier with
66 | | Some x ->
67 | match x.InteropHoleDotNetFormat with
68 | | ValueSome x -> Some x
69 | | ValueNone -> None
70 | | None -> None
71 | let dotnetFormatAddition =
72 | match dotnetFormat with
73 | | Some x -> sprintf ", dotnetFormat: '%s'" x
74 | | None -> ""
75 |
76 | sprintf
77 | "value: %A, type: %A, valueType: %s, spec: %s%s, starWidth: %s, starPrecision: %s, AsPrintF: %s"
78 | value
79 | type'
80 | (x.ValueType.FullName)
81 | (match x.Specifier with Some x -> x.ToString() | None -> "")
82 | dotnetFormatAddition
83 | (match x.StarWidth with Some x -> x.ToString() | None -> "")
84 | (match x.StarPrecision with Some x -> x.ToString() | None -> "")
85 | (x.FormatAsPrintF())
86 |
87 | /// Get the string representation that printf would have normally generated
88 | member _.FormatAsPrintF() =
89 | match type' with
90 | | PrintableElementType.FromFormatSpecifier -> printer ()
91 | | _ -> value :?> string
92 |
93 | member internal _.IsNullOrEmpty with get() =
94 | match type' with
95 | | PrintableElementType.FromFormatSpecifier -> false
96 | | _ -> String.IsNullOrEmpty(value :?> string)
97 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo.Tests/Test.fs:
--------------------------------------------------------------------------------
1 | module BlackFox.MasterOfFoo.DoPrintfTests
2 |
3 | open Expecto
4 | open System.Text
5 |
6 | open BlackFox.MasterOfFoo
7 |
8 | type TestEnv() =
9 | inherit PrintfEnv()
10 | let buf = StringBuilder()
11 | override __.Finalize() = buf.ToString ()
12 | override __.Write(s : PrintableElement) = ignore(buf.Append(s.FormatAsPrintF()))
13 | override __.WriteT(s : string) = ignore(buf.Append(s))
14 |
15 | let testprintf (format: Printf.StringFormat<'a>) = doPrintf format (fun _ -> TestEnv())
16 | let coreprintf = FSharp.Core.Printf.sprintf
17 |
18 | type Discriminated = |A of string | B of int
19 |
20 | let testStr = "Foo"
21 | let testInt = 42
22 |
23 | type MyFormatable() =
24 | interface System.IFormattable with
25 | member __.ToString(format: string, _formatProvider: System.IFormatProvider) =
26 | $"MyFormatable(%s{format})"
27 |
28 | override _.ToString() = "MyFormatable"
29 |
30 | let tests = [
31 | test "simple string" {
32 | Expect.equal
33 | (coreprintf "Foo")
34 | (testprintf "Foo")
35 | "Foo"
36 | }
37 |
38 | test "simple string interpolation" {
39 | Expect.equal
40 | (coreprintf $"Foo")
41 | (testprintf $"Foo")
42 | "Foo"
43 | }
44 |
45 | test "string format" {
46 | Expect.equal
47 | (coreprintf "%s" "Foo")
48 | (testprintf "%s" "Foo")
49 | "%s"
50 | }
51 |
52 | test "string format width" {
53 | Expect.equal
54 | (coreprintf "%1s" "Foo")
55 | (testprintf "%1s" "Foo")
56 | "%1s"
57 | Expect.equal
58 | (coreprintf "%5s" "Foo")
59 | (testprintf "%5s" "Foo")
60 | "%5s"
61 | }
62 |
63 | test "string untyped interpolation" {
64 | Expect.equal
65 | (coreprintf $"""{"Foo"}""")
66 | (testprintf $"""{"Foo"}""")
67 | "%s"
68 | }
69 |
70 | test "string typed interpolation" {
71 | Expect.equal
72 | (coreprintf $"""%s{"Foo"}""")
73 | (testprintf $"""%s{"Foo"}""")
74 | "%s"
75 | }
76 |
77 | test "int format %i" {
78 | Expect.equal
79 | (coreprintf "%i" 5)
80 | (testprintf "%i" 5)
81 | "%i"
82 | }
83 |
84 | test "int format %B" {
85 | Expect.equal
86 | (coreprintf "%B" 5)
87 | (testprintf "%B" 5)
88 | "%B"
89 | }
90 |
91 | test "int untyped interpolation" {
92 | Expect.equal
93 | (coreprintf $"{5}")
94 | (testprintf $"{5}")
95 | "{5}"
96 | }
97 |
98 | test "int untyped interpolation .NET format" {
99 | Expect.equal
100 | (coreprintf $"{System.Math.PI:N3}")
101 | (testprintf $"{System.Math.PI:N3}")
102 | "{System.Math.PI:N3}"
103 | }
104 |
105 | test "int typed interpolation" {
106 | Expect.equal
107 | (coreprintf $"%i{5}")
108 | (testprintf $"%i{5}")
109 | "%i"
110 | }
111 |
112 | test "int format width" {
113 | Expect.equal
114 | (coreprintf "%1i" 5)
115 | (testprintf "%1i" 5)
116 | "%1i"
117 | Expect.equal
118 | (coreprintf "%5i" 5)
119 | (testprintf "%5i" 5)
120 | "%5i"
121 | }
122 |
123 | test "A format" {
124 | Expect.equal
125 | (coreprintf "%A %A %A %A %A" "Foo" 5 (A("Foo")) (B(42)) System.ConsoleColor.Red)
126 | (testprintf "%A %A %A %A %A" "Foo" 5 (A("Foo")) (B(42)) System.ConsoleColor.Red)
127 | "%A %A %A %A %A"
128 | }
129 |
130 | test "custom untyped interpolation" {
131 | Expect.equal
132 | (coreprintf $"{MyFormatable()}")
133 | (testprintf $"{MyFormatable()}")
134 | "{MyFormatable()}"
135 | }
136 |
137 | test "custom untyped interpolation .NET format" {
138 | Expect.equal
139 | (coreprintf $"{MyFormatable():HelloWorld}")
140 | (testprintf $"{MyFormatable():HelloWorld}")
141 | "{MyFormatable():HelloWorld}"
142 | }
143 |
144 | test "custom untyped interpolation .NET format unusual characters" {
145 | // Using double backticks to escape the format string if it contains spaces for example
146 | Expect.equal
147 | (coreprintf $"{MyFormatable():``Hello World``}")
148 | (testprintf $"{MyFormatable():``Hello World``}")
149 | "{MyFormatable():``Hello World``}"
150 | }
151 | ]
152 |
153 | []
154 | let test = testList "Tests" tests
155 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | [Xx]64/
19 | [Xx]86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 |
24 | # Visual Studio 2015 cache/options directory
25 | .vs/
26 | # Uncomment if you have tasks that create the project's static files in wwwroot
27 | #wwwroot/
28 |
29 | # MSTest test Results
30 | [Tt]est[Rr]esult*/
31 | [Bb]uild[Ll]og.*
32 |
33 | # NUNIT
34 | *.VisualState.xml
35 | TestResult.xml
36 |
37 | # Build Results of an ATL Project
38 | [Dd]ebugPS/
39 | [Rr]eleasePS/
40 | dlldata.c
41 |
42 | # DNX
43 | project.lock.json
44 | artifacts/
45 |
46 | *_i.c
47 | *_p.c
48 | *_i.h
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.tmp_proj
63 | *.log
64 | *.vspscc
65 | *.vssscc
66 | .builds
67 | *.pidb
68 | *.svclog
69 | *.scc
70 |
71 | # Chutzpah Test files
72 | _Chutzpah*
73 |
74 | # Visual C++ cache files
75 | ipch/
76 | *.aps
77 | *.ncb
78 | *.opendb
79 | *.opensdf
80 | *.sdf
81 | *.cachefile
82 | *.VC.db
83 |
84 | # Visual Studio profiler
85 | *.psess
86 | *.vsp
87 | *.vspx
88 | *.sap
89 |
90 | # TFS 2012 Local Workspace
91 | $tf/
92 |
93 | # Guidance Automation Toolkit
94 | *.gpState
95 |
96 | # ReSharper is a .NET coding add-in
97 | _ReSharper*/
98 | *.[Rr]e[Ss]harper
99 | *.DotSettings.user
100 |
101 | # JustCode is a .NET coding add-in
102 | .JustCode
103 |
104 | # TeamCity is a build add-in
105 | _TeamCity*
106 |
107 | # DotCover is a Code Coverage Tool
108 | *.dotCover
109 |
110 | # NCrunch
111 | _NCrunch_*
112 | .*crunch*.local.xml
113 | nCrunchTemp_*
114 |
115 | # MightyMoose
116 | *.mm.*
117 | AutoTest.Net/
118 |
119 | # Web workbench (sass)
120 | .sass-cache/
121 |
122 | # Installshield output folder
123 | [Ee]xpress/
124 |
125 | # DocProject is a documentation generator add-in
126 | DocProject/buildhelp/
127 | DocProject/Help/*.HxT
128 | DocProject/Help/*.HxC
129 | DocProject/Help/*.hhc
130 | DocProject/Help/*.hhk
131 | DocProject/Help/*.hhp
132 | DocProject/Help/Html2
133 | DocProject/Help/html
134 |
135 | # Click-Once directory
136 | publish/
137 |
138 | # Publish Web Output
139 | *.[Pp]ublish.xml
140 | *.azurePubxml
141 |
142 | # TODO: Un-comment the next line if you do not want to checkin
143 | # your web deploy settings because they may include unencrypted
144 | # passwords
145 | #*.pubxml
146 | *.publishproj
147 |
148 | # NuGet Packages
149 | *.nupkg
150 | # The packages folder can be ignored because of Package Restore
151 | **/packages/*
152 | # Uncomment if necessary however generally it will be regenerated when needed
153 | #!**/packages/repositories.config
154 | # NuGet v3's project.json files produces more ignoreable files
155 | *.nuget.props
156 | *.nuget.targets
157 |
158 | # Microsoft Azure Build Output
159 | csx/
160 | *.build.csdef
161 |
162 | # Microsoft Azure Emulator
163 | ecf/
164 | rcf/
165 |
166 | # Microsoft Azure ApplicationInsights config file
167 | ApplicationInsights.config
168 |
169 | # Windows Store app package directory
170 | AppPackages/
171 | BundleArtifacts/
172 |
173 | # Visual Studio cache files
174 | # files ending in .cache can be ignored
175 | *.[Cc]ache
176 | # but keep track of directories ending in .cache
177 | !*.[Cc]ache/
178 |
179 | # Others
180 | ClientBin/
181 | [Ss]tyle[Cc]op.*
182 | ~$*
183 | *~
184 | *.dbmdl
185 | *.dbproj.schemaview
186 | *.pfx
187 | *.publishsettings
188 | node_modules/
189 | orleans.codegen.cs
190 |
191 | # RIA/Silverlight projects
192 | Generated_Code/
193 |
194 | # Backup & report files from converting an old project file
195 | # to a newer Visual Studio version. Backup files are not needed,
196 | # because we have git ;-)
197 | _UpgradeReport_Files/
198 | Backup*/
199 | UpgradeLog*.XML
200 | UpgradeLog*.htm
201 |
202 | # SQL Server files
203 | *.mdf
204 | *.ldf
205 |
206 | # Business Intelligence projects
207 | *.rdl.data
208 | *.bim.layout
209 | *.bim_*.settings
210 |
211 | # Microsoft Fakes
212 | FakesAssemblies/
213 |
214 | # GhostDoc plugin setting file
215 | *.GhostDoc.xml
216 |
217 | # Node.js Tools for Visual Studio
218 | .ntvs_analysis.dat
219 |
220 | # Visual Studio 6 build log
221 | *.plg
222 |
223 | # Visual Studio 6 workspace options file
224 | *.opt
225 |
226 | # Visual Studio LightSwitch build output
227 | **/*.HTMLClient/GeneratedArtifacts
228 | **/*.DesktopClient/GeneratedArtifacts
229 | **/*.DesktopClient/ModelManifest.xml
230 | **/*.Server/GeneratedArtifacts
231 | **/*.Server/ModelManifest.xml
232 | _Pvt_Extensions
233 |
234 | # LightSwitch generated files
235 | GeneratedArtifacts/
236 | ModelManifest.xml
237 |
238 | # Paket dependency manager
239 | .paket/paket.exe
240 | paket-files/
241 |
242 | # FAKE - F# Make
243 | .fake/
244 |
245 | launchSettings.json
246 | .ionide/
247 | .idea/
248 |
249 | TestResults.xml
250 |
251 | # MSBuild binary logs
252 | *.binlog
253 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/PrintfEnv.fs:
--------------------------------------------------------------------------------
1 | namespace BlackFox.MasterOfFoo
2 |
3 | open System
4 |
5 | []
6 | /// Represents one step in the execution of a format string
7 | type internal Step =
8 | | StepWithArg of prefix: PrintableElement * conv: (obj -> PrintableElement)
9 | | StepWithTypedArg of prefix: PrintableElement * conv: (obj -> Type -> PrintableElement)
10 | | StepString of prefix: PrintableElement
11 | | StepLittleT of prefix: PrintableElement
12 | | StepLittleA of prefix: PrintableElement
13 | | StepStar1 of prefix: PrintableElement * conv: (obj -> int -> PrintableElement)
14 | | StepPercentStar1 of prefix: PrintableElement
15 | | StepStar2 of prefix: PrintableElement * conv: (obj -> int -> int -> PrintableElement)
16 | | StepPercentStar2 of prefix: PrintableElement
17 |
18 | // Count the number of string fragments in a sequence of steps
19 | static member BlockCount(steps: Step[]) =
20 | let mutable count = 0
21 | for step in steps do
22 | match step with
23 | | StepWithArg (prefix, _conv) ->
24 | if not (prefix.IsNullOrEmpty) then count <- count + 1
25 | count <- count + 1
26 | | StepWithTypedArg (prefix, _conv) ->
27 | if not (prefix.IsNullOrEmpty) then count <- count + 1
28 | count <- count + 1
29 | | StepString prefix ->
30 | if not (prefix.IsNullOrEmpty) then count <- count + 1
31 | | StepLittleT(prefix) ->
32 | if not (prefix.IsNullOrEmpty) then count <- count + 1
33 | count <- count + 1
34 | | StepLittleA(prefix) ->
35 | if not (prefix.IsNullOrEmpty) then count <- count + 1
36 | count <- count + 1
37 | | StepStar1(prefix, _conv) ->
38 | if not (prefix.IsNullOrEmpty) then count <- count + 1
39 | count <- count + 1
40 | | StepPercentStar1(prefix) ->
41 | if not (prefix.IsNullOrEmpty) then count <- count + 1
42 | count <- count + 1
43 | | StepStar2(prefix, _conv) ->
44 | if not (prefix.IsNullOrEmpty) then count <- count + 1
45 | count <- count + 1
46 | | StepPercentStar2(prefix) ->
47 | if not (prefix.IsNullOrEmpty) then count <- count + 1
48 | count <- count + 1
49 | count
50 |
51 | /// Abstracts generated printer from the details of particular environment: how to write text, how to produce results etc...
52 | []
53 | type PrintfEnv<'State, 'Residue, 'Result>(state: 'State) =
54 | member _.State = state
55 |
56 | /// Create the final result for this printer
57 | abstract Finalize: unit -> 'Result
58 |
59 | /// Write an element from the format string (Raw text or format specifier) to the printer
60 | abstract Write: PrintableElement -> unit
61 |
62 | /// Write the result of a '%t' format. If this is a string it is written. If it is a 'unit' value
63 | /// the side effect has already happened
64 | abstract WriteT: 'Residue -> unit
65 |
66 | member env.WriteSkipEmpty(s: PrintableElement) =
67 | if not (s.IsNullOrEmpty) then
68 | env.Write s
69 |
70 | member internal env.RunSteps (args: obj[], argTys: Type[], steps: Step[]) =
71 | let mutable argIndex = 0
72 | let mutable tyIndex = 0
73 |
74 | for step in steps do
75 | match step with
76 | | StepWithArg (prefix, conv) ->
77 | env.WriteSkipEmpty prefix
78 | let arg = args.[argIndex]
79 | argIndex <- argIndex + 1
80 | env.Write(conv arg)
81 |
82 | | StepWithTypedArg (prefix, conv) ->
83 | env.WriteSkipEmpty prefix
84 | let arg = args.[argIndex]
85 | let argTy = argTys.[tyIndex]
86 | argIndex <- argIndex + 1
87 | tyIndex <- tyIndex + 1
88 | env.Write(conv arg argTy)
89 |
90 | | StepString prefix ->
91 | env.WriteSkipEmpty prefix
92 |
93 | | StepLittleT(prefix) ->
94 | env.WriteSkipEmpty prefix
95 | let farg = args.[argIndex]
96 | argIndex <- argIndex + 1
97 | let f = farg :?> ('State -> 'Residue)
98 | env.WriteT(f env.State)
99 |
100 | | StepLittleA(prefix) ->
101 | env.WriteSkipEmpty prefix
102 | let farg = args.[argIndex]
103 | argIndex <- argIndex + 1
104 | let arg = args.[argIndex]
105 | argIndex <- argIndex + 1
106 | let f = farg :?> ('State -> obj -> 'Residue)
107 | env.WriteT(f env.State arg)
108 |
109 | | StepStar1(prefix, conv) ->
110 | env.WriteSkipEmpty prefix
111 | let star1 = args.[argIndex] :?> int
112 | argIndex <- argIndex + 1
113 | let arg1 = args.[argIndex]
114 | argIndex <- argIndex + 1
115 | env.Write (conv arg1 star1)
116 |
117 | | StepPercentStar1(prefix) ->
118 | argIndex <- argIndex + 1
119 | env.WriteSkipEmpty prefix
120 | env.Write(PrintableElement("%", PrintableElementType.MadeByEngine))
121 |
122 | | StepStar2(prefix, conv) ->
123 | env.WriteSkipEmpty prefix
124 | let star1 = args.[argIndex] :?> int
125 | argIndex <- argIndex + 1
126 | let star2 = args.[argIndex] :?> int
127 | argIndex <- argIndex + 1
128 | let arg1 = args.[argIndex]
129 | argIndex <- argIndex + 1
130 | env.Write (conv arg1 star1 star2)
131 |
132 | | StepPercentStar2(prefix) ->
133 | env.WriteSkipEmpty prefix
134 | argIndex <- argIndex + 2
135 | env.Write(PrintableElement("%", PrintableElementType.MadeByEngine))
136 |
137 | env.Finalize()
138 |
139 | /// This is the new name of Finalize in the new version of FSharp.Core
140 | ///
141 | /// We can't rename our method without breaking customers code but changing the name everywhere in printf.fs is
142 | /// also a pain. So as an alternative this internal version, made only to ease porting was introduced.
143 | member internal env.Finish () =
144 | env.Finalize()
145 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # MasterOfFoo
2 |
3 | 
4 |
5 |
6 | [](https://github.com/vbfox/MasterOfFoo/actions/workflows/main.yml?query=branch%3Amaster)
7 | [](https://www.nuget.org/packages/BlackFox.MasterOfFoo)
8 |
9 | A library to allow using `printf` style strings in more places.
10 |
11 | The code is essentially an extracted version of [`printf.fs`][printf_fs] where the environment can not only decide
12 | what to do with the final blocks that compose the string (printf put them on the console, sprintf in a buffer, ...)
13 | but also what to do with the parameters passed for each format specifier.
14 |
15 | ## Sample usage
16 |
17 | ```fsharp
18 | module MyModule =
19 | open System.Text
20 | open BlackFox.MasterOfFoo
21 | type private MySprintfEnv() =
22 | inherit PrintfEnv()
23 | let buf = StringBuilder()
24 | override this.Finalize() = buf.ToString ()
25 | override this.Write(s : PrintableElement) = ignore(buf.Append(s.FormatAsPrintF()))
26 | override this.WriteT(s : string) = ignore(buf.Append(s))
27 |
28 | let mysprintf (format: Format<'T, unit, string, string>) =
29 | doPrintfFromEnv format (MySprintfEnv())
30 |
31 | MyModule.mysprintf "Hello %s." "World"
32 | ```
33 |
34 | ## Mini-Doc
35 |
36 | ### PrintableElement
37 |
38 | `PrintableElement` represent an element in a string, for example `sprintf "Foo %s bar" "x"` produce 3
39 | `PrintableElement`, the first contains the string `"Foo "` the second is a format specifier `'s'` with an associated
40 | string value `"x"` and then there is the string the string `" Bar"`.
41 |
42 | Members :
43 |
44 | * `ElementType`: Tell your if this is a string or a format specifier.
45 | * `Value`: give the value if it was a format specifier.
46 | * `ValueType`: give the type of value expected by the format specifier.
47 | * `StarWidth`: The width if specified via another parameter as in "%*i".
48 | * `StarPrecision`: The precision if specified via another parameter as in "%.*f".
49 | * `FormatAsPrintF()`: Get the string representation that printf would have normally generated.
50 | * `Specifier`: The format specification for format specifiers.
51 |
52 | ### PrintfEnv
53 |
54 | `PrintfEnv` is the type to implement to create a printf variant it has 3 type parameters:
55 |
56 | * `'State`: The state of the printer, passed as argument when using '%t'.
57 | * `'Residue`: The type that methods passed to '%t' must return.
58 | * `'Result`: The final result type for the printer.
59 |
60 | Members:
61 | * `Finalize`: Create the final result for this printer
62 | * `Write`: Write an element from the format string to the printer
63 | * `WriteT`: Write the result of the method provided by %t to the printer.
64 |
65 | ### Functions
66 |
67 | * `doPrintfFromEnv`: Take a format and a `PrintfEnv` to create a printf-like function
68 | * `doPrintf`: Same as `doPrintfFromEnv` but allow to know the number of elements when the `PrintfEnv` is created.
69 |
70 | ## FAQ
71 |
72 | ### What does it allow exactly that can't be done with the original set of functions ?
73 |
74 | * Generating complex object that aren't only a string like an `SqlCommand` or structured logging.
75 | * Escaping parts in strings, like an `xmlprintf` that would escape `<` to `<` in parameters but not in the format
76 | string.
77 |
78 | ### What are the limitations ?
79 |
80 | The main limitation is that the F# compiler allow a strict set of things an you can't go differently.
81 | The function signature that is the first argument to `Format<_,_,_,_,>` is generated from rules in the compiler and no
82 | library can change them.
83 |
84 | The consequence is that we're limited to what is present in the F# compiler, can't add a `%Z` or allow `%0s` to work.
85 |
86 | ### Aren't you just replicating `ksprintf` ?
87 |
88 | `ksprintf` allow you to run code on the final generated result, essentially allowing you to run code during
89 | `PrintfEnv.Finalize` but you can't manipualte the format specifiers or their parameters.
90 |
91 | ### What this `Star` syntax
92 |
93 | When `*` is specified for either the width or the precision an additional parameter is taken by the format to get the
94 | value.
95 |
96 | ````
97 | > sprintf "%*.*f";;
98 | val it : (int -> int -> float -> string) =
99 | ````
100 |
101 | ### How are interpolated strings represented ?
102 |
103 | The details of string interpolation internals are specified in [F# RFC FS-1001 - String Interpolation][fs-1001].
104 |
105 | They appear as follow in this library:
106 | * Type-checked "printf-style" fills behave exactly as they do in `sprintf` and friends.
107 | * Unchecked ".NET-style" fills appear with a `Specifier.TypeChar` of `'P'` and the .NET format string
108 | in `Specifier.InteropHoleDotNetFormat`.
109 |
110 | [fs-1001]: https://github.com/fsharp/fslang-design/blob/aca88da13cdb95f4f337d4f7d44cbf9d343704ae/FSharp-5.0/FS-1001-StringInterpolation.md#f-rfc-fs-1001---string-interpolation
111 |
112 | ## Projects using it
113 |
114 | * [ColoredPrintf][colorprintf]: A small library that I created to add colored parts to printf strings.
115 | * [FSharp.Logf][Logf]: printf-style functions for `Microsoft.Extensions.Logging.ILogger`.
116 |
117 | *If you use it somewhere, ping me on the fediverse [@vbfox@hachyderm.io][fedi] so I can add you.*
118 |
119 | [Logf]: https://github.com/jwosty/FSharp.Logf
120 |
121 | More fun ?
122 | ----------
123 |
124 | ```fsharp
125 | module ColorPrintf =
126 | open System
127 | open System.Text
128 | open BlackFox.MasterOfFoo
129 |
130 | type private Colorize<'Result>(k) =
131 | inherit PrintfEnv()
132 | override this.Finalize() : 'Result = k()
133 | override this.Write(s : PrintableElement) =
134 | match s.ElementType with
135 | | PrintableElementType.FromFormatSpecifier ->
136 | let color = Console.ForegroundColor
137 | Console.ForegroundColor <- ConsoleColor.Blue
138 | Console.Write(s.FormatAsPrintF())
139 | Console.ForegroundColor <- color
140 | | _ -> Console.Write(s.FormatAsPrintF())
141 | override this.WriteT(s : string) =
142 | let color = Console.ForegroundColor
143 | Console.ForegroundColor <- ConsoleColor.Red
144 | Console.Write(s)
145 | Console.ForegroundColor <- color
146 |
147 | let colorprintf (format: Format<'T, unit, string, unit>) =
148 | doPrintfFromEnv format (Colorize id)
149 |
150 | ColorPrintf.colorprintf "%s est %t" "La vie" (fun _ -> "belle !")
151 | ```
152 |
153 | [printf_fs]: https://github.com/dotnet/fsharp/blob/main/src/FSharp.Core/printf.fs
154 | [fedi]: https://hachyderm.io/@vbfox
155 | [colorprintf]: https://github.com/vbfox/ColoredPrintf
156 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo.Build/Tasks.fs:
--------------------------------------------------------------------------------
1 | module BlackFox.MasterOfFoo.Build.Tasks
2 |
3 | open Fake.Api
4 | open Fake.Core
5 | open Fake.DotNet
6 | open Fake.DotNet.Testing
7 | open Fake.IO
8 | open Fake.IO.Globbing.Operators
9 | open Fake.IO.FileSystemOperators
10 | open Fake.Tools
11 |
12 | open BlackFox
13 | open BlackFox.Fake
14 | open System.Xml.Linq
15 |
16 | let testProjectName = "BlackFox.MasterOfFoo.Tests"
17 |
18 | let createAndGetDefault () =
19 | let configuration = DotNet.BuildConfiguration.fromEnvironVarOrDefault "configuration" DotNet.BuildConfiguration.Release
20 |
21 | let rootDir = System.IO.Path.GetFullPath(__SOURCE_DIRECTORY__ > ".." > "..")
22 | let srcDir = rootDir > "src"
23 | let artifactsDir = rootDir > "artifacts"
24 |
25 | let libraryProjectFile = srcDir > "BlackFox.MasterOfFoo" > "BlackFox.MasterOfFoo.fsproj"
26 | let libraryBinDir = artifactsDir > "BlackFox.MasterOfFoo" > (string configuration)
27 | let solutionFile = srcDir > "MasterOfFoo.sln"
28 | let projects =
29 | GlobbingPattern.createFrom srcDir
30 | ++ "**/*.*proj"
31 | -- "*.Build/*"
32 |
33 | /// The profile where the project is posted
34 | let gitOwner = "vbfox"
35 | let gitHome = "https://github.com/" + gitOwner
36 |
37 | /// The name of the project on GitHub
38 | let gitName = "MasterOfFoo"
39 |
40 | let getUnionCaseName (x:'a) =
41 | match Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(x, typeof<'a>) with | case, _ -> case.Name
42 |
43 | let release =
44 | let fromFile = ReleaseNotes.load (rootDir > "Release Notes.md")
45 | if BuildServer.buildServer <> BuildServer.LocalBuild then
46 | let buildServerName = (getUnionCaseName BuildServer.buildServer).ToLowerInvariant()
47 | let nugetVer = sprintf "%s-%s.%s" fromFile.NugetVersion buildServerName BuildServer.buildVersion
48 | ReleaseNotes.ReleaseNotes.New(fromFile.AssemblyVersion, nugetVer, fromFile.Date, fromFile.Notes)
49 | else
50 | fromFile
51 |
52 | Trace.setBuildNumber release.NugetVersion
53 |
54 | let nupkgFile = libraryBinDir > (sprintf "BlackFox.MasterOfFoo.%s.nupkg" release.NugetVersion)
55 |
56 | let writeVersionProps() =
57 | let doc =
58 | XDocument(
59 | XElement(XName.Get("Project"),
60 | XElement(XName.Get("PropertyGroup"),
61 | XElement(XName.Get "Version", release.NugetVersion),
62 | XElement(XName.Get "PackageReleaseNotes", String.toLines release.Notes))))
63 | let path = artifactsDir > "Version.props"
64 | System.IO.File.WriteAllText(path, doc.ToString())
65 |
66 | let init = BuildTask.create "Init" [] {
67 | Directory.create artifactsDir
68 | }
69 |
70 | let clean = BuildTask.create "Clean" [init] {
71 | let objDirs = projects |> Seq.map(fun p -> System.IO.Path.GetDirectoryName(p) > "obj") |> List.ofSeq
72 | Shell.cleanDirs (artifactsDir :: objDirs)
73 | }
74 |
75 | let generateVersionInfo = BuildTask.create "GenerateVersionInfo" [init; clean.IfNeeded] {
76 | writeVersionProps ()
77 | AssemblyInfoFile.createFSharp (artifactsDir > "Version.fs") [AssemblyInfo.Version release.AssemblyVersion]
78 | }
79 |
80 | let build = BuildTask.create "Build" [generateVersionInfo; clean.IfNeeded] {
81 | DotNet.build
82 | (fun p -> { p with Configuration = configuration })
83 | solutionFile
84 | }
85 |
86 | let runTests = BuildTask.create "RunTests" [build] {
87 | let baseTestDir = artifactsDir > testProjectName > (string configuration)
88 | let testConfs = ["netcoreapp2.0", ".dll"; "net5.0", ".dll"; "net8.0", ".dll"]
89 |
90 | testConfs
91 | |> List.map (fun (fw, ext) -> baseTestDir > fw > (testProjectName + ext))
92 | |> Expecto.run (fun p ->
93 | { p with
94 | PrintVersion = false
95 | FailOnFocusedTests = true
96 | })
97 |
98 | for (fw, _) in testConfs do
99 | let dir = baseTestDir > fw
100 | let outFile = sprintf "TestResults_%s.xml" (fw.Replace('.', '_'))
101 | File.delete (dir > outFile)
102 | (dir > "TestResults.xml") |> Shell.rename (dir > outFile)
103 | Trace.publish (ImportData.Nunit NunitDataVersion.Nunit) (dir > outFile)
104 | }
105 |
106 | let nuget = BuildTask.create "NuGet" [build;runTests.IfNeeded] {
107 | DotNet.pack
108 | (fun p -> { p with Configuration = configuration })
109 | libraryProjectFile
110 |
111 | Trace.publish ImportData.BuildArtifact nupkgFile
112 | }
113 |
114 | let publishNuget = BuildTask.create "PublishNuget" [nuget] {
115 | let key =
116 | match Environment.environVarOrNone "nuget-key" with
117 | | Some(key) -> key
118 | | None -> UserInput.getUserPassword "NuGet key: "
119 |
120 | Paket.pushFiles
121 | (fun o -> { o with ApiKey = key; WorkingDir = rootDir })
122 | [nupkgFile]
123 | }
124 |
125 | let zipFile = artifactsDir > (sprintf "BlackFox.MasterOfFoo-%s.zip" release.NugetVersion)
126 |
127 | let zip = BuildTask.create "Zip" [build;runTests.IfNeeded] {
128 | let comment = sprintf "MasterOfFoo v%s" release.NugetVersion
129 | GlobbingPattern.createFrom libraryBinDir
130 | ++ "**/*.dll"
131 | ++ "**/*.xml"
132 | -- "**/FSharp.Core.*"
133 | |> Zip.createZip libraryBinDir zipFile comment 9 false
134 |
135 | Trace.publish ImportData.BuildArtifact zipFile
136 | }
137 |
138 | let gitRelease = BuildTask.create "GitRelease" [nuget.IfNeeded] {
139 | let remote =
140 | Git.CommandHelper.getGitResult "" "remote -v"
141 | |> Seq.filter (fun (s: string) -> s.EndsWith("(push)"))
142 | |> Seq.tryFind (fun (s: string) -> s.Contains(gitOwner + "/" + gitName))
143 | |> function None -> gitHome + "/" + gitName | Some (s: string) -> s.Split().[0]
144 |
145 | Git.Branches.tag "" release.NugetVersion
146 | Git.Branches.pushTag "" remote release.NugetVersion
147 | }
148 |
149 | let gitHubRelease = BuildTask.create "GitHubRelease" [zip; gitRelease.IfNeeded] {
150 | let user =
151 | match Environment.environVarOrNone "github-user" with
152 | | Some s -> s
153 | | _ -> UserInput.getUserInput "GitHub Username: "
154 | let pw =
155 | match Environment.environVarOrNone "github-pw" with
156 | | Some s -> s
157 | | _ -> UserInput.getUserPassword "GitHub Password or Token: "
158 |
159 | // release on github
160 | GitHub.createClient user pw
161 | |> GitHub.draftNewRelease
162 | gitOwner
163 | gitName
164 | release.NugetVersion
165 | (release.SemVer.PreRelease <> None)
166 | (release.Notes)
167 | |> GitHub.uploadFile zipFile
168 | |> GitHub.publishDraft
169 | |> Async.RunSynchronously
170 | }
171 |
172 | let _releaseTask = BuildTask.createEmpty "Release" [clean; gitRelease; gitHubRelease; publishNuget]
173 | let _ciTask = BuildTask.createEmpty "CI" [clean; runTests; zip; nuget]
174 |
175 | BuildTask.createEmpty "Default" [runTests]
176 |
--------------------------------------------------------------------------------
/src/TestApp/Program.fs:
--------------------------------------------------------------------------------
1 | open System.Text
2 | open BlackFox.MasterOfFoo
3 | open System.Data.SqlClient
4 | open System.Data.Common
5 |
6 | module ReimplementPrintf =
7 | open System
8 | type StringPrintfEnv<'Result>(k, n) =
9 | inherit PrintfEnv(())
10 |
11 | let buf : string[] = Array.zeroCreate n
12 | let mutable ptr = 0
13 |
14 | override this.Finalize() : 'Result = k (String.Concat(buf))
15 | override this.Write(s : PrintableElement) =
16 | buf.[ptr] <- s.ToString()
17 | ptr <- ptr + 1
18 | override this.WriteT(s) =
19 | buf.[ptr] <- s
20 | ptr <- ptr + 1
21 |
22 | type StringBuilderPrintfEnv<'Result>(k, buf) =
23 | inherit PrintfEnv(buf)
24 | override this.Finalize() : 'Result = k ()
25 | override this.Write(s : PrintableElement) = ignore(buf.Append(s.ToString()))
26 | override this.WriteT(()) = ()
27 |
28 | type TextWriterPrintfEnv<'Result>(k, tw : IO.TextWriter) =
29 | inherit PrintfEnv(tw)
30 | override this.Finalize() : 'Result = k()
31 | override this.Write(s : PrintableElement) = tw.Write (s.ToString())
32 | override this.WriteT(()) = ()
33 |
34 | type BuilderFormat<'T,'Result> = Format<'T, System.Text.StringBuilder, unit, 'Result>
35 | type StringFormat<'T,'Result> = Format<'T, unit, string, 'Result>
36 | type TextWriterFormat<'T,'Result> = Format<'T, System.IO.TextWriter, unit, 'Result>
37 | type BuilderFormat<'T> = BuilderFormat<'T,unit>
38 | type StringFormat<'T> = StringFormat<'T,string>
39 | type TextWriterFormat<'T> = TextWriterFormat<'T,unit>
40 |
41 | []
42 | let ksprintf continuation (format : Format<'T, unit, string, 'Result>) : 'T =
43 | doPrintf format (fun n -> StringPrintfEnv(continuation, n))
44 |
45 | []
46 | let sprintf (format : StringFormat<'T>) = ksprintf id format
47 |
48 | []
49 | let kprintf f fmt = ksprintf f fmt
50 |
51 | []
52 | let kbprintf f (buf: System.Text.StringBuilder) fmt =
53 | doPrintfFromEnv fmt (StringBuilderPrintfEnv(f, buf))
54 |
55 | []
56 | let kfprintf f os fmt = doPrintfFromEnv fmt (TextWriterPrintfEnv(f, os))
57 |
58 | []
59 | let bprintf buf fmt = kbprintf ignore buf fmt
60 |
61 | []
62 | let fprintf (os: System.IO.TextWriter) fmt = kfprintf ignore os fmt
63 |
64 | []
65 | let fprintfn (os: System.IO.TextWriter) fmt = kfprintf (fun _ -> os.WriteLine()) os fmt
66 |
67 | []
68 | let failwithf fmt = ksprintf failwith fmt
69 |
70 | []
71 | let printf fmt = fprintf System.Console.Out fmt
72 |
73 | []
74 | let eprintf fmt = fprintf System.Console.Error fmt
75 |
76 | []
77 | let printfn fmt = fprintfn System.Console.Out fmt
78 |
79 | []
80 | let eprintfn fmt = fprintfn System.Console.Error fmt
81 |
82 | type internal SqlEnv<'cmd when 'cmd :> DbCommand>(n: int, command: 'cmd) =
83 | inherit PrintfEnv(())
84 | let queryString = StringBuilder()
85 | let mutable index = 0
86 |
87 | let addParameter (p: DbParameter) =
88 | ignore(queryString.Append p.ParameterName)
89 | command.Parameters.Add p |> ignore
90 |
91 | override __.Finalize() =
92 | command.CommandText <- queryString.ToString()
93 | command
94 |
95 | override __.Write(s : PrintableElement) =
96 | let asPrintf = s.FormatAsPrintF()
97 | match s.ElementType with
98 | | PrintableElementType.FromFormatSpecifier ->
99 | let parameter =
100 | if typeof.IsAssignableFrom(s.ValueType) then
101 | s.Value :?> DbParameter
102 | else
103 | let paramName = sprintf "@p%i" index
104 | index <- index + 1
105 |
106 | let parameter = command.CreateParameter()
107 | parameter.ParameterName <- paramName
108 | parameter.Value <- s.Value
109 | parameter
110 |
111 | addParameter parameter
112 | | _ ->
113 | ignore(queryString.Append asPrintf)
114 |
115 | override __.WriteT(()) = ()
116 |
117 | let sqlCommandf (format : Format<'T, unit, unit, SqlCommand>) =
118 | MasterOfFoo.doPrintf format (fun n -> SqlEnv(n, new SqlCommand ()) :> PrintfEnv<_, _, _>)
119 |
120 | module ColorPrintf =
121 | open System
122 |
123 | type private Colorize<'Result>(k) =
124 | inherit PrintfEnv()
125 | override this.Finalize() : 'Result = k()
126 | override this.Write(s : PrintableElement) =
127 | match s.ElementType with
128 | | PrintableElementType.FromFormatSpecifier ->
129 | let color = Console.ForegroundColor
130 | Console.ForegroundColor <- ConsoleColor.Blue
131 | Console.Write(s.FormatAsPrintF())
132 | Console.ForegroundColor <- color
133 | | _ -> Console.Write(s.FormatAsPrintF())
134 | override this.WriteT(s : string) =
135 | let color = Console.ForegroundColor
136 | Console.ForegroundColor <- ConsoleColor.Red
137 | Console.Write(s)
138 | Console.ForegroundColor <- color
139 |
140 | let colorprintf (format: Format<'T, unit, string, unit>) =
141 | doPrintfFromEnv format (Colorize id)
142 |
143 | //MyModule.mysprintf "Hello %s.\n" "World"
144 |
145 | type internal QueryStringEnv() =
146 | inherit PrintfEnv(StringBuilder())
147 |
148 | override this.Finalize() = this.State.ToString()
149 |
150 | override this.Write(s : PrintableElement) =
151 | let asPrintf = s.FormatAsPrintF()
152 | match s.ElementType with
153 | | PrintableElementType.FromFormatSpecifier ->
154 | let escaped = System.Uri.EscapeDataString(asPrintf)
155 | ignore(this.State.Append escaped)
156 | | _ ->
157 | ignore(this.State.Append asPrintf)
158 |
159 | override this.WriteT(()) = ()
160 |
161 | let queryStringf (format : Format<'T, StringBuilder, unit, string>) =
162 | MasterOfFoo.doPrintf format (fun _ -> QueryStringEnv() :> PrintfEnv<_, _, _>)
163 |
164 | type internal MyTestEnv<'Result>(k, state) =
165 | inherit PrintfEnv(state)
166 | override this.Finalize() : 'Result =
167 | printfn "Finalizing"
168 | k ()
169 | override this.Write(s : PrintableElement) =
170 | printfn "Writing: %A" s
171 | state.Append(s.FormatAsPrintF()) |> ignore
172 | override this.WriteT(()) =
173 | printfn "WTF"
174 |
175 | let testprintf (sb: StringBuilder) (format : Format<'T, StringBuilder, unit, unit>) =
176 | MasterOfFoo.doPrintf format (fun n ->
177 | MyTestEnv(ignore, sb) :> PrintfEnv<_, _, _>
178 | )
179 |
180 | let title s =
181 | printfn $"%s{s}"
182 | printfn $"%s{System.String('-', s.Length)}"
183 |
184 | let endOfBlock () =
185 | printfn ""
186 | printfn ""
187 |
188 | let debugSimple () =
189 | title "Simple"
190 |
191 | let sb = StringBuilder ()
192 | testprintf sb "Hello %0-10i hello %s %A" 1000 "World" "World"
193 | sb.Clear() |> ignore
194 | testprintf sb "Hello %-010i hello %s" 1000 "World"
195 | System.Console.WriteLine("RESULT: {0}", sb.ToString())
196 |
197 | endOfBlock ()
198 |
199 | let debugPercentStar () =
200 | title "Percent Star"
201 | let sb = StringBuilder ()
202 | testprintf sb "Hello '%*i'" 5 42
203 | sb.Clear() |> ignore
204 | testprintf sb "Hello '%.*f'" 3 42.12345
205 | sb.Clear() |> ignore
206 | testprintf sb "Hello '%*.*f'" 5 3 42.12345
207 | System.Console.WriteLine("RESULT: {0}", sb.ToString())
208 |
209 | endOfBlock ()
210 |
211 | let debugChained () =
212 | title "Chained"
213 | let sb = StringBuilder ()
214 | testprintf sb "Hello %s %s %s %s %s %s" "1" "2" "3" "4" "5" "6"
215 | System.Console.WriteLine("RESULT: {0}", sb.ToString())
216 |
217 | endOfBlock ()
218 |
219 | let debugComplex () =
220 | title "Complex"
221 | let sb = StringBuilder ()
222 | testprintf sb "Hello %s %s %s %s %s %s %06i %t" "1" "2" "3" "4" "5" "6" 5 (fun x -> x.Append("CALLED") |> ignore)
223 | System.Console.WriteLine("RESULT: {0}", sb.ToString())
224 |
225 | endOfBlock ()
226 |
227 | let demoQueryString () =
228 | title "Query String"
229 |
230 | let foo = "#baz"
231 | let bar = "++Hello world && problem for arg √"
232 | let work = 1
233 | let result = queryStringf $"hello/uri+with space/x?foo=%s{foo}&bar=%s{bar}&work=%i{work}"
234 | printfn $"%s{result}"
235 |
236 | endOfBlock()
237 |
238 | let demoColorPrintf () =
239 | title "Color Printf"
240 |
241 | ColorPrintf.colorprintf "%s est %t" "La vie" (fun _ -> "belle !\n")
242 |
243 | endOfBlock()
244 |
245 | let demoColorPrintfInterpolated () =
246 | title "Color Printf Interpolated"
247 |
248 | let name = "Phillip"
249 | let age = 30
250 | let tf = (fun _ -> "Hello TF")
251 |
252 | ColorPrintf.colorprintf $"Name: {name} %s{name}, Age: {age}, tf: %t{tf}, %A{age}\n"
253 |
254 | endOfBlock()
255 |
256 | let demoSqlCommand () =
257 | title "SQL Command"
258 |
259 | let cmd: SqlCommand =
260 | sqlCommandf
261 | "SELECT * FROM tbUser WHERE UserId=%i AND NAME=%s AND CREATIONDATE > %O"
262 | 5
263 | "Test"
264 | System.DateTimeOffset.Now
265 |
266 | printfn "Command: %s " cmd.CommandText
267 | for p in cmd.Parameters do
268 | printfn " - %s = %O" p.ParameterName p.Value
269 |
270 | endOfBlock()
271 |
272 | []
273 | let main argv =
274 | if argv.Length > 0 && argv.[0].ToLowerInvariant() = "debug" then
275 | debugSimple ()
276 | debugPercentStar ()
277 | debugChained ()
278 | debugComplex ()
279 | else
280 | demoQueryString ()
281 | demoColorPrintf ()
282 | demoColorPrintfInterpolated ()
283 | demoSqlCommand ()
284 | 0
285 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo.Tests/InternalRepresentationTests.fs:
--------------------------------------------------------------------------------
1 | module BlackFox.MasterOfFoo.Tests.InternalRepresentationTests
2 |
3 | open Expecto
4 | open System.Text
5 |
6 | open BlackFox.MasterOfFoo
7 | open System.Text.RegularExpressions
8 |
9 | type MyFormatable() =
10 | interface System.IFormattable with
11 | member __.ToString(format: string, _formatProvider: System.IFormatProvider) =
12 | $"MyFormatable(%s{format})"
13 |
14 | override _.ToString() = "MyFormatable"
15 |
16 | type MyToStringSaysHi() =
17 | override _.ToString() =
18 | "Hi"
19 |
20 | type TestEnv() =
21 | inherit PrintfEnv()
22 | let buf = StringBuilder().AppendLine("Init")
23 | override _.Finalize() =
24 | buf.AppendLine("Finalize") |> ignore
25 | buf.ToString ()
26 | override _.Write(s : PrintableElement) =
27 | buf.Append("Write ") |> ignore
28 | buf.Append(sprintf "%A" s) |> ignore
29 | buf.AppendLine(";") |> ignore
30 | override _.WriteT(s : string) =
31 | buf.Append("WriteT ") |> ignore
32 | buf.AppendLine(s) |> ignore
33 |
34 | let cleanTypeParameters (s: string) =
35 | let re = Regex("""\[\[([^,]+)([^\]]*)\]\]""")
36 | let replacement (m: Match) =
37 | sprintf "[[%s]]" m.Groups.[1].Value
38 | re.Replace(s, replacement)
39 |
40 | let testprintf (format: Printf.StringFormat<'a>) =
41 | doPrintf format (fun _ -> TestEnv())
42 |
43 | let cleanText (s: string) =
44 | s.Trim().Replace("\r\n", "\n")
45 |
46 | let testEqual (actual: string) (expected:string) =
47 | Expect.equal (actual |> cleanText |> cleanTypeParameters) (expected |> cleanText) ""
48 |
49 | let tests = [
50 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
51 | // Simple case
52 |
53 | test "simple string" {
54 | testEqual
55 | (testprintf "Foo")
56 | """
57 | Init
58 | Write value: "Foo", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: Foo;
59 | Finalize
60 | """
61 | }
62 |
63 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
64 | // Supported types
65 |
66 | test "string hole only" {
67 | testEqual
68 | (testprintf "%s" "Foo")
69 | """
70 | Init
71 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo;
72 | Finalize
73 | """
74 | }
75 |
76 | test "boolean hole only" {
77 | testEqual
78 | (testprintf "%b" true)
79 | """
80 | Init
81 | Write value: true, type: FromFormatSpecifier, valueType: System.Boolean, spec: 'b', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: true;
82 | Finalize
83 | """
84 | }
85 |
86 | test "int hole only %i" {
87 | testEqual
88 | (testprintf "%i" 42)
89 | """
90 | Init
91 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42;
92 | Finalize
93 | """
94 | }
95 |
96 | test "int hole only %d" {
97 | testEqual
98 | (testprintf "%d" 42)
99 | """
100 | Init
101 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'd', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42;
102 | Finalize
103 | """
104 | }
105 |
106 | test "int hole only %u" {
107 | testEqual
108 | (testprintf "%u" 42)
109 | """
110 | Init
111 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'u', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42;
112 | Finalize
113 | """
114 | }
115 |
116 | test "int hole only %x" {
117 | testEqual
118 | (testprintf "%x" 42)
119 | """
120 | Init
121 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'x', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 2a;
122 | Finalize
123 | """
124 | }
125 |
126 | test "int hole only %X" {
127 | testEqual
128 | (testprintf "%X" 42)
129 | """
130 | Init
131 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'X', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 2A;
132 | Finalize
133 | """
134 | }
135 |
136 | test "int hole only %o" {
137 | testEqual
138 | (testprintf "%o" 42)
139 | """
140 | Init
141 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'o', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 52;
142 | Finalize
143 | """
144 | }
145 |
146 | test "int hole only %B" {
147 | testEqual
148 | (testprintf "%B" 42)
149 | """
150 | Init
151 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'B', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 101010;
152 | Finalize
153 | """
154 | }
155 |
156 | test "float64 hole only %f" {
157 | testEqual
158 | (testprintf "%f" 42.42)
159 | """
160 | Init
161 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'f', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42.420000;
162 | Finalize
163 | """
164 | }
165 |
166 | test "float64 hole only %e" {
167 | testEqual
168 | (testprintf "%e" 42.42)
169 | """
170 | Init
171 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'e', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 4.242000e+001;
172 | Finalize
173 | """
174 | }
175 |
176 | test "float64 hole only %E" {
177 | testEqual
178 | (testprintf "%E" 42.42)
179 | """
180 | Init
181 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'E', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 4.242000E+001;
182 | Finalize
183 | """
184 | }
185 |
186 | test "float64 hole only %g" {
187 | testEqual
188 | (testprintf "%g" 42.42)
189 | """
190 | Init
191 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'g', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42.42;
192 | Finalize
193 | """
194 | }
195 |
196 | test "float64 hole only %G" {
197 | testEqual
198 | (testprintf "%G" 42.42)
199 | """
200 | Init
201 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'G', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42.42;
202 | Finalize
203 | """
204 | }
205 |
206 | test "float32 hole only %f" {
207 | testEqual
208 | (testprintf "%f" 42.0f)
209 | """
210 | Init
211 | Write value: 42.0f, type: FromFormatSpecifier, valueType: System.Single, spec: 'f', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42.000000;
212 | Finalize
213 | """
214 | }
215 |
216 | test "Decimal hole only" {
217 | testEqual
218 | (testprintf "%M" 123456789.123456789M)
219 | """
220 | Init
221 | Write value: 123456789.123456789M, type: FromFormatSpecifier, valueType: System.Decimal, spec: 'M', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 123456789.123456789;
222 | Finalize
223 | """
224 | }
225 |
226 | test "F# format hole only" {
227 | testEqual
228 | (testprintf "%A" (Some true))
229 | """
230 | Init
231 | Write value: Some true, type: FromFormatSpecifier, valueType: Microsoft.FSharp.Core.FSharpOption`1[[System.Boolean]], spec: 'A', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Some true;
232 | Finalize
233 | """
234 | }
235 |
236 | test "ToString hole only" {
237 | testEqual
238 | (testprintf "%O" (MyToStringSaysHi()))
239 | """
240 | Init
241 | Write value: Hi, type: FromFormatSpecifier, valueType: BlackFox.MasterOfFoo.Tests.InternalRepresentationTests+MyToStringSaysHi, spec: 'O', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Hi;
242 | Finalize
243 | """
244 | }
245 |
246 | test "IFormattable hole only" {
247 | testEqual
248 | (testprintf $"{MyFormatable():HelloWorld}")
249 | """
250 | Init
251 | Write value: MyFormatable, type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: 'HelloWorld', starWidth: , starPrecision: , AsPrintF: MyFormatable(HelloWorld);
252 | Finalize
253 | """
254 | }
255 |
256 | test "IFormattable + unusual characters hole only" {
257 | testEqual
258 | (testprintf $"{MyFormatable():``Hello World``}")
259 | """
260 | Init
261 | Write value: MyFormatable, type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: 'Hello World', starWidth: , starPrecision: , AsPrintF: MyFormatable(Hello World);
262 | Finalize
263 | """
264 | }
265 |
266 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
267 | // Complex case mixing holes and text
268 |
269 | test "hole and text" {
270 | testEqual
271 | (testprintf "Hello%sWorld" "Foo")
272 | """
273 | Init
274 | Write value: "Hello", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: Hello;
275 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo;
276 | Write value: "World", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: World;
277 | Finalize
278 | """
279 | }
280 |
281 | test "multiple holes and text" {
282 | testEqual
283 | (testprintf "Hello%sWorld%i" "Foo" 42)
284 | """
285 | Init
286 | Write value: "Hello", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: Hello;
287 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo;
288 | Write value: "World", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: World;
289 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42;
290 | Finalize
291 | """
292 | }
293 |
294 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
295 | // Integer options
296 |
297 | test "plus sign" {
298 | testEqual
299 | (testprintf "%+i" 42)
300 | """
301 | Init
302 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=-, Flags=PlusForPositives, starWidth: , starPrecision: , AsPrintF: +42;
303 | Finalize
304 | """
305 | }
306 |
307 | test "blank plus sign" {
308 | testEqual
309 | (testprintf "% i" 42)
310 | """
311 | Init
312 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=-, Flags=SpaceForPositives, starWidth: , starPrecision: , AsPrintF: 42;
313 | Finalize
314 | """
315 | }
316 |
317 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
318 | // Alignment
319 |
320 | test "string alignment" {
321 | testEqual
322 | (testprintf "%5s" "Foo")
323 | """
324 | Init
325 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=5, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo;
326 | Finalize
327 | """
328 | }
329 |
330 | test "int alignment" {
331 | testEqual
332 | (testprintf "%5i" 42)
333 | """
334 | Init
335 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=5, Flags=None, starWidth: , starPrecision: , AsPrintF: 42;
336 | Finalize
337 | """
338 | }
339 |
340 | test "int pad with 0" {
341 | testEqual
342 | (testprintf "%05i" 42)
343 | """
344 | Init
345 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=5, Flags=PadWithZeros, starWidth: , starPrecision: , AsPrintF: 00042;
346 | Finalize
347 | """
348 | }
349 |
350 | test "string left alignment" {
351 | testEqual
352 | (testprintf "%-5s" "Foo")
353 | """
354 | Init
355 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=5, Flags=LeftJustify, starWidth: , starPrecision: , AsPrintF: Foo ;
356 | Finalize
357 | """
358 | }
359 |
360 | test "int left alignment" {
361 | testEqual
362 | (testprintf "%-5i" 42)
363 | """
364 | Init
365 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=5, Flags=LeftJustify, starWidth: , starPrecision: , AsPrintF: 42 ;
366 | Finalize
367 | """
368 | }
369 |
370 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
371 | // Star alignment
372 |
373 | test "string star alignment" {
374 | testEqual
375 | (testprintf "%*s" 5 "Foo")
376 | """
377 | Init
378 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=*, Flags=None, starWidth: 5, starPrecision: , AsPrintF: Foo;
379 | Finalize
380 | """
381 | }
382 |
383 | test "int star alignment" {
384 | testEqual
385 | (testprintf "%*i" 5 42)
386 | """
387 | Init
388 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=*, Flags=None, starWidth: 5, starPrecision: , AsPrintF: 42;
389 | Finalize
390 | """
391 | }
392 |
393 | test "string left star alignment" {
394 | testEqual
395 | (testprintf "%-*s" 5 "Foo")
396 | """
397 | Init
398 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=*, Flags=LeftJustify, starWidth: 5, starPrecision: , AsPrintF: Foo ;
399 | Finalize
400 | """
401 | }
402 |
403 | test "int left star alignment" {
404 | testEqual
405 | (testprintf "%-*i" 5 42)
406 | """
407 | Init
408 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=*, Flags=LeftJustify, starWidth: 5, starPrecision: , AsPrintF: 42 ;
409 | Finalize
410 | """
411 | }
412 |
413 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
414 | // Interpolation
415 |
416 | test "interpolated-explicit string hole only" {
417 | let value = "Foo"
418 | testEqual
419 | (testprintf $"%s{value}")
420 | """
421 | Init
422 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.Object, spec: 's', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo;
423 | Finalize
424 | """
425 | }
426 |
427 | test "interpolated-implicit string hole only" {
428 | let value = "Foo"
429 | testEqual
430 | (testprintf $"{value}")
431 | """
432 | Init
433 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: '', starWidth: , starPrecision: , AsPrintF: Foo;
434 | Finalize
435 | """
436 | }
437 |
438 | test "interpolated-embeded string hole only" {
439 | testEqual
440 | (testprintf $"""{"embedded string literal"}""")
441 | """
442 | Init
443 | Write value: "embedded string literal", type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: '', starWidth: , starPrecision: , AsPrintF: embedded string literal;
444 | Finalize
445 | """
446 | }
447 |
448 | test "interpolated-explicit int hole only" {
449 | let value = 42
450 | testEqual
451 | (testprintf $"%i{value}")
452 | """
453 | Init
454 | Write value: 42, type: FromFormatSpecifier, valueType: System.Object, spec: 'i', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42;
455 | Finalize
456 | """
457 | }
458 |
459 | test "interpolated-implicit int hole only" {
460 | let value = 42
461 | testEqual
462 | (testprintf $"{value}")
463 | """
464 | Init
465 | Write value: 42, type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: '', starWidth: , starPrecision: , AsPrintF: 42;
466 | Finalize
467 | """
468 | }
469 |
470 | test "interpolated-explicit float hole F# format only" {
471 | testEqual
472 | (testprintf $"%0.3f{System.Math.PI}")
473 | """
474 | Init
475 | Write value: 3.141592654, type: FromFormatSpecifier, valueType: System.Object, spec: 'f', Precision=3, Width=-, Flags=PadWithZeros, starWidth: , starPrecision: , AsPrintF: 3.142;
476 | Finalize
477 | """
478 | }
479 |
480 | test "interpolated-implicit float hole dotnet format only" {
481 | testEqual
482 | (testprintf $"{System.Math.PI:N3}")
483 | """
484 | Init
485 | Write value: 3.141592654, type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: 'N3', starWidth: , starPrecision: , AsPrintF: 3.142;
486 | Finalize
487 | """
488 | }
489 | ]
490 |
491 | []
492 | let test = testList "Internal Representation Tests" tests
493 |
--------------------------------------------------------------------------------
/paket.lock:
--------------------------------------------------------------------------------
1 | STORAGE: NONE
2 | RESTRICTION: || (== net461) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
3 | NUGET
4 | remote: https://api.nuget.org/v3/index.json
5 | Expecto (9.0.4)
6 | FSharp.Core (>= 4.6)
7 | Mono.Cecil (>= 0.11.3)
8 | FSharp.Core (8.0.400)
9 | Microsoft.NETCore.Platforms (7.0.4) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (&& (== net5.0) (== netstandard2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.0)) (&& (== net8.0) (== netstandard2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.0)) (== netcoreapp2.0) (&& (== netstandard2.0) (>= netcoreapp2.0)) (&& (== netstandard2.0) (>= netcoreapp2.1))
10 | Microsoft.Win32.Registry (5.0) - restriction: || (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
11 | System.Buffers (>= 4.5.1) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (>= monoandroid) (< netstandard1.3)) (&& (== net461) (>= monotouch)) (&& (== net461) (< net46) (>= netstandard2.0)) (&& (== net461) (>= xamarinios)) (&& (== net461) (>= xamarinmac)) (&& (== net461) (>= xamarintvos)) (&& (== net461) (>= xamarinwatchos)) (&& (== net5.0) (>= monoandroid) (< netstandard1.3)) (&& (== net5.0) (>= monotouch)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (>= xamarinios)) (&& (== net5.0) (>= xamarinmac)) (&& (== net5.0) (>= xamarintvos)) (&& (== net5.0) (>= xamarinwatchos)) (&& (== net8.0) (>= monoandroid) (< netstandard1.3)) (&& (== net8.0) (>= monotouch)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (>= xamarinios)) (&& (== net8.0) (>= xamarinmac)) (&& (== net8.0) (>= xamarintvos)) (&& (== net8.0) (>= xamarinwatchos)) (&& (== netcoreapp2.0) (>= monoandroid) (< netstandard1.3)) (&& (== netcoreapp2.0) (>= monotouch)) (&& (== netcoreapp2.0) (>= xamarinios)) (&& (== netcoreapp2.0) (>= xamarinmac)) (&& (== netcoreapp2.0) (>= xamarintvos)) (&& (== netcoreapp2.0) (>= xamarinwatchos)) (== netstandard2.0)
12 | System.Memory (>= 4.5.4) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net46) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0)
13 | System.Security.AccessControl (>= 5.0)
14 | System.Security.Principal.Windows (>= 5.0)
15 | Mono.Cecil (0.11.5)
16 | runtime.native.System.Data.SqlClient.sni (4.7) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
17 | runtime.win-arm64.runtime.native.System.Data.SqlClient.sni (>= 4.4)
18 | runtime.win-x64.runtime.native.System.Data.SqlClient.sni (>= 4.4)
19 | runtime.win-x86.runtime.native.System.Data.SqlClient.sni (>= 4.4)
20 | runtime.win-arm64.runtime.native.System.Data.SqlClient.sni (4.4) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
21 | runtime.win-x64.runtime.native.System.Data.SqlClient.sni (4.4) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
22 | runtime.win-x86.runtime.native.System.Data.SqlClient.sni (4.4) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
23 | System.Buffers (4.5.1) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.0)) (== netstandard2.0)
24 | System.Data.SqlClient (4.8.6)
25 | Microsoft.Win32.Registry (>= 4.7) - restriction: || (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
26 | runtime.native.System.Data.SqlClient.sni (>= 4.7) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
27 | System.Buffers (>= 4.5.1) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.0)) (== netstandard2.0)
28 | System.Diagnostics.DiagnosticSource (>= 4.7) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0)
29 | System.Memory (>= 4.5.4) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0)
30 | System.Security.Principal.Windows (>= 4.7) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
31 | System.Text.Encoding.CodePages (>= 4.7) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0)
32 | System.Diagnostics.DiagnosticSource (8.0.1) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0)
33 | System.Memory (>= 4.5.5) - restriction: || (&& (== net461) (== net8.0)) (&& (== net461) (>= net462)) (&& (== net461) (>= netstandard2.0)) (== net5.0) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netcoreapp2.0) (== netstandard2.0)
34 | System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (&& (== net461) (== net8.0)) (&& (== net461) (>= net462)) (&& (== net461) (>= net6.0)) (&& (== net461) (>= netstandard2.0)) (== net5.0) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (&& (== net8.0) (< net7.0)) (== netcoreapp2.0) (== netstandard2.0)
35 | System.Memory (4.5.5) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451) (>= net462)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= net462) (>= uap10.1)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net5.0) (>= net462)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0)
36 | System.Buffers (>= 4.5.1) - restriction: || (== net461) (&& (== net5.0) (>= monotouch)) (&& (== net5.0) (>= net461)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netstandard1.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= xamarinios)) (&& (== net5.0) (>= xamarinmac)) (&& (== net5.0) (>= xamarintvos)) (&& (== net5.0) (>= xamarinwatchos)) (&& (== net8.0) (>= monotouch)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netstandard1.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= xamarinios)) (&& (== net8.0) (>= xamarinmac)) (&& (== net8.0) (>= xamarintvos)) (&& (== net8.0) (>= xamarinwatchos)) (&& (== netcoreapp2.0) (>= monotouch)) (&& (== netcoreapp2.0) (>= net461)) (&& (== netcoreapp2.0) (< netstandard1.1)) (&& (== netcoreapp2.0) (< netstandard2.0)) (&& (== netcoreapp2.0) (>= xamarinios)) (&& (== netcoreapp2.0) (>= xamarinmac)) (&& (== netcoreapp2.0) (>= xamarintvos)) (&& (== netcoreapp2.0) (>= xamarinwatchos)) (== netstandard2.0)
37 | System.Numerics.Vectors (>= 4.4) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (< net45) (>= netstandard2.0)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.0)) (== netstandard2.0)
38 | System.Runtime.CompilerServices.Unsafe (>= 4.5.3) - restriction: || (== net461) (&& (== net5.0) (>= monotouch)) (&& (== net5.0) (>= net461)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netstandard1.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net5.0) (>= xamarinios)) (&& (== net5.0) (>= xamarinmac)) (&& (== net5.0) (>= xamarintvos)) (&& (== net5.0) (>= xamarinwatchos)) (&& (== net8.0) (>= monotouch)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netstandard1.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (&& (== net8.0) (>= xamarinios)) (&& (== net8.0) (>= xamarinmac)) (&& (== net8.0) (>= xamarintvos)) (&& (== net8.0) (>= xamarinwatchos)) (== netcoreapp2.0) (== netstandard2.0)
39 | System.Numerics.Vectors (4.5) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (< net45) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net5.0) (>= net462)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netstandard2.0)
40 | System.Runtime.CompilerServices.Unsafe (6.0) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451) (>= net462)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= net462) (>= uap10.1)) (&& (== net461) (>= net6.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net5.0) (>= net462)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0)
41 | System.Security.AccessControl (6.0.1) - restriction: || (&& (== net461) (>= monoandroid) (< netstandard1.3) (>= netstandard2.0)) (&& (== net461) (>= monotouch) (>= netstandard2.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net461) (>= netstandard2.0) (>= xamarintvos)) (&& (== net461) (>= netstandard2.0) (>= xamarinwatchos)) (&& (== net461) (>= xamarinios)) (&& (== net461) (>= xamarinmac)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
42 | System.Security.Principal.Windows (>= 5.0) - restriction: || (== net461) (== net5.0) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< net6.0)) (== netcoreapp2.0) (== netstandard2.0)
43 | System.Security.Principal.Windows (5.0) - restriction: || (&& (== net461) (>= monoandroid) (< netstandard1.3) (>= netstandard2.0)) (&& (== net461) (>= monotouch) (>= netstandard2.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net461) (>= netstandard2.0) (>= xamarintvos)) (&& (== net461) (>= netstandard2.0) (>= xamarinwatchos)) (&& (== net461) (>= xamarinios)) (&& (== net461) (>= xamarinmac)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0)
44 | Microsoft.NETCore.Platforms (>= 5.0) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net5.0) (== netstandard2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.0)) (&& (== net8.0) (== netstandard2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.0)) (== netcoreapp2.0) (&& (== netstandard2.0) (>= netcoreapp2.0)) (&& (== netstandard2.0) (>= netcoreapp2.1))
45 | System.Text.Encoding.CodePages (8.0) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0)
46 | System.Memory (>= 4.5.5) - restriction: || (&& (== net461) (== net8.0)) (&& (== net461) (>= net462)) (&& (== net461) (>= netstandard2.0)) (== net5.0) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netcoreapp2.0) (== netstandard2.0)
47 | System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (&& (== net461) (== net8.0)) (&& (== net461) (>= net462)) (&& (== net461) (>= net6.0)) (&& (== net461) (>= netstandard2.0)) (== net5.0) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (&& (== net8.0) (< net7.0)) (== netcoreapp2.0) (== netstandard2.0)
48 |
49 | GROUP build
50 | STORAGE: NONE
51 | RESTRICTION: == net8.0
52 | NUGET
53 | remote: https://api.nuget.org/v3/index.json
54 | BlackFox.Fake.BuildTask (0.1.3)
55 | Fake.Core.Target (>= 5.1)
56 | FSharp.Core (>= 4.3.4)
57 | BlackFox.VsWhere (1.1)
58 | FSharp.Core (>= 4.2.3)
59 | Microsoft.Win32.Registry (>= 4.7)
60 | Fake.Api.GitHub (5.20.4)
61 | FSharp.Core (>= 4.7.2)
62 | Octokit (>= 0.48)
63 | Fake.BuildServer.GitHubActions (5.20.4)
64 | Fake.Core.Environment (>= 5.20.4)
65 | Fake.Core.Trace (>= 5.20.4)
66 | Fake.IO.FileSystem (>= 5.20.4)
67 | FSharp.Core (>= 4.7.2)
68 | Fake.Core.CommandLineParsing (5.20.4)
69 | FParsec (>= 1.1.1)
70 | FSharp.Core (>= 4.7.2)
71 | Fake.Core.Context (5.20.4)
72 | FSharp.Core (>= 4.7.2)
73 | Fake.Core.Environment (5.20.4)
74 | FSharp.Core (>= 4.7.2)
75 | Fake.Core.FakeVar (5.20.4)
76 | Fake.Core.Context (>= 5.20.4)
77 | FSharp.Core (>= 4.7.2)
78 | Fake.Core.Process (5.20.4)
79 | Fake.Core.Environment (>= 5.20.4)
80 | Fake.Core.FakeVar (>= 5.20.4)
81 | Fake.Core.String (>= 5.20.4)
82 | Fake.Core.Trace (>= 5.20.4)
83 | Fake.IO.FileSystem (>= 5.20.4)
84 | FSharp.Core (>= 4.7.2)
85 | System.Collections.Immutable (>= 1.7.1)
86 | Fake.Core.ReleaseNotes (5.20.4)
87 | Fake.Core.SemVer (>= 5.20.4)
88 | Fake.Core.String (>= 5.20.4)
89 | FSharp.Core (>= 4.7.2)
90 | Fake.Core.SemVer (5.20.4)
91 | FSharp.Core (>= 4.7.2)
92 | Fake.Core.String (5.20.4)
93 | FSharp.Core (>= 4.7.2)
94 | Fake.Core.Target (5.20.4)
95 | Fake.Core.CommandLineParsing (>= 5.20.4)
96 | Fake.Core.Context (>= 5.20.4)
97 | Fake.Core.Environment (>= 5.20.4)
98 | Fake.Core.FakeVar (>= 5.20.4)
99 | Fake.Core.Process (>= 5.20.4)
100 | Fake.Core.String (>= 5.20.4)
101 | Fake.Core.Trace (>= 5.20.4)
102 | FSharp.Control.Reactive (>= 4.4.2)
103 | FSharp.Core (>= 4.7.2)
104 | Fake.Core.Tasks (5.20.4)
105 | Fake.Core.Trace (>= 5.20.4)
106 | FSharp.Core (>= 4.7.2)
107 | Fake.Core.Trace (5.20.4)
108 | Fake.Core.Environment (>= 5.20.4)
109 | Fake.Core.FakeVar (>= 5.20.4)
110 | FSharp.Core (>= 4.7.2)
111 | Fake.Core.UserInput (5.20.4)
112 | FSharp.Core (>= 4.7.2)
113 | Fake.Core.Xml (5.20.4)
114 | Fake.Core.String (>= 5.20.4)
115 | FSharp.Core (>= 4.7.2)
116 | Fake.DotNet.AssemblyInfoFile (5.20.4)
117 | Fake.Core.Environment (>= 5.20.4)
118 | Fake.Core.String (>= 5.20.4)
119 | Fake.Core.Trace (>= 5.20.4)
120 | Fake.IO.FileSystem (>= 5.20.4)
121 | FSharp.Core (>= 4.7.2)
122 | Fake.DotNet.Cli (5.20.4)
123 | Fake.Core.Environment (>= 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.MSBuild (>= 5.20.4)
128 | Fake.DotNet.NuGet (>= 5.20.4)
129 | Fake.IO.FileSystem (>= 5.20.4)
130 | FSharp.Core (>= 4.7.2)
131 | Mono.Posix.NETStandard (>= 1.0)
132 | Newtonsoft.Json (>= 12.0.3)
133 | Fake.DotNet.MSBuild (5.20.4)
134 | BlackFox.VsWhere (>= 1.1)
135 | Fake.Core.Environment (>= 5.20.4)
136 | Fake.Core.Process (>= 5.20.4)
137 | Fake.Core.String (>= 5.20.4)
138 | Fake.Core.Trace (>= 5.20.4)
139 | Fake.IO.FileSystem (>= 5.20.4)
140 | FSharp.Core (>= 4.7.2)
141 | MSBuild.StructuredLogger (>= 2.1.176)
142 | Fake.DotNet.NuGet (5.20.4)
143 | Fake.Core.Environment (>= 5.20.4)
144 | Fake.Core.Process (>= 5.20.4)
145 | Fake.Core.SemVer (>= 5.20.4)
146 | Fake.Core.String (>= 5.20.4)
147 | Fake.Core.Tasks (>= 5.20.4)
148 | Fake.Core.Trace (>= 5.20.4)
149 | Fake.Core.Xml (>= 5.20.4)
150 | Fake.IO.FileSystem (>= 5.20.4)
151 | Fake.Net.Http (>= 5.20.4)
152 | FSharp.Core (>= 4.7.2)
153 | Newtonsoft.Json (>= 12.0.3)
154 | NuGet.Protocol (>= 5.6)
155 | Fake.DotNet.Paket (5.20.4)
156 | Fake.Core.Process (>= 5.20.4)
157 | Fake.Core.String (>= 5.20.4)
158 | Fake.Core.Trace (>= 5.20.4)
159 | Fake.DotNet.Cli (>= 5.20.4)
160 | Fake.IO.FileSystem (>= 5.20.4)
161 | FSharp.Core (>= 4.7.2)
162 | Fake.DotNet.Testing.Expecto (5.20.4)
163 | Fake.Core.Process (>= 5.20.4)
164 | Fake.Core.String (>= 5.20.4)
165 | Fake.Core.Trace (>= 5.20.4)
166 | Fake.IO.FileSystem (>= 5.20.4)
167 | Fake.Testing.Common (>= 5.20.4)
168 | FSharp.Core (>= 4.7.2)
169 | Fake.IO.FileSystem (5.20.4)
170 | Fake.Core.String (>= 5.20.4)
171 | FSharp.Core (>= 4.7.2)
172 | Fake.IO.Zip (5.20.4)
173 | Fake.Core.String (>= 5.20.4)
174 | Fake.IO.FileSystem (>= 5.20.4)
175 | FSharp.Core (>= 4.7.2)
176 | Fake.Net.Http (5.20.4)
177 | Fake.Core.Trace (>= 5.20.4)
178 | FSharp.Core (>= 4.7.2)
179 | Fake.Testing.Common (5.20.4)
180 | Fake.Core.Trace (>= 5.20.4)
181 | FSharp.Core (>= 4.7.2)
182 | Fake.Tools.Git (5.20.4)
183 | Fake.Core.Environment (>= 5.20.4)
184 | Fake.Core.Process (>= 5.20.4)
185 | Fake.Core.SemVer (>= 5.20.4)
186 | Fake.Core.String (>= 5.20.4)
187 | Fake.Core.Trace (>= 5.20.4)
188 | Fake.IO.FileSystem (>= 5.20.4)
189 | FSharp.Core (>= 4.7.2)
190 | FParsec (1.1.1)
191 | FSharp.Core (>= 4.3.4)
192 | FSharp.Control.Reactive (5.0.5)
193 | FSharp.Core (>= 4.7.2)
194 | System.Reactive (>= 5.0 < 6.0)
195 | FSharp.Core (5.0.2)
196 | Microsoft.Build.Framework (17.11.4)
197 | Microsoft.Build.Utilities.Core (17.11.4)
198 | Microsoft.Build.Framework (>= 17.11.4)
199 | Microsoft.NET.StringTools (>= 17.11.4)
200 | System.Collections.Immutable (>= 8.0)
201 | System.Configuration.ConfigurationManager (>= 8.0)
202 | Microsoft.NET.StringTools (17.11.4)
203 | Microsoft.Win32.Registry (5.0)
204 | System.Security.AccessControl (>= 5.0)
205 | System.Security.Principal.Windows (>= 5.0)
206 | Mono.Posix.NETStandard (1.0)
207 | MSBuild.StructuredLogger (2.2.337)
208 | Microsoft.Build.Framework (>= 17.5)
209 | Microsoft.Build.Utilities.Core (>= 17.5)
210 | Newtonsoft.Json (13.0.3)
211 | NuGet.Common (6.11)
212 | NuGet.Frameworks (>= 6.11)
213 | NuGet.Configuration (6.11)
214 | NuGet.Common (>= 6.11)
215 | System.Security.Cryptography.ProtectedData (>= 4.4)
216 | NuGet.Frameworks (6.11)
217 | NuGet.Packaging (6.11)
218 | Newtonsoft.Json (>= 13.0.3)
219 | NuGet.Configuration (>= 6.11)
220 | NuGet.Versioning (>= 6.11)
221 | System.Security.Cryptography.Pkcs (>= 6.0.4)
222 | NuGet.Protocol (6.11)
223 | NuGet.Packaging (>= 6.11)
224 | NuGet.Versioning (6.11)
225 | Octokit (0.48)
226 | System.Collections.Immutable (8.0)
227 | System.Configuration.ConfigurationManager (8.0)
228 | System.Diagnostics.EventLog (>= 8.0)
229 | System.Security.Cryptography.ProtectedData (>= 8.0)
230 | System.Diagnostics.EventLog (8.0)
231 | System.Formats.Asn1 (8.0.1)
232 | System.Reactive (5.0)
233 | System.Security.AccessControl (6.0.1)
234 | System.Security.Cryptography.Pkcs (8.0)
235 | System.Formats.Asn1 (>= 8.0)
236 | System.Security.Cryptography.ProtectedData (8.0)
237 | System.Security.Principal.Windows (5.0)
238 |
--------------------------------------------------------------------------------
/.paket/Paket.Restore.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
8 |
9 | $(MSBuildVersion)
10 | 15.0.0
11 | false
12 | true
13 |
14 | true
15 | $(MSBuildThisFileDirectory)
16 | $(MSBuildThisFileDirectory)..\
17 | $(PaketRootPath)paket-files\paket.restore.cached
18 | $(PaketRootPath)paket.lock
19 | classic
20 | proj
21 | assembly
22 | native
23 | /Library/Frameworks/Mono.framework/Commands/mono
24 | mono
25 |
26 |
27 | $(PaketRootPath)paket.bootstrapper.exe
28 | $(PaketToolsPath)paket.bootstrapper.exe
29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\
30 |
31 | "$(PaketBootStrapperExePath)"
32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)"
33 |
34 |
35 |
36 |
37 | true
38 | true
39 |
40 |
41 | True
42 |
43 |
44 | False
45 |
46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/'))
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | $(PaketRootPath)paket
56 | $(PaketToolsPath)paket
57 |
58 |
59 |
60 |
61 |
62 | $(PaketRootPath)paket.exe
63 | $(PaketToolsPath)paket.exe
64 |
65 |
66 |
67 |
68 |
69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json"))
70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"'))
71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | <_PaketCommand>dotnet paket
83 |
84 |
85 |
86 |
87 |
88 | $(PaketToolsPath)paket
89 | $(PaketBootStrapperExeDir)paket
90 |
91 |
92 | paket
93 |
94 |
95 |
96 |
97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)"))
98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)"
99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)"
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | true
122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608
123 | false
124 | true
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))
134 |
135 |
136 |
137 |
138 |
139 |
141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``))
142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``))
143 |
144 |
145 |
146 |
147 | %(PaketRestoreCachedKeyValue.Value)
148 | %(PaketRestoreCachedKeyValue.Value)
149 |
150 |
151 |
152 |
153 | true
154 | false
155 | true
156 |
157 |
158 |
162 |
163 | true
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached
183 |
184 | $(MSBuildProjectFullPath).paket.references
185 |
186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references
187 |
188 | $(MSBuildProjectDirectory)\paket.references
189 |
190 | false
191 | true
192 | true
193 | references-file-or-cache-not-found
194 |
195 |
196 |
197 |
198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)'))
199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)'))
200 | references-file
201 | false
202 |
203 |
204 |
205 |
206 | false
207 |
208 |
209 |
210 |
211 | true
212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths)
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | false
224 | true
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length)
236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])
237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])
238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4])
239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])
240 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6])
241 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7])
242 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[8])
243 |
244 |
245 | %(PaketReferencesFileLinesInfo.PackageVersion)
246 | All
247 | runtime
248 | $(ExcludeAssets);contentFiles
249 | $(ExcludeAssets);build;buildMultitargeting;buildTransitive
250 | %(PaketReferencesFileLinesInfo.Aliases)
251 | true
252 | true
253 |
254 |
255 |
256 |
257 |
258 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0])
268 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1])
269 |
270 |
271 | %(PaketCliToolFileLinesInfo.PackageVersion)
272 |
273 |
274 |
275 |
279 |
280 |
281 |
282 |
283 |
284 | false
285 |
286 |
287 |
288 |
289 |
290 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/>
291 |
292 |
293 |
294 |
295 |
296 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile)
297 | true
298 | false
299 | true
300 | false
301 | true
302 | false
303 | true
304 | false
305 | true
306 | false
307 | true
308 | $(PaketIntermediateOutputPath)\$(Configuration)
309 | $(PaketIntermediateOutputPath)
310 |
311 |
312 |
313 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/>
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
373 |
374 |
423 |
424 |
469 |
470 |
514 |
515 |
558 |
559 |
560 |
561 |
--------------------------------------------------------------------------------
/src/BlackFox.MasterOfFoo/printf.fs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2 |
3 | namespace BlackFox.MasterOfFoo.Core
4 |
5 | open System
6 | open System.IO
7 | open System.Text
8 |
9 | open System.Collections.Concurrent
10 | open System.Globalization
11 | open System.Reflection
12 |
13 | open Microsoft.FSharp.Core
14 | open Microsoft.FSharp.Core.Operators
15 | open Microsoft.FSharp.Collections
16 |
17 | open LanguagePrimitives.IntrinsicOperators
18 |
19 | type objnull = obj
20 | open BlackFox.MasterOfFoo
21 | open BlackFox.MasterOfFoo.FormatFlagsHelpers
22 |
23 | []
24 | module internal PrintfImpl =
25 |
26 | /// Basic idea of implementation:
27 | /// Every Printf.* family should returns curried function that collects arguments and then somehow prints them.
28 | /// Idea - instead of building functions on fly argument by argument we instead introduce some predefined parts and then construct functions from these parts
29 | /// Parts include:
30 | /// Plain ones:
31 | /// 1. Final pieces (1..5) - set of functions with arguments number 1..5.
32 | /// Primary characteristic - these functions produce final result of the *printf* operation
33 | /// 2. Chained pieces (1..5) - set of functions with arguments number 1..5.
34 | /// Primary characteristic - these functions doesn not produce final result by itself, instead they tailed with some another piece (chained or final).
35 | /// Plain parts correspond to simple format specifiers (that are projected to just one parameter of the function, say %d or %s). However we also have
36 | /// format specifiers that can be projected to more than one argument (i.e %a, %t or any simple format specified with * width or precision).
37 | /// For them we add special cases (both chained and final to denote that they can either return value themselves or continue with some other piece)
38 | /// These primitives allow us to construct curried functions with arbitrary signatures.
39 | /// For example:
40 | /// - function that corresponds to %s%s%s%s%s (string -> string -> string -> string -> string -> T) will be represented by one piece final 5.
41 | /// - function that has more that 5 arguments will include chained parts: %s%s%s%s%s%d%s => chained2 -> final 5
42 | /// Primary benefits:
43 | /// 1. creating specialized version of any part requires only one reflection call. This means that we can handle up to 5 simple format specifiers
44 | /// with just one reflection call
45 | /// 2. we can make combinable parts independent from particular printf implementation. Thus final result can be cached and shared.
46 | /// i.e when first call to printf "%s %s" will trigger creation of the specialization. Subsequent calls will pick existing specialization
47 |
48 | /// Used for width and precision to denote that user has specified '*' flag
49 | []
50 | let StarValue = -1
51 | /// Used for width and precision to denote that corresponding value was omitted in format string
52 | []
53 | let NotSpecifiedValue = -2
54 |
55 | /// Set of helpers to parse format string
56 | module private FormatString =
57 |
58 | let intFromString (s: string) (i: byref) =
59 | let mutable res = 0
60 | while (Char.IsDigit s.[i]) do
61 | let n = int s.[i] - int '0'
62 | res <- res * 10 + n
63 | i <- i + 1
64 | res
65 |
66 | let parseFlags (s: string) (i: byref) =
67 | let mutable flags = FormatFlags.None
68 | let mutable fin = false
69 | while not fin do
70 | match s.[i] with
71 | | '0' ->
72 | flags <- flags ||| FormatFlags.PadWithZeros
73 | i <- i + 1
74 | | '+' ->
75 | flags <- flags ||| FormatFlags.PlusForPositives
76 | i <- i + 1
77 | | ' ' ->
78 | flags <- flags ||| FormatFlags.SpaceForPositives
79 | i <- i + 1
80 | | '-' ->
81 | flags <- flags ||| FormatFlags.LeftJustify
82 | i <- i + 1
83 | | _ ->
84 | fin <- true
85 | flags
86 |
87 | let parseWidth (s: string) (i: byref) =
88 | if s.[i] = '*' then
89 | i <- i + 1
90 | StarValue
91 | elif Char.IsDigit s.[i] then
92 | intFromString s (&i)
93 | else
94 | NotSpecifiedValue
95 |
96 | let parsePrecision (s: string) (i: byref) =
97 | if s.[i] = '.' then
98 | if s.[i + 1] = '*' then
99 | i <- i + 2
100 | StarValue
101 | elif Char.IsDigit s.[i + 1] then
102 | i <- i + 1
103 | intFromString s (&i)
104 | else raise (ArgumentException("invalid precision value"))
105 | else
106 | NotSpecifiedValue
107 |
108 | let parseTypeChar (s: string) (i: byref) =
109 | let res = s.[i]
110 | i <- i + 1
111 | res
112 |
113 | let parseInterpolatedHoleDotNetFormat typeChar (s: string) (i: byref) =
114 | if typeChar = 'P' then
115 | if i < s.Length && s.[i] = '(' then
116 | let i2 = s.IndexOf(")", i)
117 | if i2 = -1 then
118 | ValueNone
119 | else
120 | let res = s.[i+1..i2-1]
121 | i <- i2+1
122 | ValueSome res
123 | else
124 | ValueNone
125 | else
126 | ValueNone
127 |
128 | // Skip %P() added for hole in "...%d{x}..."
129 | let skipInterpolationHole typeChar (fmt: string) (i: byref) =
130 | if typeChar <> 'P' then
131 | if i+1 < fmt.Length && fmt.[i] = '%' && fmt.[i+1] = 'P' then
132 | i <- i + 2
133 | if i+1 < fmt.Length && fmt.[i] = '(' && fmt.[i+1] = ')' then
134 | i <- i+2
135 |
136 | let findNextFormatSpecifier (s: string) (i: byref): PrintableElement =
137 | let buf = StringBuilder()
138 | let mutable fin = false
139 | while not fin do
140 | if i >= s.Length then
141 | fin <- true
142 | else
143 | let c = s.[i]
144 | if c = '%' then
145 | if i + 1 < s.Length then
146 | let mutable i2 = i+1
147 | let _ = parseFlags s &i2
148 | let w = parseWidth s &i2
149 | let p = parsePrecision s &i2
150 | let typeChar = parseTypeChar s &i2
151 |
152 | // shortcut for the simplest case
153 | // if typeChar is not % or it has star as width\precision - resort to long path
154 | if typeChar = '%' && not (w = StarValue || p = StarValue) then
155 | buf.Append('%') |> ignore
156 | i <- i2
157 | else
158 | fin <- true
159 | else
160 | raise (ArgumentException("Missing format specifier"))
161 | else
162 | buf.Append c |> ignore
163 | i <- i + 1
164 | PrintableElement(buf.ToString(), PrintableElementType.Direct)
165 |
166 | /// Type of results produced by specialization.
167 | ///
168 | /// This is a function that accepts a thunk to create PrintfEnv on demand (at the very last
169 | /// application of an argument) and returns a concrete instance of an appropriate curried printer.
170 | ///
171 | /// After all arguments are collected, specialization obtains concrete PrintfEnv from the thunk
172 | /// and uses it to output collected data.
173 | ///
174 | /// Note the arguments must be captured in an *immutable* collection. For example consider
175 | /// let f1 = printf "%d%d%d" 3 // activation captures '3' (args --> [3])
176 | /// let f2 = f1 4 // same activation captures 4 (args --> [3;4])
177 | /// let f3 = f1 5 // same activation captures 5 (args --> [3;5])
178 | /// f2 7 // same activation captures 7 (args --> [3;4;7])
179 | /// f3 8 // same activation captures 8 (args --> [3;5;8])
180 | ///
181 | /// If we captured into an mutable array then these would interfere
182 | type PrintfInitial<'State, 'Residue, 'Result> = (unit -> PrintfEnv<'State, 'Residue, 'Result>)
183 | type PrintfFuncFactory<'Printer, 'State, 'Residue, 'Result> =
184 | delegate of objnull list * PrintfInitial<'State, 'Residue, 'Result> -> 'Printer
185 |
186 | []
187 | let MaxArgumentsInSpecialization = 3
188 |
189 | let revToArray extra (args: 'T list) =
190 | // We've reached the end, now fill in the array, reversing steps, avoiding reallocating
191 | let n = args.Length
192 | let res = Array.zeroCreate (n+extra)
193 | let mutable j = 0
194 | for arg in args do
195 | res.[n-j-1] <- arg
196 | j <- j + 1
197 | res
198 |
199 | type Specializations<'State, 'Residue, 'Result>() =
200 |
201 | static member Final0(allSteps) =
202 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial ->
203 | let env = initial()
204 | env.RunSteps(revToArray 0 args, null, allSteps)
205 | )
206 |
207 | static member CaptureFinal1<'A>(allSteps) =
208 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial ->
209 | (fun (arg1: 'A) ->
210 | let env = initial()
211 | let argArray = revToArray 1 args
212 | argArray.[argArray.Length-1] <- box arg1
213 | env.RunSteps(argArray, null, allSteps)
214 | )
215 | )
216 |
217 | static member CaptureFinal2<'A, 'B>(allSteps) =
218 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial ->
219 | (fun (arg1: 'A) (arg2: 'B) ->
220 | let env = initial()
221 | let argArray = revToArray 2 args
222 | argArray.[argArray.Length-1] <- box arg2
223 | argArray.[argArray.Length-2] <- box arg1
224 | env.RunSteps(argArray, null, allSteps)
225 | )
226 | )
227 |
228 | static member CaptureFinal3<'A, 'B, 'C>(allSteps) =
229 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial ->
230 | (fun (arg1: 'A) (arg2: 'B) (arg3: 'C) ->
231 | let env = initial()
232 | let argArray = revToArray 3 args
233 | argArray.[argArray.Length-1] <- box arg3
234 | argArray.[argArray.Length-2] <- box arg2
235 | argArray.[argArray.Length-3] <- box arg1
236 | env.RunSteps(argArray, null, allSteps)
237 | )
238 | )
239 |
240 | static member Capture1<'A, 'Tail>(next: PrintfFuncFactory<_, 'State, 'Residue, 'Result>) =
241 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial ->
242 | (fun (arg1: 'A) ->
243 | let args = (box arg1 :: args)
244 | next.Invoke(args, initial) : 'Tail
245 | )
246 | )
247 |
248 | static member CaptureLittleA<'A, 'Tail>(next: PrintfFuncFactory<_, 'State, 'Residue, 'Result>) =
249 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial ->
250 | (fun (f: 'State -> 'A -> 'Residue) (arg1: 'A) ->
251 | let args = box arg1 :: box (fun s (arg:objnull) -> f s (unbox arg)) :: args
252 | next.Invoke(args, initial) : 'Tail
253 | )
254 | )
255 |
256 | static member Capture2<'A, 'B, 'Tail>(next: PrintfFuncFactory<_, 'State, 'Residue, 'Result>) =
257 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial ->
258 | (fun (arg1: 'A) (arg2: 'B) ->
259 | let args = box arg2 :: box arg1 :: args
260 | next.Invoke(args, initial) : 'Tail
261 | )
262 | )
263 |
264 | static member Capture3<'A, 'B, 'C, 'Tail>(next: PrintfFuncFactory<_, 'State, 'Residue, 'Result>) =
265 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial ->
266 | (fun (arg1: 'A) (arg2: 'B) (arg3: 'C) ->
267 | let args = box arg3 :: box arg2 :: box arg1 :: args
268 | next.Invoke(args, initial) : 'Tail
269 | )
270 | )
271 |
272 | // Special case for format strings containing just one '%d' etc, i.e. StepWithArg then StepString.
273 | // This avoids allocating an argument array, and unfolds the single iteration of RunSteps.
274 | static member OneStepWithArg<'A>(prefix1, conv1, prefix2) =
275 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun _args initial ->
276 | // Note this is the actual computed/stored closure for
277 | // sprintf "prefix1 %d prefix2"
278 | // for any simple format specifiers, where conv1 and conv2 will depend on the format specifiers etc.
279 | (fun (arg1: 'A) ->
280 | let env = initial()
281 | env.WriteSkipEmpty prefix1
282 | env.Write(conv1 (box arg1))
283 | env.WriteSkipEmpty prefix2
284 | env.Finish())
285 | )
286 |
287 | // Special case for format strings containing two simple formats like '%d %s' etc, i.e.
288 | ///StepWithArg then StepWithArg then StepString. This avoids allocating an argument array,
289 | // and unfolds the two iteration of RunSteps.
290 | static member TwoStepWithArg<'A, 'B>(prefix1, conv1, prefix2, conv2, prefix3) =
291 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun _args initial ->
292 | // Note this is the actual computed/stored closure for
293 | // sprintf "prefix1 %d prefix2 %s prefix3"
294 | // for any simple format specifiers, where conv1 and conv2 will depend on the format specifiers etc.
295 | (fun (arg1: 'A) (arg2: 'B) ->
296 | let env = initial()
297 | env.WriteSkipEmpty prefix1
298 | env.Write(conv1 (box arg1))
299 | env.WriteSkipEmpty prefix2
300 | env.Write(conv2 (box arg2))
301 | env.WriteSkipEmpty prefix3
302 | env.Finish())
303 | )
304 |
305 | let inline (===) a b = Object.ReferenceEquals(a, b)
306 |
307 | let inline boolToString v = if v then "true" else "false"
308 |
309 | let inline stringToSafeString v =
310 | match v with
311 | | null -> ""
312 | | _ -> v
313 |
314 | []
315 | let DefaultPrecision = 6
316 |
317 | /// A wrapper struct used to slightly strengthen the types of "ValueConverter" objects produced during composition of
318 | /// the dynamic implementation. These are always functions but sometimes they take one argument, sometimes two.
319 | []
320 | type ValueConverter internal (f: objnull) =
321 | member x.FuncObj = f
322 |
323 | static member inline Make (f: objnull -> string) = ValueConverter(box f)
324 | static member inline Make (f: objnull -> int -> string) = ValueConverter(box f)
325 | static member inline Make (f: objnull -> int-> int -> string) = ValueConverter(box f)
326 |
327 | let getFormatForFloat (ch: char) (prec: int) = ch.ToString() + prec.ToString()
328 |
329 | let normalizePrecision prec = min (max prec 0) 99
330 |
331 | /// Contains helpers to convert printer functions to functions that prints value with respect to specified justification
332 | /// There are two kinds to printers:
333 | /// 'T -> string - converts value to string - used for strings, basic integers etc..
334 | /// string -> 'T -> string - converts value to string with given format string - used by numbers with floating point, typically precision is set via format string
335 | /// To support both categories there are two entry points:
336 | /// - withPadding - adapts first category
337 | /// - withPaddingFormatted - adapts second category
338 | module Padding =
339 |
340 | /// pad here is function that converts T to string with respect of justification
341 | /// basic - function that converts T to string without applying justification rules
342 | /// adaptPaddedFormatted returns boxed function that has various number of arguments depending on if width\precision flags has '*' value
343 | let adaptPaddedFormatted (spec: FormatSpecifier) getFormat (basic: string -> objnull -> string) (pad: string -> int -> objnull -> string) : ValueConverter =
344 | if spec.IsStarWidth then
345 | if spec.IsStarPrecision then
346 | // width=*, prec=*
347 | ValueConverter.Make (fun v width prec ->
348 | let fmt = getFormat (normalizePrecision prec)
349 | pad fmt width v)
350 | else
351 | // width=*, prec=?
352 | let prec = if spec.IsPrecisionSpecified then normalizePrecision spec.Precision else DefaultPrecision
353 | let fmt = getFormat prec
354 | ValueConverter.Make (fun v width ->
355 | pad fmt width v)
356 |
357 | elif spec.IsStarPrecision then
358 | if spec.IsWidthSpecified then
359 | // width=val, prec=*
360 | ValueConverter.Make (fun v prec ->
361 | let fmt = getFormat prec
362 | pad fmt spec.Width v)
363 | else
364 | // width=X, prec=*
365 | ValueConverter.Make (fun v prec ->
366 | let fmt = getFormat prec
367 | basic fmt v)
368 | else
369 | let prec = if spec.IsPrecisionSpecified then normalizePrecision spec.Precision else DefaultPrecision
370 | let fmt = getFormat prec
371 | if spec.IsWidthSpecified then
372 | // width=val, prec=*
373 | ValueConverter.Make (
374 | pad fmt spec.Width)
375 | else
376 | // width=X, prec=*
377 | ValueConverter.Make (
378 | basic fmt)
379 |
380 | /// pad here is function that converts T to string with respect of justification
381 | /// basic - function that converts T to string without applying justification rules
382 | /// adaptPadded returns boxed function that has various number of arguments depending on if width flags has '*' value
383 | let adaptPadded (spec: FormatSpecifier) (basic: objnull -> string) (pad: int -> objnull -> string) : ValueConverter =
384 | if spec.IsStarWidth then
385 | // width=*, prec=?
386 | ValueConverter.Make (fun v width ->
387 | pad width v)
388 | else
389 | if spec.IsWidthSpecified then
390 | // width=val, prec=*
391 | ValueConverter.Make (
392 | pad spec.Width)
393 | else
394 | // width=X, prec=*
395 | ValueConverter.Make (
396 | basic)
397 |
398 | let withPaddingFormatted (spec: FormatSpecifier) getFormat (defaultFormat: string) (f: string -> objnull -> string) left right : ValueConverter =
399 | if not (spec.IsWidthSpecified || spec.IsPrecisionSpecified) then
400 | ValueConverter.Make (f defaultFormat)
401 | else
402 | if isLeftJustify spec.Flags then
403 | adaptPaddedFormatted spec getFormat f left
404 | else
405 | adaptPaddedFormatted spec getFormat f right
406 |
407 | let withPadding (spec: FormatSpecifier) (f: objnull -> string) left right : ValueConverter =
408 | if not spec.IsWidthSpecified then
409 | ValueConverter.Make f
410 | else
411 | if isLeftJustify spec.Flags then
412 | adaptPadded spec f left
413 | else
414 | adaptPadded spec f right
415 |
416 | /// Contains functions to handle left/right justifications for non-numeric types (strings/bools)
417 | module Basic =
418 | let leftJustify (f: objnull -> string) padChar =
419 | fun (w: int) v ->
420 | (f v).PadRight(w, padChar)
421 |
422 | let rightJustify (f: objnull -> string) padChar =
423 | fun (w: int) v ->
424 | (f v).PadLeft(w, padChar)
425 |
426 | let withPadding (spec: FormatSpecifier) f =
427 | let padChar, _ = spec.GetPadAndPrefix false
428 | Padding.withPadding spec f (leftJustify f padChar) (rightJustify f padChar)
429 |
430 | /// Contains functions to handle left/right and no justification case for numbers
431 | module GenericNumber =
432 |
433 | let isPositive (n: obj) =
434 | match n with
435 | | :? int8 as n -> n >= 0y
436 | | :? uint8 -> true
437 | | :? int16 as n -> n >= 0s
438 | | :? uint16 -> true
439 | | :? int32 as n -> n >= 0
440 | | :? uint32 -> true
441 | | :? int64 as n -> n >= 0L
442 | | :? uint64 -> true
443 | | :? nativeint as n -> n >= 0n
444 | | :? unativeint -> true
445 | | :? single as n -> n >= 0.0f
446 | | :? double as n -> n >= 0.0
447 | | :? decimal as n -> n >= 0.0M
448 | | _ -> failwith "isPositive: unreachable"
449 |
450 | /// handles right justification when pad char = '0'
451 | /// this case can be tricky:
452 | /// - negative numbers, -7 should be printed as '-007', not '00-7'
453 | /// - positive numbers when prefix for positives is set: 7 should be '+007', not '00+7'
454 | let rightJustifyWithZeroAsPadChar (str: string) isNumber isPositive w (prefixForPositives: string) =
455 | System.Diagnostics.Debug.Assert(prefixForPositives.Length = 0 || prefixForPositives.Length = 1)
456 | if isNumber then
457 | if isPositive then
458 | prefixForPositives + (if w = 0 then str else str.PadLeft(w - prefixForPositives.Length, '0')) // save space to
459 | else
460 | if str.[0] = '-' then
461 | let str = str.Substring 1
462 | "-" + (if w = 0 then str else str.PadLeft(w - 1, '0'))
463 | else
464 | str.PadLeft(w, '0')
465 | else
466 | str.PadLeft(w, ' ')
467 |
468 | /// handler right justification when pad char = ' '
469 | let rightJustifyWithSpaceAsPadChar (str: string) isNumber isPositive w (prefixForPositives: string) =
470 | System.Diagnostics.Debug.Assert(prefixForPositives.Length = 0 || prefixForPositives.Length = 1)
471 | (if isNumber && isPositive then prefixForPositives + str else str).PadLeft(w, ' ')
472 |
473 | /// handles left justification with formatting with 'G'\'g' - either for decimals or with 'g'\'G' is explicitly set
474 | let leftJustifyWithGFormat (str: string) isNumber isInteger isPositive w (prefixForPositives: string) padChar =
475 | if isNumber then
476 | let str = if isPositive then prefixForPositives + str else str
477 | // NOTE: difference - for 'g' format we use isInt check to detect situations when '5.0' is printed as '5'
478 | // in this case we need to override padding and always use ' ', otherwise we'll produce incorrect results
479 | if isInteger then
480 | str.PadRight(w, ' ') // don't pad integer numbers with '0' when 'g' format is specified (may yield incorrect results)
481 | else
482 | str.PadRight(w, padChar) // non-integer => string representation has point => can pad with any character
483 | else
484 | str.PadRight(w, ' ') // pad NaNs with ' '
485 |
486 | let leftJustifyWithNonGFormat (str: string) isNumber isPositive w (prefixForPositives: string) padChar =
487 | if isNumber then
488 | let str = if isPositive then prefixForPositives + str else str
489 | str.PadRight(w, padChar)
490 | else
491 | str.PadRight(w, ' ') // pad NaNs with ' '
492 |
493 | /// processes given string based depending on values isNumber\isPositive
494 | let noJustificationCore (str: string) isNumber isPositive prefixForPositives =
495 | if isNumber && isPositive then prefixForPositives + str
496 | else str
497 |
498 | /// noJustification handler for f: 'T -> string - basic integer types
499 | let noJustification (f: objnull -> string) (prefix: string) isUnsigned =
500 | if isUnsigned then
501 | fun (v: objnull) -> noJustificationCore (f v) true true prefix
502 | else
503 | fun (v: objnull) -> noJustificationCore (f v) true (isPositive v) prefix
504 |
505 | /// contains functions to handle left/right and no justification case for numbers
506 | module Integer =
507 |
508 | let eliminateNative (v: objnull) =
509 | match v with
510 | | :? nativeint as n ->
511 | if IntPtr.Size = 4 then box (n.ToInt32())
512 | else box (n.ToInt64())
513 | | :? unativeint as n ->
514 | if IntPtr.Size = 4 then box (uint32 (n.ToUInt32()))
515 | else box (uint64 (n.ToUInt64()))
516 | | _ -> v
517 |
518 | let rec toString (v: objnull) =
519 | match v with
520 | | :? int32 as n -> n.ToString(CultureInfo.InvariantCulture)
521 | | :? int64 as n -> n.ToString(CultureInfo.InvariantCulture)
522 | | :? sbyte as n -> n.ToString(CultureInfo.InvariantCulture)
523 | | :? byte as n -> n.ToString(CultureInfo.InvariantCulture)
524 | | :? int16 as n -> n.ToString(CultureInfo.InvariantCulture)
525 | | :? uint16 as n -> n.ToString(CultureInfo.InvariantCulture)
526 | | :? uint32 as n -> n.ToString(CultureInfo.InvariantCulture)
527 | | :? uint64 as n -> n.ToString(CultureInfo.InvariantCulture)
528 | | :? nativeint | :? unativeint -> toString (eliminateNative v)
529 | | _ -> failwith "toString: unreachable"
530 |
531 | let rec toFormattedString fmt (v: obj) =
532 | match v with
533 | | :? int32 as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
534 | | :? int64 as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
535 | | :? sbyte as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
536 | | :? byte as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
537 | | :? int16 as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
538 | | :? uint16 as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
539 | | :? uint32 as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
540 | | :? uint64 as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
541 | | :? nativeint | :? unativeint -> toFormattedString fmt (eliminateNative v)
542 | | _ -> failwith "toFormattedString: unreachable"
543 |
544 | let rec toUnsigned (v: objnull) =
545 | match v with
546 | | :? int32 as n -> box (uint32 n)
547 | | :? int64 as n -> box (uint64 n)
548 | | :? sbyte as n -> box (byte n)
549 | | :? int16 as n -> box (uint16 n)
550 | | :? nativeint | :? unativeint -> toUnsigned (eliminateNative v)
551 | | _ -> v
552 |
553 | /// Left justification handler for f: 'T -> string - basic integer types
554 | let leftJustify isGFormat (f: objnull -> string) (prefix: string) padChar isUnsigned =
555 | if isUnsigned then
556 | if isGFormat then
557 | fun (w: int) (v: objnull) ->
558 | GenericNumber.leftJustifyWithGFormat (f v) true true true w prefix padChar
559 | else
560 | fun (w: int) (v: objnull) ->
561 | GenericNumber.leftJustifyWithNonGFormat (f v) true true w prefix padChar
562 | else
563 | if isGFormat then
564 | fun (w: int) (v: objnull) ->
565 | GenericNumber.leftJustifyWithGFormat (f v) true true (GenericNumber.isPositive v) w prefix padChar
566 | else
567 | fun (w: int) (v: objnull) ->
568 | GenericNumber.leftJustifyWithNonGFormat (f v) true (GenericNumber.isPositive v) w prefix padChar
569 |
570 | /// Right justification handler for f: 'T -> string - basic integer types
571 | let rightJustify f (prefixForPositives: string) padChar isUnsigned =
572 | if isUnsigned then
573 | if padChar = '0' then
574 | fun (w: int) (v: objnull) ->
575 | GenericNumber.rightJustifyWithZeroAsPadChar (f v) true true w prefixForPositives
576 | else
577 | System.Diagnostics.Debug.Assert((padChar = ' '))
578 | fun (w: int) (v: objnull) ->
579 | GenericNumber.rightJustifyWithSpaceAsPadChar (f v) true true w prefixForPositives
580 | else
581 | if padChar = '0' then
582 | fun (w: int) (v: objnull) ->
583 | GenericNumber.rightJustifyWithZeroAsPadChar (f v) true (GenericNumber.isPositive v) w prefixForPositives
584 |
585 | else
586 | System.Diagnostics.Debug.Assert((padChar = ' '))
587 | fun (w: int) v ->
588 | GenericNumber.rightJustifyWithSpaceAsPadChar (f v) true (GenericNumber.isPositive v) w prefixForPositives
589 |
590 | /// Computes a new function from 'f' that wraps the basic conversion given
591 | /// by 'f' with padding for 0, spacing and justification, if the flags specify
592 | /// it. If they don't, f is made into a value converter
593 | let withPadding (spec: FormatSpecifier) isUnsigned (f: objnull -> string) =
594 | let allowZeroPadding = not (isLeftJustify spec.Flags) || spec.IsDecimalFormat
595 | let padChar, prefix = spec.GetPadAndPrefix allowZeroPadding
596 | Padding.withPadding spec
597 | (GenericNumber.noJustification f prefix isUnsigned)
598 | (leftJustify spec.IsGFormat f prefix padChar isUnsigned)
599 | (rightJustify f prefix padChar isUnsigned)
600 |
601 | let getValueConverter (spec: FormatSpecifier) : ValueConverter =
602 | match spec.TypeChar with
603 | | 'd' | 'i' ->
604 | withPadding spec false toString
605 | | 'u' ->
606 | withPadding spec true (toUnsigned >> toString)
607 | | 'x' ->
608 | withPadding spec true (toFormattedString "x")
609 | | 'X' ->
610 | withPadding spec true (toFormattedString "X")
611 | | 'o' ->
612 | withPadding spec true (fun (v: objnull) ->
613 | // Convert.ToInt64 throws for uint64 with values above int64 range so cast directly
614 | match toUnsigned v with
615 | | :? uint64 as u -> Convert.ToString(int64 u, 8)
616 | | u -> Convert.ToString(Convert.ToInt64 u, 8))
617 | | 'B' ->
618 | withPadding spec true (fun (v: objnull) ->
619 | match toUnsigned v with
620 | | :? uint64 as u -> Convert.ToString(int64 u, 2)
621 | | u -> Convert.ToString(Convert.ToInt64 u, 2))
622 | | _ -> invalidArg "spec" "Invalid integer format"
623 |
624 | module FloatAndDecimal =
625 |
626 | let rec toFormattedString fmt (v: obj) =
627 | match v with
628 | | :? single as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
629 | | :? double as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
630 | | :? decimal as n -> n.ToString(fmt, CultureInfo.InvariantCulture)
631 | | _ -> failwith "toFormattedString: unreachable"
632 |
633 | let isNumber (x: obj) =
634 | match x with
635 | | :? single as x ->
636 | not (Single.IsPositiveInfinity(x)) &&
637 | not (Single.IsNegativeInfinity(x)) &&
638 | not (Single.IsNaN(x))
639 | | :? double as x ->
640 | not (Double.IsPositiveInfinity(x)) &&
641 | not (Double.IsNegativeInfinity(x)) &&
642 | not (Double.IsNaN(x))
643 | | :? decimal -> true
644 | | _ -> failwith "isNumber: unreachable"
645 |
646 | let isInteger (n: obj) =
647 | match n with
648 | | :? single as n -> n % 1.0f = 0.0f
649 | | :? double as n -> n % 1. = 0.
650 | | :? decimal as n -> n % 1.0M = 0.0M
651 | | _ -> failwith "isInteger: unreachable"
652 |
653 | let noJustification (prefixForPositives: string) =
654 | fun (fmt: string) (v: obj) ->
655 | GenericNumber.noJustificationCore (toFormattedString fmt v) (isNumber v) (GenericNumber.isPositive v) prefixForPositives
656 |
657 | let leftJustify isGFormat (prefix: string) padChar =
658 | if isGFormat then
659 | fun (fmt: string) (w: int) (v: obj) ->
660 | GenericNumber.leftJustifyWithGFormat (toFormattedString fmt v) (isNumber v) (isInteger v) (GenericNumber.isPositive v) w prefix padChar
661 | else
662 | fun (fmt: string) (w: int) (v: obj) ->
663 | GenericNumber.leftJustifyWithNonGFormat (toFormattedString fmt v) (isNumber v) (GenericNumber.isPositive v) w prefix padChar
664 |
665 | let rightJustify (prefixForPositives: string) padChar =
666 | if padChar = '0' then
667 | fun (fmt: string) (w: int) (v: obj) ->
668 | GenericNumber.rightJustifyWithZeroAsPadChar (toFormattedString fmt v) (isNumber v) (GenericNumber.isPositive v) w prefixForPositives
669 | else
670 | System.Diagnostics.Debug.Assert((padChar = ' '))
671 | fun (fmt: string) (w: int) (v: obj) ->
672 | GenericNumber.rightJustifyWithSpaceAsPadChar (toFormattedString fmt v) (isNumber v) (GenericNumber.isPositive v) w prefixForPositives
673 |
674 | let withPadding (spec: FormatSpecifier) getFormat defaultFormat =
675 | let padChar, prefix = spec.GetPadAndPrefix true
676 | Padding.withPaddingFormatted spec getFormat defaultFormat
677 | (noJustification prefix)
678 | (leftJustify spec.IsGFormat prefix padChar)
679 | (rightJustify prefix padChar)
680 |
681 | type ObjectPrinter =
682 |
683 | static member ObjectToString(spec: FormatSpecifier) : ValueConverter =
684 | Basic.withPadding spec (fun (v: objnull) ->
685 | match v with
686 | | null -> ""
687 | | x -> x.ToString())
688 |
689 | /// Convert an interpoland to a string
690 | static member InterpolandToString(spec: FormatSpecifier) : ValueConverter =
691 | let fmt =
692 | match spec.InteropHoleDotNetFormat with
693 | | ValueNone -> null
694 | | ValueSome fmt -> "{0:" + fmt + "}"
695 | Basic.withPadding spec (fun (vobj: objnull) ->
696 | match vobj with
697 | | null -> ""
698 | | x ->
699 | match fmt with
700 | | null -> x.ToString()
701 | | fmt -> String.Format(fmt, x))
702 |
703 | static member GenericToStringCore(v: 'T, opts: FormatOptions, bindingFlags) =
704 | let vty =
705 | match box v with
706 | | null -> typeof<'T>
707 | | _ -> v.GetType()
708 | Display.anyToStringForPrintf opts bindingFlags (v, vty)
709 |
710 | static member GenericToString<'T>(spec: FormatSpecifier) : ValueConverter =
711 | let bindingFlags =
712 | if isPlusForPositives spec.Flags then BindingFlags.Public ||| BindingFlags.NonPublic
713 | else BindingFlags.Public
714 |
715 | let useZeroWidth = isPadWithZeros spec.Flags
716 | let opts =
717 | let o = FormatOptions.Default
718 | let o =
719 | if useZeroWidth then { o with PrintWidth = 0}
720 | elif spec.IsWidthSpecified then { o with PrintWidth = spec.Width}
721 | else o
722 | if spec.IsPrecisionSpecified then { o with PrintSize = spec.Precision}
723 | else o
724 |
725 | match spec.IsStarWidth, spec.IsStarPrecision with
726 | | true, true ->
727 | ValueConverter.Make (fun (vobj: objnull) (width: int) (prec: int) ->
728 | let v = unbox<'T> vobj
729 | let opts = { opts with PrintSize = prec }
730 | let opts = if not useZeroWidth then { opts with PrintWidth = width} else opts
731 | ObjectPrinter.GenericToStringCore(v, opts, bindingFlags)
732 | )
733 |
734 | | true, false ->
735 | ValueConverter.Make (fun (vobj: objnull) (width: int) ->
736 | let v = unbox<'T> vobj
737 | let opts = if not useZeroWidth then { opts with PrintWidth = width} else opts
738 | ObjectPrinter.GenericToStringCore(v, opts, bindingFlags))
739 |
740 | | false, true ->
741 | ValueConverter.Make (fun (vobj: objnull) (prec: int) ->
742 | let v = unbox<'T> vobj
743 | let opts = { opts with PrintSize = prec }
744 | ObjectPrinter.GenericToStringCore(v, opts, bindingFlags) )
745 |
746 | | false, false ->
747 | ValueConverter.Make (fun (vobj: objnull) ->
748 | let v = unbox<'T> vobj
749 | ObjectPrinter.GenericToStringCore(v, opts, bindingFlags))
750 |
751 | let basicFloatToString spec =
752 | let defaultFormat = getFormatForFloat spec.TypeChar DefaultPrecision
753 | FloatAndDecimal.withPadding spec (getFormatForFloat spec.TypeChar) defaultFormat
754 |
755 | let private AllStatics = BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Static
756 |
757 | let mi_GenericToString = typeof.GetMethod("GenericToString", AllStatics)
758 |
759 | /// **MasterOfFoo specifics**
760 | ///
761 | /// The code of this function is the code of `getValueConverter` renamed to `getValueConverterCore` as we need a
762 | /// different return type for converters.
763 | let private getValueConverterCore (ty: Type) (spec: FormatSpecifier) : ValueConverter =
764 | match spec.TypeChar with
765 | | 'b' ->
766 | Basic.withPadding spec (unbox >> boolToString)
767 | | 's' ->
768 | Basic.withPadding spec (unbox >> stringToSafeString)
769 | | 'c' ->
770 | Basic.withPadding spec (fun (c: objnull) -> (unbox c).ToString())
771 | | 'M' ->
772 | FloatAndDecimal.withPadding spec (fun _ -> "G") "G" // %M ignores precision
773 | | 'd' | 'i' | 'u' | 'B' | 'o' | 'x' | 'X' ->
774 | Integer.getValueConverter spec
775 | | 'e' | 'E'
776 | | 'f' | 'F'
777 | | 'g' | 'G' ->
778 | basicFloatToString spec
779 | | 'A' ->
780 | let mi = mi_GenericToString.MakeGenericMethod ty
781 | mi.Invoke(null, [| box spec |]) |> unbox
782 | | 'O' ->
783 | ObjectPrinter.ObjectToString(spec)
784 | | 'P' ->
785 | ObjectPrinter.InterpolandToString(spec)
786 | | _ ->
787 | raise (ArgumentException(sprintf "Bad format specifier: %c" spec.TypeChar))
788 |
789 | /// **MasterOfFoo specifics**
790 | ///
791 | /// This type is equivalent to the `ValueConverter` type from the F# source code but with `PrintableElement` as
792 | /// the final result instead of `string`.
793 | []
794 | type PrintableValueConverter private (f: obj) =
795 | member x.FuncObj = f
796 |
797 | static member inline Make<'t> (f: obj -> PrintableElement) = PrintableValueConverter(box f)
798 | static member inline Make<'t> (f: obj -> int -> PrintableElement) = PrintableValueConverter(box f)
799 | static member inline Make<'t> (f: obj -> int-> int -> PrintableElement) = PrintableValueConverter(box f)
800 |
801 | /// **MasterOfFoo specifics**
802 | ///
803 | /// As our converters need to return a `PrintableElement` instead of a `string`, we need to adapt the
804 | /// `getValueConverter` by guessing how many arguments the function will take (Depending on if star width and/or
805 | /// star precision are used), generate the correct function and box it.
806 | let getValueConverter (ty : Type) (spec : FormatSpecifier) : PrintableValueConverter =
807 | let et = PrintableElementType.FromFormatSpecifier
808 | let realUntyped = getValueConverterCore ty spec
809 | if spec.IsStarWidth && spec.IsStarPrecision then
810 | let real = realUntyped.FuncObj :?> (obj -> int -> int -> string)
811 | PrintableValueConverter.Make(fun (x: obj) width prec ->
812 | let printer = fun () -> real x width prec
813 | PrintableElement(printer, x, et, ty, spec, width, prec))
814 | else if spec.IsStarWidth || spec.IsStarPrecision then
815 | let real = realUntyped.FuncObj :?> (obj -> int -> string)
816 | if spec.IsStarWidth then
817 | PrintableValueConverter.Make(fun (x: obj) width ->
818 | let printer = fun () -> real x width
819 | PrintableElement(printer, x, et, ty, spec, width, NotSpecifiedValue))
820 | else
821 | PrintableValueConverter.Make(fun (x: obj) prec ->
822 | let printer = fun () -> real x prec
823 | PrintableElement(printer, x, et, ty, spec, NotSpecifiedValue, prec))
824 | else
825 | let real = realUntyped.FuncObj :?> (obj -> string)
826 | PrintableValueConverter.Make(fun (x: obj) ->
827 | let printer = fun () -> real x
828 | PrintableElement(printer, x, et, ty, spec, NotSpecifiedValue, NotSpecifiedValue))
829 |
830 | let extractCurriedArguments (ty: Type) n =
831 | System.Diagnostics.Debug.Assert(n = 1 || n = 2 || n = 3, "n = 1 || n = 2 || n = 3")
832 | let buf = Array.zeroCreate n
833 | let rec go (ty: Type) i =
834 | if i < n then
835 | match ty.GetGenericArguments() with
836 | | [| argTy; retTy|] ->
837 | buf.[i] <- argTy
838 | go retTy (i + 1)
839 | | _ -> failwith (String.Format("Expected function with {0} arguments", n))
840 | else
841 | System.Diagnostics.Debug.Assert((i = n), "i = n")
842 | (buf, ty)
843 | go ty 0
844 |
845 | let MAX_CAPTURE = 3
846 |
847 | /// Parses format string and creates resulting step list and printer factory function.
848 | []
849 | type FormatParser<'Printer, 'State, 'Residue, 'Result>(fmt: string) =
850 |
851 | let buildCaptureFunc (spec: FormatSpecifier, allSteps, argTys: Type array, retTy, nextInfo) =
852 | let (next:obj, nextCanCombine: bool, nextArgTys: Type array, nextRetTy, nextNextOpt) = nextInfo
853 | assert (argTys.Length > 0)
854 |
855 | // See if we can compress a capture to a multi-capture
856 | // CaptureN + Final --> CaptureFinalN
857 | // Capture1 + Capture1 --> Capture2
858 | // Capture1 + Capture2 --> Capture3
859 | // Capture2 + Capture1 --> Capture3
860 | match argTys.Length, nextArgTys.Length with
861 | | _ when spec.TypeChar = 'a' ->
862 | // %a has an existential type which must be converted to obj
863 | assert (argTys.Length = 2)
864 | let captureMethName = "CaptureLittleA"
865 | let mi = typeof>.GetMethod(captureMethName, AllStatics)
866 | let mi = mi.MakeGenericMethod([| argTys.[1]; retTy |])
867 | let factoryObj = mi.Invoke(null, [| next |])
868 | factoryObj, false, argTys, retTy, None
869 |
870 | | n1, n2 when nextCanCombine && n1 + n2 <= MAX_CAPTURE ->
871 | // 'next' is thrown away on this path and replaced by a combined Capture
872 | let captureCount = n1 + n2
873 | let combinedArgTys = Array.append argTys nextArgTys
874 | match nextNextOpt with
875 | | None ->
876 | let captureMethName = "CaptureFinal" + string captureCount
877 | let mi = typeof>.GetMethod(captureMethName, AllStatics)
878 | let mi = mi.MakeGenericMethod(combinedArgTys)
879 | let factoryObj = mi.Invoke(null, [| allSteps |])
880 | factoryObj, true, combinedArgTys, nextRetTy, None
881 | | Some nextNext ->
882 | let captureMethName = "Capture" + string captureCount
883 | let mi = typeof>.GetMethod(captureMethName, AllStatics)
884 | let mi = mi.MakeGenericMethod(Array.append combinedArgTys [| nextRetTy |])
885 | let factoryObj = mi.Invoke(null, [| nextNext |])
886 | factoryObj, true, combinedArgTys, nextRetTy, nextNextOpt
887 |
888 | | captureCount, _ ->
889 | let captureMethName = "Capture" + string captureCount
890 | let mi = typeof>.GetMethod(captureMethName, AllStatics)
891 | let mi = mi.MakeGenericMethod(Array.append argTys [| retTy |])
892 | let factoryObj = mi.Invoke(null, [| next |])
893 | factoryObj, true, argTys, retTy, Some next
894 |
895 | let buildStep (spec: FormatSpecifier) (argTys: Type array) prefix =
896 | if spec.TypeChar = 'a' then
897 | StepLittleA prefix
898 | elif spec.TypeChar = 't' then
899 | StepLittleT prefix
900 | elif spec.IsStarPrecision || spec.IsStarWidth then
901 | let isTwoStar = (spec.IsStarWidth = spec.IsStarPrecision)
902 | match isTwoStar, spec.TypeChar with
903 | | false, '%' -> StepPercentStar1 prefix
904 | | true, '%' -> StepPercentStar2 prefix
905 | | _ ->
906 | // For curried interpolated string format processing, the static types of the '%A' arguments
907 | // are provided via the argument typed extracted from the curried function. They are known on first phase.
908 | let argTy = match argTys with null -> typeof | _ -> argTys.[argTys.Length - 1]
909 | let conv = getValueConverter argTy spec
910 | if isTwoStar then
911 | let convFunc = conv.FuncObj :?> (objnull -> int -> int -> PrintableElement)
912 | StepStar2 (prefix, convFunc)
913 | else
914 | let convFunc = conv.FuncObj :?> (objnull -> int -> PrintableElement)
915 | StepStar1 (prefix, convFunc)
916 | else
917 | // For interpolated string format processing, the static types of the '%A' arguments
918 | // are provided via CaptureTypes and are only known on second phase.
919 | match argTys with
920 | | null when spec.TypeChar = 'A' ->
921 | let convFunc arg argTy =
922 | let mi = mi_GenericToString.MakeGenericMethod [| argTy |]
923 | let f = mi.Invoke(null, [| box spec |]) :?> ValueConverter
924 | let f2 = f.FuncObj :?> (objnull -> string)
925 | let printer = fun () -> f2 arg
926 | PrintableElement(
927 | printer,
928 | arg,
929 | PrintableElementType.FromFormatSpecifier,
930 | argTy,
931 | spec,
932 | NotSpecifiedValue, NotSpecifiedValue)
933 |
934 | StepWithTypedArg (prefix, convFunc)
935 |
936 | | _ ->
937 | // For curried interpolated string format processing, the static types of the '%A' arguments
938 | // are provided via the argument typed extracted from the curried function. They are known on first phase.
939 | let argTy = match argTys with null -> typeof | _ -> argTys.[0]
940 | let conv = getValueConverter argTy spec
941 | let convFunc = conv.FuncObj :?> (objnull -> PrintableElement)
942 | StepWithArg (prefix, convFunc)
943 |
944 | let parseSpec (i: byref) =
945 | i <- i + 1
946 | let flags = FormatString.parseFlags fmt &i
947 | let width = FormatString.parseWidth fmt &i
948 | let precision = FormatString.parsePrecision fmt &i
949 | let typeChar = FormatString.parseTypeChar fmt &i
950 | let interpHoleDotnetFormat = FormatString.parseInterpolatedHoleDotNetFormat typeChar fmt &i
951 |
952 | // Skip %P insertion points added after %d{...} etc. in interpolated strings
953 | FormatString.skipInterpolationHole typeChar fmt &i
954 |
955 | let spec =
956 | { TypeChar = typeChar
957 | Precision = precision
958 | Flags = flags
959 | Width = width
960 | InteropHoleDotNetFormat = interpHoleDotnetFormat }
961 | spec
962 |
963 | // The steps, populated on-demand. This is for the case where the string is being used
964 | // with interpolands captured in the Format object, including the %A capture types.
965 | //
966 | // We may initialize this twice, but the assignment is atomic and the computation will give functionally
967 | // identical results each time, so it is ok.
968 | let mutable stepsForCapturedFormat = Unchecked.defaultof<_>
969 |
970 | // The function factory, populated on-demand, for the case where the string is being used to make a curried function for printf.
971 | //
972 | // We may initialize this twice, but the assignment is atomic and the computation will give functionally
973 | // identical results each time, so it is ok.
974 | let mutable factory = Unchecked.defaultof>
975 | let mutable printer = Unchecked.defaultof<'Printer>
976 |
977 | // The function factory, populated on-demand.
978 | //
979 | // We may initialize this twice, but the assignment is atomic and the computation will give functionally
980 | // identical results each time, so it is ok.
981 | let mutable stringCount = 0
982 |
983 | // A simplified parser. For the case where the string is being used with interpolands captured in the Format object.
984 | let rec parseAndCreateStepsForCapturedFormatAux steps (prefix: PrintableElement) (i: byref) =
985 | if i >= fmt.Length then
986 | let step = StepString(prefix)
987 | let allSteps = revToArray 1 steps
988 | allSteps.[allSteps.Length-1] <- step
989 | stringCount <- Step.BlockCount allSteps
990 | stepsForCapturedFormat <- allSteps
991 | else
992 | let spec = parseSpec &i
993 | let suffix = FormatString.findNextFormatSpecifier fmt &i
994 | let step = buildStep spec null prefix
995 | parseAndCreateStepsForCapturedFormatAux (step::steps) suffix &i
996 |
997 | let parseAndCreateStepsForCapturedFormat () =
998 | let mutable i = 0
999 | let prefix = FormatString.findNextFormatSpecifier fmt &i
1000 | parseAndCreateStepsForCapturedFormatAux [] prefix &i
1001 |
1002 | /// The more advanced parser which both builds the steps (with %A types extracted from the funcTy),
1003 | /// and produces a curried function value of the right type guided by funcTy
1004 | let rec parseAndCreateFuncFactoryAux steps (prefix: PrintableElement) (funcTy: Type) (i: byref) =
1005 |
1006 | if i >= fmt.Length then
1007 | let step = StepString(prefix)
1008 | let allSteps = revToArray 1 steps
1009 | allSteps.[allSteps.Length-1] <- step
1010 | let last = Specializations<'State, 'Residue, 'Result>.Final0(allSteps)
1011 | stringCount <- Step.BlockCount allSteps
1012 | let nextInfo = (box last, true, [| |], funcTy, None)
1013 | (allSteps, nextInfo)
1014 | else
1015 | assert (fmt.[i] = '%')
1016 | let spec = parseSpec &i
1017 | let suffix = FormatString.findNextFormatSpecifier fmt &i
1018 | let n = spec.ArgCount
1019 | let (argTys, retTy) = extractCurriedArguments funcTy n
1020 | let step = buildStep spec argTys prefix
1021 | let (allSteps, nextInfo) = parseAndCreateFuncFactoryAux (step::steps) suffix retTy &i
1022 | let nextInfoNew = buildCaptureFunc (spec, allSteps, argTys, retTy, nextInfo)
1023 | (allSteps, nextInfoNew)
1024 |
1025 | let parseAndCreateFunctionFactory () =
1026 | let funcTy = typeof<'Printer>
1027 |
1028 | // Find the first format specifier
1029 | let mutable i = 0
1030 | let prefix = FormatString.findNextFormatSpecifier fmt &i
1031 |
1032 | let (allSteps, (factoryObj, _, combinedArgTys, _, _)) = parseAndCreateFuncFactoryAux [] prefix funcTy &i
1033 |
1034 | // If there are no format specifiers then take a simple path
1035 | match allSteps with
1036 | | [| StepString prefix |] ->
1037 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun _args initial ->
1038 | let env = initial()
1039 | env.WriteSkipEmpty prefix
1040 | env.Finish()
1041 | ) |> box
1042 |
1043 | // If there is one simple format specifier then we can create an even better factory function
1044 | | [| StepWithArg (prefix1, conv1); StepString prefix2 |] ->
1045 | let captureMethName = "OneStepWithArg"
1046 | let mi = typeof>.GetMethod(captureMethName, AllStatics)
1047 | let mi = mi.MakeGenericMethod(combinedArgTys)
1048 | let factoryObj = mi.Invoke(null, [| box prefix1; box conv1; box prefix2 |])
1049 | factoryObj
1050 |
1051 | // If there are two simple format specifiers then we can create an even better factory function
1052 | | [| StepWithArg (prefix1, conv1); StepWithArg (prefix2, conv2); StepString prefix3 |] ->
1053 | let captureMethName = "TwoStepWithArg"
1054 | let mi = typeof>.GetMethod(captureMethName, AllStatics)
1055 | let mi = mi.MakeGenericMethod(combinedArgTys)
1056 | let factoryObj = mi.Invoke(null, [| box prefix1; box conv1; box prefix2; box conv2; box prefix3 |])
1057 | factoryObj
1058 |
1059 | | _ ->
1060 | factoryObj
1061 |
1062 | /// The format string, used to help identify the cache entry (the cache index types are taken
1063 | /// into account as well).
1064 | member _.FormatString = fmt
1065 |
1066 | /// The steps involved in executing the format string when interpolands are captured
1067 | ///
1068 | /// If %A patterns are involved these steps are only accurate when the %A capture types
1069 | /// are given in the format string through interpolation capture.
1070 | member _.GetStepsForCapturedFormat() =
1071 | match stepsForCapturedFormat with
1072 | | null -> parseAndCreateStepsForCapturedFormat ()
1073 | | _ -> ()
1074 | stepsForCapturedFormat
1075 |
1076 | /// The number of strings produced for a sprintf
1077 | member _.BlockCount = stringCount
1078 |
1079 | /// The factory function used to generate the result or the resulting function.
1080 | member _.GetCurriedPrinterFactory() =
1081 | match box factory with
1082 | | null ->
1083 | let factoryObj = parseAndCreateFunctionFactory ()
1084 | let p = (factoryObj :?> PrintfFuncFactory<'Printer, 'State, 'Residue, 'Result>)
1085 | // We may initialize this twice, but the assignment is atomic and the computation will give functionally
1086 | // identical results each time it is ok
1087 | factory <- p
1088 | p
1089 | | _ -> factory
1090 |
1091 | /// 2-level cache, keyed by format string and index types
1092 | type Cache<'Printer, 'State, 'Residue, 'Result>() =
1093 |
1094 | /// 1st level cache (type-indexed). Stores last value that was consumed by the current thread in
1095 | /// thread-static field thus providing shortcuts for scenarios when printf is called in tight loop.
1096 | []
1097 | static val mutable private mostRecent: FormatParser<'Printer, 'State, 'Residue, 'Result>
1098 |
1099 | // 2nd level cache (type-indexed). Dictionary that maps format string to the corresponding cache entry
1100 | static let mutable dict : ConcurrentDictionary> = null
1101 |
1102 | static member GetParser(format: Format<'Printer, 'State, 'Residue, 'Result>) =
1103 | let recent = Cache<'Printer, 'State, 'Residue, 'Result>.mostRecent
1104 | let fmt = format.Value
1105 | if isNull recent then
1106 | let parser = FormatParser(fmt)
1107 | Cache<'Printer, 'State, 'Residue, 'Result>.mostRecent <- parser
1108 | parser
1109 | elif fmt.Equals recent.FormatString then
1110 | recent
1111 | else
1112 | // Initialize the 2nd level cache if necessary. Note there's a race condition but it doesn't
1113 | // matter if we initialize these values twice (and lose one entry)
1114 | if isNull dict then
1115 | dict <- ConcurrentDictionary<_,_>()
1116 |
1117 | let parser =
1118 | match dict.TryGetValue(fmt) with
1119 | | true, res -> res
1120 | | _ ->
1121 | let parser = FormatParser(fmt)
1122 | // There's a race condition - but the computation is functional and it doesn't matter if we do it twice
1123 | dict.TryAdd(fmt, parser) |> ignore
1124 | parser
1125 | Cache<'Printer, 'State, 'Residue, 'Result>.mostRecent <- parser
1126 | parser
1127 |
--------------------------------------------------------------------------------