├── .config └── dotnet-tools.json ├── .devcontainer ├── Dockerfile ├── devcontainer.json ├── install-dotnets.sh └── settings.vscode.json ├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build.yml ├── .gitignore ├── .paket └── Paket.Restore.targets ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── Directory.Build.props ├── LICENSE.md ├── README.md ├── TheAngryByrd.TypeSafeInternals.sln ├── build.cmd ├── build.sh ├── build ├── build.fs ├── build.fsproj └── paket.references ├── docsSrc ├── Explanations │ └── Background.md ├── How_Tos │ ├── Doing_A_Thing.md │ └── Doing_Another_Thing.md ├── Tutorials │ └── Getting_Started.md ├── content │ ├── cleanups.js │ ├── hotload.js │ ├── style.css │ ├── submenu.js │ ├── themes.js │ ├── tips.js │ ├── toggle-bootstrap-dark.min.css │ └── toggle-bootstrap.min.css ├── files │ └── placeholder.md └── index.md ├── docsTool ├── CLI.fs ├── Prelude.fs ├── Program.fs ├── README.md ├── WebServer.fs ├── docsTool.fsproj ├── paket.references └── templates │ ├── helpers.fs │ ├── master.fs │ ├── modules.fs │ ├── namespaces.fs │ ├── nav.fs │ ├── partMembers.fs │ ├── partNested.fs │ └── types.fs ├── global.json ├── paket.dependencies ├── paket.lock ├── src ├── Directory.Build.props ├── TheAngryByrd.Myriad.Plugins.TypeSafeInternals │ ├── AssemblyInfo.fs │ ├── TheAngryByrd.Myriad.Plugins.TypeSafeInternals.fs │ ├── TheAngryByrd.Myriad.Plugins.TypeSafeInternals.fsproj │ ├── build │ │ ├── TheAngryByrd.Myriad.Plugins.TypeSafeInternals.InTest.props │ │ └── TheAngryByrd.Myriad.Plugins.TypeSafeInternals.props │ └── paket.references └── TheAngryByrd.TypeSafeInternals │ ├── AssemblyInfo.fs │ ├── Library.fs │ ├── TheAngryByrd.TypeSafeInternals.fsproj │ └── paket.references └── tests ├── Directory.Build.props ├── TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests ├── AssemblyInfo.fs ├── Generated.fs ├── Main.fs ├── PrivateDlls.txt ├── Tests.fs ├── TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests.fsproj ├── myriad.toml └── paket.references └── TheAngryByrd.TypeSafeInternals.Tests ├── AssemblyInfo.fs ├── Main.fs ├── Tests.fs ├── TheAngryByrd.TypeSafeInternals.Tests.fsproj └── paket.references /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-reportgenerator-globaltool": { 6 | "version": "4.2.15", 7 | "commands": [ 8 | "reportgenerator" 9 | ] 10 | }, 11 | "paket": { 12 | "version": "6.2.1", 13 | "commands": [ 14 | "paket" 15 | ] 16 | }, 17 | "fcswatch-cli": { 18 | "version": "0.7.14", 19 | "commands": [ 20 | "fcswatch" 21 | ] 22 | }, 23 | "fsharp-analyzers": { 24 | "version": "0.7.0", 25 | "commands": [ 26 | "fsharp-analyzers" 27 | ] 28 | }, 29 | "fantomas": { 30 | "version": "5.0.1", 31 | "commands": [ 32 | "fantomas" 33 | ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster-slim 2 | 3 | 4 | RUN apt-get update \ 5 | && apt-get install -y --no-install-recommends \ 6 | ca-certificates \ 7 | \ 8 | # .NET Core dependencies 9 | libc6 \ 10 | libgcc1 \ 11 | libgssapi-krb5-2 \ 12 | libicu63 \ 13 | libssl1.1 \ 14 | libstdc++6 \ 15 | zlib1g \ 16 | curl \ 17 | git \ 18 | procps \ 19 | wget \ 20 | && rm -rf /var/lib/apt/lists/* 21 | 22 | 23 | ENV \ 24 | # Enable detection of running in a container 25 | DOTNET_RUNNING_IN_CONTAINER=true \ 26 | DOTNET_INSTALL_DIR=/usr/share/dotnet/ \ 27 | DOTNET_ROOT=/usr/share/dotnet/ 28 | 29 | COPY ./.devcontainer/install-dotnets.sh global.json* . 30 | 31 | RUN /bin/bash install-dotnets.sh 32 | 33 | ENV PATH="$DOTNET_ROOT:${PATH}" 34 | RUN ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet 35 | 36 | RUN dotnet --info 37 | 38 | # Copy endpoint specific user settings into container to specify 39 | # .NET Core should be used as the runtime. 40 | COPY ./.devcontainer/settings.vscode.json /root/.vscode-remote/data/Machine/settings.json 41 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dotnet", 3 | // Set the build context one level higher so we can grab metadata like global.json 4 | "context": "..", 5 | "dockerFile": "Dockerfile", 6 | "forwardPorts": [ 7 | 0 8 | ], 9 | "extensions": [ 10 | "ionide.ionide-fsharp", 11 | "ms-dotnettools.csharp", 12 | "editorconfig.editorconfig", 13 | "ionide.ionide-paket", 14 | "ionide.ionide-fake" 15 | ] 16 | } -------------------------------------------------------------------------------- /.devcontainer/install-dotnets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # downloads installer script https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script 4 | curl -SL --output dotnet-install.sh https://dot.net/v1/dotnet-install.sh 5 | 6 | 7 | # Attempt to install via global.json first 8 | FILE=global.json 9 | if test -f "$FILE"; then 10 | echo "installing dotnet via $FILE" 11 | /bin/bash dotnet-install.sh --verbose --jsonfile $FILE 12 | fi 13 | 14 | 15 | # Add additional versions if required 16 | DOTNET_VERSIONS=( 17 | # 'latest' 18 | '5.0.100' 19 | ) 20 | for version in ${DOTNET_VERSIONS[@]}; do 21 | echo "installing dotnet $version" 22 | /bin/bash dotnet-install.sh --verbose --version $version 23 | done 24 | -------------------------------------------------------------------------------- /.devcontainer/settings.vscode.json: -------------------------------------------------------------------------------- 1 | { 2 | "FSharp.fsacRuntime":"netcore", 3 | "FSharp.enableAnalyzers": true, 4 | "FSharp.analyzersPath": [ 5 | "./packages/analyzers" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: 2 | http://EditorConfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Default settings: 8 | # A newline ending every file 9 | # Use 4 spaces as indentation 10 | [*] 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{fs,fsi,fsx,config}] 16 | # https://fsprojects.github.io/fantomas/docs/end-users/Configuration.html 17 | charset = utf-8 18 | trim_trailing_whitespace = true 19 | max_line_length=100 20 | fsharp_multiline_block_brackets_on_same_column=true 21 | fsharp_experimental_stroustrup_style=true 22 | fsharp_keep_max_number_of_blank_lines=2 23 | fsharp_max_array_or_list_number_of_items=1 24 | fsharp_array_or_list_multiline_formatter=number_of_items 25 | fsharp_max_infix_operator_expression=10 26 | fsharp_multi_line_lambda_closing_newline=true 27 | 28 | 29 | [paket.*] 30 | trim_trailing_whitespace = true 31 | indent_size = 2 32 | 33 | [*.paket.references] 34 | trim_trailing_whitespace = true 35 | indent_size = 2 36 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # This file contains a list of git hashes of revisions to be ignored by git 2 | # These revisions are considered "unimportant" in 3 | # that they are unlikely to be what you are interested in when blaming. 4 | # Like formatting with Fantomas 5 | # https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view 6 | # Add formatting commits here 7 | 6f419bbfcba8143cabe0bf4823bb8de65572d166 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp text=auto eol=lf 6 | *.vb diff=csharp text=auto eol=lf 7 | *.fs diff=csharp text=auto eol=lf 8 | *.fsi diff=csharp text=auto eol=lf 9 | *.fsx diff=csharp text=auto eol=lf 10 | *.sln text eol=crlf merge=union 11 | *.csproj merge=union 12 | *.vbproj merge=union 13 | *.fsproj merge=union 14 | *.dbproj merge=union 15 | *.sh text eol=lf 16 | 17 | # Standard to msysgit 18 | *.doc diff=astextplain 19 | *.DOC diff=astextplain 20 | *.docx diff=astextplain 21 | *.DOCX diff=astextplain 22 | *.dot diff=astextplain 23 | *.DOT diff=astextplain 24 | *.pdf diff=astextplain 25 | *.PDF diff=astextplain 26 | *.rtf diff=astextplain 27 | *.RTF diff=astextplain 28 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: TheAngryByrd 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please insert a description of your problem or question. 4 | 5 | ## Error messages, screenshots 6 | 7 | Please add any error logs or screenshots if available. 8 | 9 | ## Failing test, failing GitHub repo, or reproduction steps 10 | 11 | Please add either a failing test, a GitHub repo of the problem or detailed reproduction steps. 12 | 13 | ## Expected Behavior 14 | 15 | Please define what you would expect the behavior to be like. 16 | 17 | ## Known workarounds 18 | 19 | Please provide a description of any known workarounds. 20 | 21 | ## Other information 22 | 23 | * Operating System: 24 | - [ ] windows [insert version here] 25 | - [ ] macOs [insert version] 26 | - [ ] linux [insert flavor/version here] 27 | * Platform 28 | - [ ] dotnet core 29 | - [ ] dotnet full 30 | - [ ] mono 31 | * Branch or release version: 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | 3 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 4 | 5 | ## Types of changes 6 | 7 | What types of changes does your code introduce to TypeSafeInternals? 8 | _Put an `x` in the boxes that apply_ 9 | 10 | - [ ] Bugfix (non-breaking change which fixes an issue) 11 | - [ ] New feature (non-breaking change which adds functionality) 12 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 13 | 14 | 15 | ## Checklist 16 | 17 | _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ 18 | 19 | - [ ] Build and tests pass locally 20 | - [ ] I have added tests that prove my fix is effective or that my feature works (if appropriate) 21 | - [ ] I have added necessary documentation (if appropriate) 22 | 23 | ## Further comments 24 | 25 | If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build master 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, windows-latest, macOS-latest] 10 | runs-on: ${{ matrix.os }} 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Use .NET Core 2.1 SDK 15 | uses: actions/setup-dotnet@v1 16 | with: 17 | dotnet-version: '2.1.x' 18 | - name: Use .NET Core 3.1 SDK 19 | uses: actions/setup-dotnet@v1 20 | with: 21 | dotnet-version: '3.1.x' 22 | - name: Use .NET Core 5.x SDK 23 | uses: actions/setup-dotnet@v1 24 | with: 25 | dotnet-version: '5.0.x' 26 | # Not specifying a version will attempt to install via global.json 27 | - name: Use .NET Core global.json 28 | uses: actions/setup-dotnet@v1 29 | 30 | - name: Build 31 | if: runner.os != 'Windows' 32 | run: | 33 | chmod +x ./build.sh 34 | ./build.sh 35 | env: 36 | # Work around https://github.com/actions/setup-dotnet/issues/29 37 | DOTNET_ROOT: ${{ runner.tool_cache }}/dncs/${{ matrix.dotnet }}/x64 38 | CI: true 39 | - name: Build 40 | if: runner.os == 'Windows' 41 | run: ./build.cmd 42 | env: 43 | # Work around https://github.com/actions/setup-dotnet/issues/29 44 | DOTNET_ROOT: ${{ runner.tool_cache }}/dncs/${{ matrix.dotnet }}/x64 45 | CI: true 46 | -------------------------------------------------------------------------------- /.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 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | packages/ 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.pfx 193 | *.publishsettings 194 | node_modules/ 195 | orleans.codegen.cs 196 | 197 | # Since there are multiple workflows, uncomment next line to ignore bower_components 198 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 199 | #bower_components/ 200 | 201 | # RIA/Silverlight projects 202 | Generated_Code/ 203 | 204 | # Backup & report files from converting an old project file 205 | # to a newer Visual Studio version. Backup files are not needed, 206 | # because we have git ;-) 207 | _UpgradeReport_Files/ 208 | Backup*/ 209 | UpgradeLog*.XML 210 | UpgradeLog*.htm 211 | 212 | # SQL Server files 213 | *.mdf 214 | *.ldf 215 | 216 | # Business Intelligence projects 217 | *.rdl.data 218 | *.bim.layout 219 | *.bim_*.settings 220 | 221 | # Microsoft Fakes 222 | FakesAssemblies/ 223 | 224 | # GhostDoc plugin setting file 225 | *.GhostDoc.xml 226 | 227 | # Node.js Tools for Visual Studio 228 | .ntvs_analysis.dat 229 | 230 | # Visual Studio 6 build log 231 | *.plg 232 | 233 | # Visual Studio 6 workspace options file 234 | *.opt 235 | 236 | # Visual Studio LightSwitch build output 237 | **/*.HTMLClient/GeneratedArtifacts 238 | **/*.DesktopClient/GeneratedArtifacts 239 | **/*.DesktopClient/ModelManifest.xml 240 | **/*.Server/GeneratedArtifacts 241 | **/*.Server/ModelManifest.xml 242 | _Pvt_Extensions 243 | 244 | # Paket dependency manager 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | 254 | TestResults.xml 255 | 256 | # NuGet packages distributables 257 | dist/ 258 | 259 | # Ionide cache 260 | .ionide/ 261 | 262 | # Test coverage files 263 | coverage.xml 264 | coverage.*.xml 265 | 266 | # Paket tool store 267 | .paket/.store 268 | .paket/paket 269 | 270 | .fake 271 | .ionide 272 | 273 | docs/coverage/ 274 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ionide.ionide-paket", 4 | "ionide.ionide-fsharp", 5 | "ionide.ionide-fake", 6 | "ms-dotnettools.csharp", 7 | "editorConfig.editorConfig" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [{ 7 | "name": ".NET Core Attach", 8 | "type": "coreclr", 9 | "request": "attach", 10 | "processId": "${command:pickProcess}" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "FSharp.fsacRuntime":"netcore", 3 | "FSharp.enableAnalyzers": true, 4 | "FSharp.analyzersPath": [ 5 | "./packages/analyzers" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.1.1] - 2022-02-04 9 | 10 | ### Fixed 11 | - Update Myriad to 0.7.4 12 | 13 | ## [0.1.1-beta001] - 2022-02-04 14 | 15 | ### Fixed 16 | - Update Myriad to 0.7.4 17 | 18 | ## [0.1.0] - 2021-05-24 19 | 20 | ### Fixed 21 | - Packaging release correctly 22 | - Packaging release correctly 23 | - Packaging release correctly 24 | - Packaging release correctly 25 | 26 | ### Added 27 | - This release already has lots of features 28 | - This release already has lots of features 29 | 30 | ## [0.1.0-beta003] - 2021-05-24 31 | 32 | ### Fixed 33 | - Packaging release correctly 34 | 35 | - Packaging release correctly 36 | 37 | ### Added 38 | - This release already has lots of features 39 | 40 | ## [0.1.0-beta002] - 2021-05-24 41 | 42 | ### Fixed 43 | - Packaging release correctly 44 | 45 | ## [0.1.0-beta001] - 2021-05-24 46 | 47 | First release 48 | 49 | ### Added 50 | - This release already has lots of features 51 | 52 | ## [0.0.1] - 2020-05-26 53 | 54 | ### Added 55 | - Initial release 56 | 57 | [Unreleased]: https://github.com/TheAngryByrd/TypeSafeInternals/compare/v0.1.1...HEAD 58 | [0.1.1]: https://github.com/TheAngryByrd/TypeSafeInternals/compare/v0.1.0...v0.1.1 59 | [0.1.1-beta001]: https://github.com/TheAngryByrd/TypeSafeInternals/compare/v0.1.0...v0.1.1-beta001 60 | [0.1.0]: https://github.com/TheAngryByrd/TypeSafeInternals/compare/v0.0.1...v0.1.0 61 | [0.1.0-beta003]: https://github.com/TheAngryByrd/TypeSafeInternals/compare/v0.0.1...v0.1.0-beta003 62 | [0.1.0-beta002]: https://github.com/TheAngryByrd/TypeSafeInternals/releases/tag/v0.1.0-beta002 63 | [0.1.0-beta001]: https://github.com/TheAngryByrd/TypeSafeInternals/releases/tag/v0.1.0-beta001 64 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | f#, fsharp 5 | https://github.com/TheAngryByrd/TypeSafeInternals 6 | https://github.com/TheAngryByrd/TypeSafeInternals/blob/master/LICENSE.md 7 | false 8 | git 9 | TheAngryByrd 10 | https://github.com/TheAngryByrd/TypeSafeInternals 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TheAngryByrd.TypeSafeInternals 2 | 3 | ## What 4 | 5 | This uses [Myriad](https://github.com/MoiraeSoftware/myriad) to generate type safe reflection calls to internal functions/properties/methods. 6 | 7 | ## Why 8 | 9 | Sometimes you have to use reflection and there's no simple way around it. When creating reflection calls to internal/private functions/properties/methods, the underlying function may change when you go to upgrade the dependency. With `TypeSafeInternals`, it will re-generate the code for the calls so you would get a compile error if the API has drifted. 10 | 11 | See also: 12 | 13 | * [Microsoft Reflection Documentation](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection) 14 | * [Making Reflection Fly and Exploring Delegates](https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/) 15 | * [What is reflection and why is it useful?](https://stackoverflow.com/questions/37628/what-is-reflection-and-why-is-it-useful) 16 | 17 | ## How 18 | 19 | 1. Install `TheAngryByrd.Myriad.Plugins.TypeSafeInternals` via [Nuget](https://www.nuget.org/packages/TheAngryByrd.Myriad.Plugins.TypeSafeInternals/#dotnet-cli) or [Paket](https://www.nuget.org/packages/TheAngryByrd.Myriad.Plugins.TypeSafeInternals/#paket-cli). 20 | 2. Create an empty `myriad.toml` file next to your `fsproj` file. 21 | 3. Add this xml snippet to your `fsproj` file. 22 | 23 | ```xml 24 | 25 | TypeSafeInternals.txt 26 | 27 | ``` 28 | 29 | 4. Create `TypeSafeInternals.txt` next to you `fsproj` file. 30 | 5. Add the name of the Nuget package (such as `Npgsql.FSharp`) to `TypeSafeInternals.txt`. 31 | 6. run `dotnet build` 32 | 33 | ## Features 34 | 35 | - [x] F# [module level NonPublic functions](https://stackoverflow.com/a/7379327) 36 | - [x] Getters for [visible](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.typeinfo.isvisible?view=netstandard-1.6) [record with NonPublic members](https://stackoverflow.com/a/12389894) 37 | - [ ] Copy-and-Update for visible records with NonPublic members 38 | - [ ] NonPublic Static Methods 39 | - [ ] NonPublic instance methods 40 | - [ ] Shims for NonPublic types 41 | 42 | --- 43 | 44 | ## Maintainers 45 | 46 | * [TheAngryByrd](https://github.com/TheAngryByrd) 47 | 48 | --- 49 | 50 | ## Builds 51 | 52 | GitHub Actions | 53 | :---: | 54 | [![GitHub Actions](https://github.com/TheAngryByrd/TypeSafeInternals/workflows/Build%20master/badge.svg)](https://github.com/TheAngryByrd/TypeSafeInternals/actions?query=branch%3Amaster) | 55 | [![Build History](https://buildstats.info/github/chart/TheAngryByrd/TypeSafeInternals)](https://github.com/TheAngryByrd/TypeSafeInternals/actions?query=branch%3Amaster) | 56 | 57 | --- 58 | 59 | ## NuGet 60 | 61 | Package | Stable | Prerelease 62 | --- | --- | --- 63 | TheAngryByrd.TypeSafeInternals | [![NuGet Badge](https://buildstats.info/nuget/TheAngryByrd.TypeSafeInternals)](https://www.nuget.org/packages/TheAngryByrd.TypeSafeInternals/) | [![NuGet Badge](https://buildstats.info/nuget/TheAngryByrd.TypeSafeInternals?includePreReleases=true)](https://www.nuget.org/packages/TheAngryByrd.TypeSafeInternals/) 64 | TheAngryByrd.Myriad.Plugins.TypeSafeInternals | [![NuGet Badge](https://buildstats.info/nuget/TheAngryByrd.Myriad.Plugins.TypeSafeInternals)](https://www.nuget.org/packages/TheAngryByrd.Myriad.Plugins.TypeSafeInternals/) | [![NuGet Badge](https://buildstats.info/nuget/TheAngryByrd.Myriad.Plugins.TypeSafeInternals?includePreReleases=true)](https://www.nuget.org/packages/TheAngryByrd.Myriad.Plugins.TypeSafeInternals/) 65 | 66 | --- 67 | 68 | ### Developing 69 | 70 | Make sure the following **requirements** are installed on your system: 71 | 72 | * [dotnet SDK](https://www.microsoft.com/net/download/core) 5.0 or higher 73 | 74 | or 75 | 76 | * [VSCode Dev Container](https://code.visualstudio.com/docs/remote/containers) 77 | 78 | --- 79 | 80 | ### Environment Variables 81 | 82 | * `CONFIGURATION` will set the [configuration](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x#options) of the dotnet commands. If not set, it will default to Release. 83 | * `CONFIGURATION=Debug ./build.sh` will result in `-c` additions to commands such as in `dotnet build -c Debug` 84 | * `GITHUB_TOKEN` will be used to upload release notes and Nuget packages to GitHub. 85 | * Be sure to set this before releasing 86 | * `DISABLE_COVERAGE` Will disable running code coverage metrics. AltCover can have [severe performance degradation](https://github.com/SteveGilham/altcover/issues/57) so it's worth disabling when looking to do a quicker feedback loop. 87 | * `DISABLE_COVERAGE=1 ./build.sh` 88 | 89 | --- 90 | 91 | ### Building 92 | 93 | ```sh 94 | > build.cmd // on windows 95 | $ ./build.sh // on unix 96 | ``` 97 | 98 | The bin of your library should look similar to: 99 | 100 | ```bash 101 | $ tree src/MyCoolNewLib/bin/ 102 | src/MyCoolNewLib/bin/ 103 | └── Debug 104 | └── net50 105 | ├── MyCoolNewLib.deps.json 106 | ├── MyCoolNewLib.dll 107 | ├── MyCoolNewLib.pdb 108 | └── MyCoolNewLib.xml 109 | 110 | ``` 111 | 112 | --- 113 | 114 | ### Build Targets 115 | 116 | * `Clean` - Cleans artifact and temp directories. 117 | * `DotnetRestore` - Runs [dotnet restore](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-restore?tabs=netcore2x) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). 118 | * [`DotnetBuild`](#Building) - Runs [dotnet build](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). 119 | * `DotnetTest` - Runs [dotnet test](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=netcore21) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). 120 | * `GenerateCoverageReport` - Code coverage is run during `DotnetTest` and this generates a report via [ReportGenerator](https://github.com/danielpalme/ReportGenerator). 121 | * `WatchTests` - Runs [dotnet watch](https://docs.microsoft.com/en-us/aspnet/core/tutorials/dotnet-watch?view=aspnetcore-3.0) with the test projects. Useful for rapid feedback loops. 122 | * `GenerateAssemblyInfo` - Generates [AssemblyInfo](https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.applicationservices.assemblyinfo?view=netframework-4.8) for libraries. 123 | * `DotnetPack` - Runs [dotnet pack](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-pack). This includes running [Source Link](https://github.com/dotnet/sourcelink). 124 | * `SourceLinkTest` - Runs a Source Link test tool to verify Source Links were properly generated. 125 | * `PublishToNuGet` - Publishes the NuGet packages generated in `DotnetPack` to NuGet via [paket push](https://fsprojects.github.io/Paket/paket-push.html). 126 | * `GitRelease` - Creates a commit message with the [Release Notes](https://fake.build/apidocs/v5/fake-core-releasenotes.html) and a git tag via the version in the `Release Notes`. 127 | * `GitHubRelease` - Publishes a [GitHub Release](https://help.github.com/en/articles/creating-releases) with the Release Notes and any NuGet packages. 128 | * `FormatCode` - Runs [Fantomas](https://github.com/fsprojects/fantomas) on the solution file. 129 | * `BuildDocs` - Generates Documentation from `docsSrc` and the [XML Documentation Comments](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/) from your libraries in `src`. 130 | * `WatchDocs` - Generates documentation and starts a webserver locally. It will rebuild and hot reload if it detects any changes made to `docsSrc` files, libraries in `src`, or the `docsTool` itself. 131 | * `ReleaseDocs` - Will stage, commit, and push docs generated in the `BuildDocs` target. 132 | * [`Release`](#Releasing) - Task that runs all release type tasks such as `PublishToNuGet`, `GitRelease`, `ReleaseDocs`, and `GitHubRelease`. Make sure to read [Releasing](#Releasing) to setup your environment correctly for releases. 133 | 134 | --- 135 | 136 | ### Releasing 137 | 138 | * [Start a git repo with a remote](https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/) 139 | 140 | ```sh 141 | git add . 142 | git commit -m "Scaffold" 143 | git remote add origin https://github.com/user/MyCoolNewLib.git 144 | git push -u origin master 145 | ``` 146 | 147 | * [Create your NuGeT API key](https://docs.microsoft.com/en-us/nuget/nuget-org/publish-a-package#create-api-keys) 148 | * [Add your NuGet API key to paket](https://fsprojects.github.io/Paket/paket-config.html#Adding-a-NuGet-API-key) 149 | 150 | ```sh 151 | paket config add-token "https://www.nuget.org" 4003d786-cc37-4004-bfdf-c4f3e8ef9b3a 152 | ``` 153 | 154 | * or set the environment variable `NUGET_TOKEN` to your key 155 | 156 | * [Create a GitHub OAuth Token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) 157 | * You can then set the environment variable `GITHUB_TOKEN` to upload release notes and artifacts to github 158 | * Otherwise it will fallback to username/password 159 | 160 | * Then update the `CHANGELOG.md` with an "Unreleased" section containing release notes for this version, in [KeepAChangelog](https://keepachangelog.com/en/1.1.0/) format. 161 | 162 | NOTE: Its highly recommend to add a link to the Pull Request next to the release note that it affects. The reason for this is when the `RELEASE` target is run, it will add these new notes into the body of git commit. GitHub will notice the links and will update the Pull Request with what commit referenced it saying ["added a commit that referenced this pull request"](https://github.com/TheAngryByrd/MiniScaffold/pull/179#ref-commit-837ad59). Since the build script automates the commit message, it will say "Bump Version to x.y.z". The benefit of this is when users goto a Pull Request, it will be clear when and which version those code changes released. Also when reading the `CHANGELOG`, if someone is curious about how or why those changes were made, they can easily discover the work and discussions. 163 | 164 | Here's an example of adding an "Unreleased" section to a `CHANGELOG.md` with a `0.1.0` section already released. 165 | 166 | ```markdown 167 | ## [Unreleased] 168 | 169 | ### Added 170 | - Does cool stuff! 171 | 172 | ### Fixed 173 | - Fixes that silly oversight 174 | 175 | ## [0.1.0] - 2017-03-17 176 | First release 177 | 178 | ### Added 179 | - This release already has lots of features 180 | 181 | [Unreleased]: https://github.com/user/MyCoolNewLib.git/compare/v0.1.0...HEAD 182 | [0.1.0]: https://github.com/user/MyCoolNewLib.git/releases/tag/v0.1.0 183 | ``` 184 | 185 | * You can then use the `Release` target, specifying the version number either in the `RELEASE_VERSION` environment 186 | variable, or else as a parameter after the target name. This will: 187 | * update `CHANGELOG.md`, moving changes from the `Unreleased` section into a new `0.2.0` section 188 | * if there were any prerelease versions of 0.2.0 in the changelog, it will also collect their changes into the final 0.2.0 entry 189 | * make a commit bumping the version: `Bump version to 0.2.0` and adds the new changelog section to the commit's body 190 | * publish the package to NuGet 191 | * push a git tag 192 | * create a GitHub release for that git tag 193 | 194 | macOS/Linux Parameter: 195 | 196 | ```sh 197 | ./build.sh Release 0.2.0 198 | ``` 199 | 200 | macOS/Linux Environment Variable: 201 | 202 | ```sh 203 | RELEASE_VERSION=0.2.0 ./build.sh Release 204 | ``` 205 | -------------------------------------------------------------------------------- /TheAngryByrd.TypeSafeInternals.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{CAB33DE3-D0A3-46E2-8CA8-1AB2C97B111F}" 7 | ProjectSection(SolutionItems) = preProject 8 | paket.dependencies = paket.dependencies 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C397A34C-84F1-49E7-AEBC-2F9F2B196216}" 12 | EndProject 13 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TheAngryByrd.TypeSafeInternals", "src\TheAngryByrd.TypeSafeInternals\TheAngryByrd.TypeSafeInternals.fsproj", "{5D30E174-2538-47AC-8443-318C8C5DC2C9}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ACBEE43C-7A88-4FB1-9B06-DB064D22B29F}" 16 | EndProject 17 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TheAngryByrd.TypeSafeInternals.Tests", "tests\TheAngryByrd.TypeSafeInternals.Tests\TheAngryByrd.TypeSafeInternals.Tests.fsproj", "{1CA2E092-2320-451D-A4F0-9ED7C7C528CA}" 18 | EndProject 19 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "docsTool", "docsTool\docsTool.fsproj", "{8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}" 20 | EndProject 21 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TheAngryByrd.Myriad.Plugins.TypeSafeInternals", "src\TheAngryByrd.Myriad.Plugins.TypeSafeInternals\TheAngryByrd.Myriad.Plugins.TypeSafeInternals.fsproj", "{7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}" 22 | EndProject 23 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests", "tests\TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests\TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests.fsproj", "{C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}" 24 | EndProject 25 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "build", "build\build.fsproj", "{85C7A212-E7E2-454F-9C75-3C324B4EF007}" 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Debug|x64 = Debug|x64 31 | Debug|x86 = Debug|x86 32 | Release|Any CPU = Release|Any CPU 33 | Release|x64 = Release|x64 34 | Release|x86 = Release|x86 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 40 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x64.ActiveCfg = Debug|x64 43 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x64.Build.0 = Debug|x64 44 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x86.ActiveCfg = Debug|x86 45 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x86.Build.0 = Debug|x86 46 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x64.ActiveCfg = Release|x64 49 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x64.Build.0 = Release|x64 50 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x86.ActiveCfg = Release|x86 51 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x86.Build.0 = Release|x86 52 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x64.ActiveCfg = Debug|x64 55 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x64.Build.0 = Debug|x64 56 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x86.ActiveCfg = Debug|x86 57 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x86.Build.0 = Debug|x86 58 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x64.ActiveCfg = Release|x64 61 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x64.Build.0 = Release|x64 62 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x86.ActiveCfg = Release|x86 63 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x86.Build.0 = Release|x86 64 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|x64.ActiveCfg = Debug|Any CPU 67 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|x64.Build.0 = Debug|Any CPU 68 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|x86.ActiveCfg = Debug|Any CPU 69 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|x86.Build.0 = Debug|Any CPU 70 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|x64.ActiveCfg = Release|Any CPU 73 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|x64.Build.0 = Release|Any CPU 74 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|x86.ActiveCfg = Release|Any CPU 75 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|x86.Build.0 = Release|Any CPU 76 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Debug|x64.ActiveCfg = Debug|Any CPU 79 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Debug|x64.Build.0 = Debug|Any CPU 80 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Debug|x86.ActiveCfg = Debug|Any CPU 81 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Debug|x86.Build.0 = Debug|Any CPU 82 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Release|x64.ActiveCfg = Release|Any CPU 85 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Release|x64.Build.0 = Release|Any CPU 86 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Release|x86.ActiveCfg = Release|Any CPU 87 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F}.Release|x86.Build.0 = Release|Any CPU 88 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Debug|x64.ActiveCfg = Debug|Any CPU 91 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Debug|x64.Build.0 = Debug|Any CPU 92 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Debug|x86.ActiveCfg = Debug|Any CPU 93 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Debug|x86.Build.0 = Debug|Any CPU 94 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Release|x64.ActiveCfg = Release|Any CPU 97 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Release|x64.Build.0 = Release|Any CPU 98 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Release|x86.ActiveCfg = Release|Any CPU 99 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC}.Release|x86.Build.0 = Release|Any CPU 100 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 101 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Debug|Any CPU.Build.0 = Debug|Any CPU 102 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Debug|x64.ActiveCfg = Debug|Any CPU 103 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Debug|x64.Build.0 = Debug|Any CPU 104 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Debug|x86.ActiveCfg = Debug|Any CPU 105 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Debug|x86.Build.0 = Debug|Any CPU 106 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Release|Any CPU.ActiveCfg = Release|Any CPU 107 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Release|Any CPU.Build.0 = Release|Any CPU 108 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Release|x64.ActiveCfg = Release|Any CPU 109 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Release|x64.Build.0 = Release|Any CPU 110 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Release|x86.ActiveCfg = Release|Any CPU 111 | {85C7A212-E7E2-454F-9C75-3C324B4EF007}.Release|x86.Build.0 = Release|Any CPU 112 | EndGlobalSection 113 | GlobalSection(NestedProjects) = preSolution 114 | {5D30E174-2538-47AC-8443-318C8C5DC2C9} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216} 115 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA} = {ACBEE43C-7A88-4FB1-9B06-DB064D22B29F} 116 | {7A27ED39-2BA4-4C2E-87BB-4AADDFB77A7F} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216} 117 | {C617B88E-FF8D-4ACF-AE8A-5D95676A9CDC} = {ACBEE43C-7A88-4FB1-9B06-DB064D22B29F} 118 | EndGlobalSection 119 | EndGlobal 120 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | echo Restoring dotnet tools... 2 | dotnet tool restore 3 | 4 | dotnet run --project ./build/build.fsproj -- -t %* 5 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # -*- coding: utf-8 -*- 3 | 4 | set -eu 5 | set -o pipefail 6 | 7 | echo "Restoring dotnet tools..." 8 | dotnet tool restore 9 | 10 | FAKE_DETAILED_ERRORS=true dotnet run --project ./build/build.fsproj -- -t "$@" 11 | -------------------------------------------------------------------------------- /build/build.fs: -------------------------------------------------------------------------------- 1 | open Argu 2 | open System.IO.Compression 3 | open System 4 | open Fake.SystemHelper 5 | open Fake.Core 6 | open Fake.DotNet 7 | open Fake.Tools 8 | open Fake.IO 9 | open Fake.IO.FileSystemOperators 10 | open Fake.IO.Globbing.Operators 11 | open Fake.Core.TargetOperators 12 | open Fake.Api 13 | open Fake.BuildServer 14 | 15 | let getCLIArgs () = 16 | System.Environment.GetCommandLineArgs() 17 | |> Array.skip 1 // The first element in the array contains the file name of the executing program 18 | 19 | getCLIArgs () 20 | |> Array.toList 21 | |> Context.FakeExecutionContext.Create false "build.fsx" 22 | |> Context.RuntimeContext.Fake 23 | |> Context.setExecutionContext 24 | 25 | BuildServer.install [ 26 | GitHubActions.Installer 27 | ] 28 | 29 | let environVarAsBoolOrDefault varName defaultValue = 30 | let truthyConsts = [ 31 | "1" 32 | "Y" 33 | "YES" 34 | "T" 35 | "TRUE" 36 | ] 37 | try 38 | let envvar = (Environment.environVar varName).ToUpper() 39 | truthyConsts |> List.exists((=)envvar) 40 | with 41 | | _ -> defaultValue 42 | 43 | //----------------------------------------------------------------------------- 44 | // Metadata and Configuration 45 | //----------------------------------------------------------------------------- 46 | 47 | let productName = "TheAngryByrd.TypeSafeInternals" 48 | let sln = "TheAngryByrd.TypeSafeInternals.sln" 49 | 50 | let rootDir = __SOURCE_DIRECTORY__ ".." 51 | 52 | 53 | let srcCodeGlob = 54 | !! (rootDir "src/**/*.fs") 55 | ++ (rootDir "src/**/*.fsx") 56 | 57 | let testsCodeGlob = 58 | !! (rootDir "tests/**/*.fs") 59 | ++ (rootDir "tests/**/*.fsx") 60 | 61 | let srcGlob =rootDir "src/**/*.??proj" 62 | let testsGlob = rootDir "tests/**/*.??proj" 63 | 64 | let srcAndTest = 65 | !! srcGlob 66 | ++ testsGlob 67 | 68 | let distDir = rootDir "dist" 69 | let distGlob = distDir @@ "*.nupkg" 70 | 71 | let coverageThresholdPercent = 80 72 | let coverageReportDir = rootDir "docs" @@ "coverage" 73 | 74 | 75 | let docsDir = rootDir "docs" 76 | let docsSrcDir = rootDir "docsSrc" 77 | let docsToolDir = __SOURCE_DIRECTORY__ @@ "docsTool" 78 | 79 | let gitOwner = "TheAngryByrd" 80 | let gitRepoName = "TypeSafeInternals" 81 | 82 | let gitHubRepoUrl = sprintf "https://github.com/%s/%s" gitOwner gitRepoName 83 | 84 | let releaseBranch = "master" 85 | 86 | let tagFromVersionNumber versionNumber = sprintf "v%s" versionNumber 87 | 88 | let changelogFilename = "CHANGELOG.md" 89 | let changelog = Fake.Core.Changelog.load changelogFilename 90 | let mutable latestEntry = 91 | if Seq.isEmpty changelog.Entries 92 | then Changelog.ChangelogEntry.New("0.0.1", "0.0.1-alpha.1", Some DateTime.Today, None, [], false) 93 | else changelog.LatestEntry 94 | let mutable linkReferenceForLatestEntry = "" 95 | let mutable changelogBackupFilename = "" 96 | 97 | let publishUrl = "https://www.nuget.org" 98 | 99 | let docsSiteBaseUrl = sprintf "https://%s.github.io/%s" gitOwner gitRepoName 100 | 101 | let disableCodeCoverage = environVarAsBoolOrDefault "DISABLE_COVERAGE" false 102 | 103 | let githubToken = Environment.environVarOrNone "GITHUB_TOKEN" 104 | Option.iter(TraceSecrets.register "" ) githubToken 105 | 106 | 107 | let nugetToken = Environment.environVarOrNone "NUGET_TOKEN" 108 | Option.iter(TraceSecrets.register "") nugetToken 109 | 110 | //----------------------------------------------------------------------------- 111 | // Helpers 112 | //----------------------------------------------------------------------------- 113 | 114 | let isRelease (targets : Target list) = 115 | targets 116 | |> Seq.map(fun t -> t.Name) 117 | |> Seq.exists ((=)"Release") 118 | 119 | let invokeAsync f = async { f () } 120 | 121 | let configuration (targets : Target list) = 122 | let defaultVal = if isRelease targets then "Release" else "Debug" 123 | match Environment.environVarOrDefault "CONFIGURATION" defaultVal with 124 | | "Debug" -> DotNet.BuildConfiguration.Debug 125 | | "Release" -> DotNet.BuildConfiguration.Release 126 | | config -> DotNet.BuildConfiguration.Custom config 127 | 128 | let failOnBadExitAndPrint (p : ProcessResult) = 129 | if p.ExitCode <> 0 then 130 | p.Errors |> Seq.iter Trace.traceError 131 | failwithf "failed with exitcode %d" p.ExitCode 132 | 133 | // CI Servers can have bizzare failures that have nothing to do with your code 134 | let rec retryIfInCI times fn = 135 | match Environment.environVarOrNone "CI" with 136 | | Some _ -> 137 | if times > 1 then 138 | try 139 | fn() 140 | with 141 | | _ -> retryIfInCI (times - 1) fn 142 | else 143 | fn() 144 | | _ -> fn() 145 | 146 | let isReleaseBranchCheck () = 147 | if Git.Information.getBranchName "" <> releaseBranch then failwithf "Not on %s. If you want to release please switch to this branch." releaseBranch 148 | 149 | module Changelog = 150 | 151 | let isEmptyChange = function 152 | | Changelog.Change.Added s 153 | | Changelog.Change.Changed s 154 | | Changelog.Change.Deprecated s 155 | | Changelog.Change.Fixed s 156 | | Changelog.Change.Removed s 157 | | Changelog.Change.Security s 158 | | Changelog.Change.Custom (_, s) -> 159 | String.IsNullOrWhiteSpace s.CleanedText 160 | 161 | let isChangelogEmpty () = 162 | let isEmpty = 163 | (latestEntry.Changes |> Seq.forall isEmptyChange) 164 | || latestEntry.Changes |> Seq.isEmpty 165 | if isEmpty then failwith "No changes in CHANGELOG. Please add your changes under a heading specified in https://keepachangelog.com/" 166 | 167 | let mkLinkReference (newVersion : SemVerInfo) (changelog : Changelog.Changelog) = 168 | if changelog.Entries |> List.isEmpty then 169 | // No actual changelog entries yet: link reference will just point to the Git tag 170 | sprintf "[%s]: %s/releases/tag/%s" newVersion.AsString gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) 171 | else 172 | let versionTuple version = (version.Major, version.Minor, version.Patch) 173 | // Changelog entries come already sorted, most-recent first, by the Changelog module 174 | let prevEntry = changelog.Entries |> List.skipWhile (fun entry -> entry.SemVer.PreRelease.IsSome && versionTuple entry.SemVer = versionTuple newVersion) |> List.tryHead 175 | let linkTarget = 176 | match prevEntry with 177 | | Some entry -> sprintf "%s/compare/%s...%s" gitHubRepoUrl (tagFromVersionNumber entry.SemVer.AsString) (tagFromVersionNumber newVersion.AsString) 178 | | None -> sprintf "%s/releases/tag/%s" gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) 179 | sprintf "[%s]: %s" newVersion.AsString linkTarget 180 | 181 | let mkReleaseNotes (linkReference : string) (latestEntry : Changelog.ChangelogEntry) = 182 | if String.isNullOrEmpty linkReference then latestEntry.ToString() 183 | else 184 | // Add link reference target to description before building release notes, since in main changelog file it's at the bottom of the file 185 | let description = 186 | match latestEntry.Description with 187 | | None -> linkReference 188 | | Some desc when desc.Contains(linkReference) -> desc 189 | | Some desc -> sprintf "%s\n\n%s" (desc.Trim()) linkReference 190 | { latestEntry with Description = Some description }.ToString() 191 | 192 | let getVersionNumber envVarName ctx = 193 | let args = ctx.Context.Arguments 194 | let verArg = 195 | args 196 | |> List.tryHead 197 | |> Option.defaultWith (fun () -> Environment.environVarOrDefault envVarName "") 198 | if SemVer.isValid verArg then verArg 199 | elif verArg.StartsWith("v") && SemVer.isValid verArg.[1..] then 200 | let target = ctx.Context.FinalTarget 201 | Trace.traceImportantfn "Please specify a version number without leading 'v' next time, e.g. \"./build.sh %s %s\" rather than \"./build.sh %s %s\"" target verArg.[1..] target verArg 202 | verArg.[1..] 203 | elif String.isNullOrEmpty verArg then 204 | let target = ctx.Context.FinalTarget 205 | Trace.traceErrorfn "Please specify a version number, either at the command line (\"./build.sh %s 1.0.0\") or in the %s environment variable" target envVarName 206 | failwith "No version number found" 207 | else 208 | Trace.traceErrorfn "Please specify a valid version number: %A could not be recognized as a version number" verArg 209 | failwith "Invalid version number" 210 | 211 | 212 | module dotnet = 213 | let watch cmdParam program args = 214 | DotNet.exec cmdParam (sprintf "watch %s" program) args 215 | 216 | let run cmdParam args = 217 | DotNet.exec cmdParam "run" args 218 | 219 | let tool optionConfig command args = 220 | DotNet.exec optionConfig (sprintf "%s" command) args 221 | |> failOnBadExitAndPrint 222 | 223 | let reportgenerator optionConfig args = 224 | tool optionConfig "reportgenerator" args 225 | 226 | let sourcelink optionConfig args = 227 | tool optionConfig "sourcelink" args 228 | 229 | let fcswatch optionConfig args = 230 | tool optionConfig "fcswatch" args 231 | 232 | let fsharpAnalyzer optionConfig args = 233 | tool optionConfig "fsharp-analyzers" args 234 | 235 | 236 | let fantomas args = 237 | DotNet.exec id "fantomas" args 238 | 239 | module FSharpAnalyzers = 240 | type Arguments = 241 | | Project of string 242 | | Analyzers_Path of string 243 | | Fail_On_Warnings of string list 244 | | Ignore_Files of string list 245 | | Verbose 246 | with 247 | interface IArgParserTemplate with 248 | member s.Usage = "" 249 | 250 | 251 | open DocsTool.CLIArgs 252 | module DocsTool = 253 | open Argu 254 | let buildparser = ArgumentParser.Create(programName = "docstool") 255 | let buildCLI () = 256 | [ 257 | BuildArgs.SiteBaseUrl docsSiteBaseUrl 258 | BuildArgs.ProjectGlob srcGlob 259 | BuildArgs.DocsOutputDirectory docsDir 260 | BuildArgs.DocsSourceDirectory docsSrcDir 261 | BuildArgs.GitHubRepoUrl gitHubRepoUrl 262 | BuildArgs.ProjectName gitRepoName 263 | BuildArgs.ReleaseVersion latestEntry.NuGetVersion 264 | ] 265 | |> buildparser.PrintCommandLineArgumentsFlat 266 | 267 | let build () = 268 | dotnet.run (fun args -> 269 | { args with WorkingDirectory = docsToolDir } 270 | ) (sprintf " -- build %s" (buildCLI())) 271 | |> failOnBadExitAndPrint 272 | 273 | let watchparser = ArgumentParser.Create(programName = "docstool") 274 | let watchCLI () = 275 | [ 276 | WatchArgs.ProjectGlob srcGlob 277 | WatchArgs.DocsSourceDirectory docsSrcDir 278 | WatchArgs.GitHubRepoUrl gitHubRepoUrl 279 | WatchArgs.ProjectName gitRepoName 280 | WatchArgs.ReleaseVersion latestEntry.NuGetVersion 281 | ] 282 | |> watchparser.PrintCommandLineArgumentsFlat 283 | 284 | let watch () = 285 | dotnet.watch (fun args -> 286 | { args with WorkingDirectory = docsToolDir } 287 | ) "run" (sprintf "-- watch %s" (watchCLI())) 288 | |> failOnBadExitAndPrint 289 | 290 | let allReleaseChecks () = 291 | isReleaseBranchCheck () 292 | Changelog.isChangelogEmpty () 293 | 294 | //----------------------------------------------------------------------------- 295 | // Target Implementations 296 | //----------------------------------------------------------------------------- 297 | 298 | 299 | let clean _ = 300 | ["bin"; "temp" ; distDir; coverageReportDir] 301 | |> Shell.cleanDirs 302 | 303 | !! srcGlob 304 | ++ testsGlob 305 | |> Seq.collect(fun p -> 306 | ["bin";"obj"] 307 | |> Seq.map(fun sp -> IO.Path.GetDirectoryName p @@ sp )) 308 | |> Shell.cleanDirs 309 | 310 | [ 311 | "paket-files/paket.restore.cached" 312 | ] 313 | |> Seq.iter Shell.rm 314 | 315 | let dotnetRestore _ = 316 | [sln] 317 | |> Seq.map(fun dir -> fun () -> 318 | let args = 319 | [ 320 | ] |> String.concat " " 321 | DotNet.restore(fun c -> 322 | { c with 323 | Common = 324 | c.Common 325 | |> DotNet.Options.withCustomParams 326 | (Some(args)) 327 | }) dir) 328 | |> Seq.iter(retryIfInCI 10) 329 | 330 | let updateChangelog ctx = 331 | let description, unreleasedChanges = 332 | match changelog.Unreleased with 333 | | None -> None, [] 334 | | Some u -> u.Description, u.Changes 335 | let verStr = ctx |> Changelog.getVersionNumber "RELEASE_VERSION" 336 | let newVersion = SemVer.parse verStr 337 | changelog.Entries 338 | |> List.tryFind (fun entry -> entry.SemVer = newVersion) 339 | |> Option.iter (fun entry -> 340 | Trace.traceErrorfn "Version %s already exists in %s, released on %s" verStr changelogFilename (if entry.Date.IsSome then entry.Date.Value.ToString("yyyy-MM-dd") else "(no date specified)") 341 | failwith "Can't release with a duplicate version number" 342 | ) 343 | changelog.Entries 344 | |> List.tryFind (fun entry -> entry.SemVer > newVersion) 345 | |> Option.iter (fun entry -> 346 | Trace.traceErrorfn "You're trying to release version %s, but a later version %s already exists, released on %s" verStr entry.SemVer.AsString (if entry.Date.IsSome then entry.Date.Value.ToString("yyyy-MM-dd") else "(no date specified)") 347 | failwith "Can't release with a version number older than an existing release" 348 | ) 349 | let versionTuple version = (version.Major, version.Minor, version.Patch) 350 | let prereleaseEntries = changelog.Entries |> List.filter (fun entry -> entry.SemVer.PreRelease.IsSome && versionTuple entry.SemVer = versionTuple newVersion) 351 | let prereleaseChanges = prereleaseEntries |> List.collect (fun entry -> entry.Changes |> List.filter (not << Changelog.isEmptyChange)) 352 | let assemblyVersion, nugetVersion = Changelog.parseVersions newVersion.AsString 353 | linkReferenceForLatestEntry <- Changelog.mkLinkReference newVersion changelog 354 | let newEntry = Changelog.ChangelogEntry.New(assemblyVersion.Value, nugetVersion.Value, Some System.DateTime.Today, description, unreleasedChanges @ prereleaseChanges, false) 355 | let newChangelog = Changelog.Changelog.New(changelog.Header, changelog.Description, None, newEntry :: changelog.Entries) 356 | latestEntry <- newEntry 357 | 358 | // Save changelog to temporary file before making any edits 359 | changelogBackupFilename <- System.IO.Path.GetTempFileName() 360 | changelogFilename |> Shell.copyFile changelogBackupFilename 361 | Target.activateFinal "DeleteChangelogBackupFile" 362 | 363 | newChangelog 364 | |> Changelog.save changelogFilename 365 | 366 | // Now update the link references at the end of the file 367 | linkReferenceForLatestEntry <- Changelog.mkLinkReference newVersion changelog 368 | let linkReferenceForUnreleased = sprintf "[Unreleased]: %s/compare/%s...%s" gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) "HEAD" 369 | let tailLines = File.read changelogFilename |> List.ofSeq |> List.rev 370 | 371 | let isRef line = System.Text.RegularExpressions.Regex.IsMatch(line, @"^\[.+?\]:\s?[a-z]+://.*$") 372 | let linkReferenceTargets = 373 | tailLines 374 | |> List.skipWhile String.isNullOrWhiteSpace 375 | |> List.takeWhile isRef 376 | |> List.rev // Now most recent entry is at the head of the list 377 | 378 | let newLinkReferenceTargets = 379 | match linkReferenceTargets with 380 | | [] -> 381 | [linkReferenceForUnreleased; linkReferenceForLatestEntry] 382 | | first :: rest when first |> String.startsWith "[Unreleased]:" -> 383 | linkReferenceForUnreleased :: linkReferenceForLatestEntry :: rest 384 | | first :: rest -> 385 | linkReferenceForUnreleased :: linkReferenceForLatestEntry :: first :: rest 386 | 387 | let blankLineCount = tailLines |> Seq.takeWhile String.isNullOrWhiteSpace |> Seq.length 388 | let linkRefCount = linkReferenceTargets |> List.length 389 | let skipCount = blankLineCount + linkRefCount 390 | let updatedLines = List.rev (tailLines |> List.skip skipCount) @ newLinkReferenceTargets 391 | File.write false changelogFilename updatedLines 392 | 393 | // If build fails after this point but before we push the release out, undo our modifications 394 | Target.activateBuildFailure "RevertChangelog" 395 | 396 | let revertChangelog _ = 397 | if String.isNotNullOrEmpty changelogBackupFilename then 398 | changelogBackupFilename |> Shell.copyFile changelogFilename 399 | 400 | let deleteChangelogBackupFile _ = 401 | if String.isNotNullOrEmpty changelogBackupFilename then 402 | Shell.rm changelogBackupFilename 403 | 404 | let dotnetBuild ctx = 405 | let args = 406 | [ 407 | sprintf "/p:PackageVersion=%s" latestEntry.NuGetVersion 408 | "--no-restore" 409 | ] 410 | DotNet.build(fun c -> 411 | { c with 412 | Configuration = configuration (ctx.Context.AllExecutingTargets) 413 | Common = 414 | c.Common 415 | |> DotNet.Options.withAdditionalArgs args 416 | 417 | }) sln 418 | 419 | let fsharpAnalyzers _ = 420 | let argParser = ArgumentParser.Create(programName = "fsharp-analyzers") 421 | !! srcGlob 422 | |> Seq.iter(fun proj -> 423 | let args = 424 | [ 425 | FSharpAnalyzers.Analyzers_Path (__SOURCE_DIRECTORY__ "packages/analyzers") 426 | FSharpAnalyzers.Arguments.Project proj 427 | FSharpAnalyzers.Arguments.Fail_On_Warnings [ 428 | "BDH0002" 429 | ] 430 | FSharpAnalyzers.Verbose 431 | ] 432 | |> argParser.PrintCommandLineArgumentsFlat 433 | dotnet.fsharpAnalyzer id args 434 | ) 435 | 436 | let dotnetTest ctx = 437 | let excludeCoverage = 438 | !! testsGlob 439 | |> Seq.map IO.Path.GetFileNameWithoutExtension 440 | |> String.concat "|" 441 | let args = 442 | [ 443 | "--no-build" 444 | sprintf "/p:AltCover=%b" (not disableCodeCoverage) 445 | // sprintf "/p:AltCoverThreshold=%d" coverageThresholdPercent 446 | sprintf "/p:AltCoverAssemblyExcludeFilter=%s" excludeCoverage 447 | "/p:AltCoverLocalSource=true" 448 | ] 449 | DotNet.test(fun c -> 450 | 451 | { c with 452 | Configuration = configuration (ctx.Context.AllExecutingTargets) 453 | Common = 454 | c.Common 455 | |> DotNet.Options.withAdditionalArgs args 456 | }) sln 457 | 458 | let generateCoverageReport _ = 459 | let coverageReports = 460 | !!"tests/**/coverage*.xml" 461 | |> String.concat ";" 462 | let sourceDirs = 463 | !! srcGlob 464 | |> Seq.map Path.getDirectory 465 | |> String.concat ";" 466 | let independentArgs = 467 | [ 468 | sprintf "-reports:\"%s\"" coverageReports 469 | sprintf "-targetdir:\"%s\"" coverageReportDir 470 | // Add source dir 471 | sprintf "-sourcedirs:\"%s\"" sourceDirs 472 | // Ignore Tests and if AltCover.Recorder.g sneaks in 473 | sprintf "-assemblyfilters:\"%s\"" "-*.Tests;-AltCover.Recorder.g" 474 | sprintf "-Reporttypes:%s" "Html" 475 | ] 476 | let args = 477 | independentArgs 478 | |> String.concat " " 479 | dotnet.reportgenerator id args 480 | 481 | let watchTests _ = 482 | !! testsGlob 483 | |> Seq.map(fun proj -> fun () -> 484 | dotnet.watch 485 | (fun opt -> 486 | opt |> DotNet.Options.withWorkingDirectory (IO.Path.GetDirectoryName proj)) 487 | "test" 488 | "" 489 | |> ignore 490 | ) 491 | |> Seq.iter (invokeAsync >> Async.Catch >> Async.Ignore >> Async.Start) 492 | 493 | printfn "Press Ctrl+C (or Ctrl+Break) to stop..." 494 | let cancelEvent = Console.CancelKeyPress |> Async.AwaitEvent |> Async.RunSynchronously 495 | cancelEvent.Cancel <- true 496 | 497 | let generateAssemblyInfo _ = 498 | 499 | let (|Fsproj|Csproj|Vbproj|) (projFileName:string) = 500 | match projFileName with 501 | | f when f.EndsWith("fsproj") -> Fsproj 502 | | f when f.EndsWith("csproj") -> Csproj 503 | | f when f.EndsWith("vbproj") -> Vbproj 504 | | _ -> failwith (sprintf "Project file %s not supported. Unknown project type." projFileName) 505 | 506 | let releaseChannel = 507 | match latestEntry.SemVer.PreRelease with 508 | | Some pr -> pr.Name 509 | | _ -> "release" 510 | let getAssemblyInfoAttributes projectName = 511 | [ 512 | AssemblyInfo.Title (projectName) 513 | AssemblyInfo.Product productName 514 | AssemblyInfo.Version latestEntry.AssemblyVersion 515 | AssemblyInfo.Metadata("ReleaseDate", latestEntry.Date.Value.ToString("o")) 516 | AssemblyInfo.FileVersion latestEntry.AssemblyVersion 517 | AssemblyInfo.InformationalVersion latestEntry.AssemblyVersion 518 | AssemblyInfo.Metadata("ReleaseChannel", releaseChannel) 519 | AssemblyInfo.Metadata("GitHash", Git.Information.getCurrentSHA1(null)) 520 | ] 521 | 522 | let getProjectDetails (projectPath : string) = 523 | let projectName = IO.Path.GetFileNameWithoutExtension(projectPath) 524 | ( 525 | projectPath, 526 | projectName, 527 | IO.Path.GetDirectoryName(projectPath), 528 | (getAssemblyInfoAttributes projectName) 529 | ) 530 | 531 | srcAndTest 532 | |> Seq.map getProjectDetails 533 | |> Seq.iter (fun (projFileName, _, folderName, attributes) -> 534 | match projFileName with 535 | | Fsproj -> AssemblyInfoFile.createFSharp (folderName @@ "AssemblyInfo.fs") attributes 536 | | Csproj -> AssemblyInfoFile.createCSharp ((folderName @@ "Properties") @@ "AssemblyInfo.cs") attributes 537 | | Vbproj -> AssemblyInfoFile.createVisualBasic ((folderName @@ "My Project") @@ "AssemblyInfo.vb") attributes 538 | ) 539 | 540 | 541 | [] 542 | type private DisposableDirectory (directory : string) = 543 | static member Create() = 544 | let tempPath = IO.Path.Combine(IO.Path.GetTempPath(), Guid.NewGuid().ToString("n")) 545 | Trace.tracefn "Creating disposable directory %s" tempPath 546 | IO.Directory.CreateDirectory tempPath |> ignore 547 | new DisposableDirectory(tempPath) 548 | member x.DirectoryInfo = IO.DirectoryInfo(directory) 549 | interface IDisposable with 550 | member x.Dispose() = 551 | Trace.tracefn "Deleting disposable directory %s" x.DirectoryInfo.FullName 552 | IO.Directory.Delete(x.DirectoryInfo.FullName,true) 553 | 554 | let dotnetPack ctx = 555 | let pack configuration args proj= 556 | DotNet.pack (fun c -> 557 | { c with 558 | Configuration = configuration 559 | OutputPath = Some distDir 560 | Common = 561 | c.Common 562 | |> DotNet.Options.withAdditionalArgs args 563 | }) proj 564 | 565 | let getTargetFramework (proj : string) = 566 | let projDoc = System.Xml.XmlDocument() 567 | projDoc.Load(proj) 568 | projDoc.GetElementsByTagName("TargetFramework").ItemOf(0).InnerText 569 | 570 | let configuration = configuration (ctx.Context.AllExecutingTargets) 571 | let args = 572 | [ 573 | sprintf "/p:PackageVersion=%s" latestEntry.NuGetVersion 574 | sprintf "/p:PackageReleaseNotes=\"%s\"" (latestEntry |> Changelog.mkReleaseNotes linkReferenceForLatestEntry) 575 | ] 576 | !! srcGlob 577 | |> Seq.map(fun proj -> pack configuration args proj ; proj) 578 | |> Seq.filter(fun proj -> proj.Contains "Myriad.Plugins") 579 | |> Seq.iter(fun proj -> 580 | // Myriad Plugins need some additional work to bundle 3rd party dependencies: see https://github.com/ionide/FSharp.Analyzers.SDK#packaging-and-distribution 581 | let publishFramework = getTargetFramework proj 582 | DotNet.publish (fun c -> 583 | { c with 584 | Configuration = configuration 585 | Framework = Some (publishFramework) 586 | }) proj 587 | 588 | let nupkg = 589 | let projectName = IO.Path.GetFileNameWithoutExtension proj 590 | IO.Directory.GetFiles distDir 591 | |> Seq.filter(fun path -> path.Contains projectName) 592 | |> Seq.tryExactlyOne 593 | |> Option.defaultWith(fun () -> failwithf "Could not find corresponsiding nuget package with name containing %s" projectName ) 594 | |> IO.FileInfo 595 | 596 | let publishPath = IO.FileInfo(proj).Directory.FullName "bin" (string configuration) publishFramework "publish" 597 | 598 | use dd = DisposableDirectory.Create() 599 | // Unzip the nuget 600 | ZipFile.ExtractToDirectory(nupkg.FullName, dd.DirectoryInfo.FullName) 601 | // delete the initial nuget package 602 | nupkg.Delete() 603 | // remove stuff from ./lib/{{publishFramework}} 604 | Shell.deleteDir (dd.DirectoryInfo.FullName "lib" publishFramework) 605 | // move the output of publish folder into the ./lib/{{publishFramework}} directory 606 | Shell.copyDir (dd.DirectoryInfo.FullName "lib" publishFramework) publishPath (fun _ -> true) 607 | // re-create the nuget package 608 | ZipFile.CreateFromDirectory(dd.DirectoryInfo.FullName, nupkg.FullName) 609 | ) 610 | 611 | 612 | let sourceLinkTest _ = 613 | !! distGlob 614 | |> Seq.iter (fun nupkg -> 615 | dotnet.sourcelink id (sprintf "test %s" nupkg) 616 | ) 617 | 618 | let publishToNuget _ = 619 | allReleaseChecks () 620 | Paket.push(fun c -> 621 | { c with 622 | ToolType = ToolType.CreateLocalTool() 623 | PublishUrl = publishUrl 624 | WorkingDir = "dist" 625 | ApiKey = match nugetToken with 626 | | Some s -> s 627 | | _ -> c.ApiKey // assume paket-config was set properly 628 | } 629 | ) 630 | // If build fails after this point, we've pushed a release out with this version of CHANGELOG.md so we should keep it around 631 | Target.deactivateBuildFailure "RevertChangelog" 632 | 633 | let gitRelease _ = 634 | allReleaseChecks () 635 | 636 | let releaseNotesGitCommitFormat = latestEntry.ToString() 637 | 638 | Git.Staging.stageFile "" "CHANGELOG.md" 639 | |> ignore 640 | 641 | !! "src/**/AssemblyInfo.fs" 642 | |> Seq.iter (Git.Staging.stageFile "" >> ignore) 643 | 644 | Git.Commit.exec "" (sprintf "Bump version to %s\n\n%s" latestEntry.NuGetVersion releaseNotesGitCommitFormat) 645 | Git.Branches.push "" 646 | 647 | let tag = tagFromVersionNumber latestEntry.NuGetVersion 648 | 649 | Git.Branches.tag "" tag 650 | Git.Branches.pushTag "" "origin" tag 651 | 652 | let githubRelease _ = 653 | allReleaseChecks () 654 | let token = 655 | match githubToken with 656 | | Some s -> s 657 | | _ -> failwith "please set the github_token environment variable to a github personal access token with repo access." 658 | 659 | let files = !! distGlob 660 | // Get release notes with properly-linked version number 661 | let releaseNotes = latestEntry |> Changelog.mkReleaseNotes linkReferenceForLatestEntry 662 | 663 | GitHub.createClientWithToken token 664 | |> GitHub.draftNewRelease gitOwner gitRepoName (tagFromVersionNumber latestEntry.NuGetVersion) (latestEntry.SemVer.PreRelease <> None) (releaseNotes |> Seq.singleton) 665 | |> GitHub.uploadFiles files 666 | |> GitHub.publishDraft 667 | |> Async.RunSynchronously 668 | 669 | let ignoredFilePredicates = [ 670 | fun (f : string) -> f.EndsWith("AssemblyInfo.fs") 671 | fun (f : string) -> f.EndsWith("Generated.fs") 672 | ] 673 | 674 | module Seq = 675 | let filterOut predicate xs = 676 | xs |> Seq.filter(predicate >> not) 677 | 678 | let withoutIgnoredFiles = 679 | Seq.filterOut(fun f -> ignoredFilePredicates |> Seq.exists (fun p -> p f)) 680 | 681 | let formatCode _ = 682 | let result = 683 | [ 684 | srcCodeGlob 685 | testsCodeGlob 686 | ] 687 | |> Seq.collect id 688 | |> withoutIgnoredFiles 689 | |> String.concat " " 690 | |> dotnet.fantomas 691 | 692 | if not result.OK then 693 | Trace.traceErrorfn "Errors while formatting all files: %A" result.Messages 694 | 695 | 696 | let checkFormatCode _ = 697 | let result = 698 | [ 699 | srcCodeGlob 700 | testsCodeGlob 701 | ] 702 | |> Seq.collect id 703 | |> withoutIgnoredFiles 704 | |> String.concat " " 705 | |> sprintf "%s --check" 706 | |> dotnet.fantomas 707 | 708 | if result.ExitCode = 0 then 709 | Trace.log "No files need formatting" 710 | elif result.ExitCode = 99 then 711 | failwith "Some files need formatting, check output for more info" 712 | else 713 | Trace.logf "Errors while formatting: %A" result.Errors 714 | 715 | 716 | 717 | let buildDocs _ = 718 | DocsTool.build () 719 | 720 | let watchDocs _ = 721 | DocsTool.watch () 722 | 723 | let releaseDocs ctx = 724 | isReleaseBranchCheck () // Docs changes don't need a full release to the library 725 | 726 | Git.Staging.stageAll docsDir 727 | Git.Commit.exec "" (sprintf "Documentation release of version %s" latestEntry.NuGetVersion) 728 | if isRelease (ctx.Context.AllExecutingTargets) |> not then 729 | // We only want to push if we're only calling "ReleaseDocs" target 730 | // If we're calling "Release" target, we'll let the "GitRelease" target do the git push 731 | Git.Branches.push "" 732 | 733 | 734 | //----------------------------------------------------------------------------- 735 | // Target Declaration 736 | //----------------------------------------------------------------------------- 737 | 738 | Target.create "Clean" clean 739 | Target.create "DotnetRestore" dotnetRestore 740 | Target.create "UpdateChangelog" updateChangelog 741 | Target.createBuildFailure "RevertChangelog" revertChangelog // Do NOT put this in the dependency chain 742 | Target.createFinal "DeleteChangelogBackupFile" deleteChangelogBackupFile // Do NOT put this in the dependency chain 743 | Target.create "DotnetBuild" dotnetBuild 744 | Target.create "FSharpAnalyzers" fsharpAnalyzers 745 | Target.create "DotnetTest" dotnetTest 746 | Target.create "GenerateCoverageReport" generateCoverageReport 747 | Target.create "WatchTests" watchTests 748 | Target.create "GenerateAssemblyInfo" generateAssemblyInfo 749 | Target.create "DotnetPack" dotnetPack 750 | Target.create "SourceLinkTest" sourceLinkTest 751 | Target.create "PublishToNuGet" publishToNuget 752 | Target.create "GitRelease" gitRelease 753 | Target.create "GitHubRelease" githubRelease 754 | Target.create "FormatCode" formatCode 755 | Target.create "CheckFormatCode" checkFormatCode 756 | Target.create "Release" ignore 757 | Target.create "BuildDocs" buildDocs 758 | Target.create "WatchDocs" watchDocs 759 | Target.create "ReleaseDocs" releaseDocs 760 | 761 | //----------------------------------------------------------------------------- 762 | // Target Dependencies 763 | //----------------------------------------------------------------------------- 764 | 765 | 766 | // Only call Clean if DotnetPack was in the call chain 767 | // Ensure Clean is called before DotnetRestore 768 | "Clean" ?=> "DotnetRestore" |> ignore 769 | "Clean" ==> "DotnetPack" |> ignore 770 | 771 | // Only call GenerateAssemblyInfo if Publish was in the call chain 772 | // Ensure GenerateAssemblyInfo is called after DotnetRestore and before DotnetBuild 773 | "DotnetRestore" ?=> "GenerateAssemblyInfo" |> ignore 774 | "GenerateAssemblyInfo" ?=> "DotnetBuild" |> ignore 775 | "GenerateAssemblyInfo" ==> "PublishToNuGet" |> ignore 776 | 777 | // Only call UpdateChangelog if Publish was in the call chain 778 | // Ensure UpdateChangelog is called after DotnetRestore and before GenerateAssemblyInfo 779 | "DotnetRestore" ?=> "UpdateChangelog" |> ignore 780 | "UpdateChangelog" ?=> "GenerateAssemblyInfo" |> ignore 781 | "UpdateChangelog" ==> "PublishToNuGet" |> ignore 782 | 783 | "BuildDocs" ==> "ReleaseDocs" |> ignore 784 | "BuildDocs" ?=> "PublishToNuget" |> ignore 785 | "DotnetPack" ?=> "BuildDocs" |> ignore 786 | "GenerateCoverageReport" ?=> "ReleaseDocs" |> ignore 787 | 788 | 789 | "DotnetRestore" 790 | ==> "CheckFormatCode" 791 | ==> "DotnetBuild" 792 | // ==> "FSharpAnalyzers" 793 | ==> "DotnetTest" 794 | =?> ("GenerateCoverageReport", not disableCodeCoverage) 795 | ==> "DotnetPack" 796 | // ==> "SourceLinkTest" 797 | ==> "PublishToNuGet" 798 | ==> "GitRelease" 799 | ==> "GitHubRelease" 800 | ==> "Release" 801 | |> ignore 802 | "DotnetRestore" 803 | ==> "WatchTests" 804 | |> ignore 805 | //----------------------------------------------------------------------------- 806 | // Target Start 807 | //----------------------------------------------------------------------------- 808 | 809 | Target.runOrDefaultWithArguments "DotnetPack" 810 | -------------------------------------------------------------------------------- /build/build.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 3390;$(WarnOn) 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /build/paket.references: -------------------------------------------------------------------------------- 1 | group Build 2 | FSharp.Core 3 | Fake.IO.FileSystem 4 | Fake.Core.Target 5 | Fake.Core.ReleaseNotes 6 | FAKE.Core.Environment 7 | Fake.DotNet.Cli 8 | FAKE.Core.Process 9 | Fake.DotNet.AssemblyInfoFile 10 | Fake.Tools.Git 11 | Fake.DotNet.Paket 12 | Fake.Api.GitHub 13 | Fake.BuildServer.GitHubActions 14 | Argu 15 | Octokit 16 | -------------------------------------------------------------------------------- /docsSrc/Explanations/Background.md: -------------------------------------------------------------------------------- 1 | # Background 2 | 3 | Here's a core concept and reasons why this exists at a deeper level. 4 | -------------------------------------------------------------------------------- /docsSrc/How_Tos/Doing_A_Thing.md: -------------------------------------------------------------------------------- 1 | # How To do this specific thing 2 | 3 | -------------------------------------------------------------------------------- /docsSrc/How_Tos/Doing_Another_Thing.md: -------------------------------------------------------------------------------- 1 | # How To do this specific thing 2 | 3 | -------------------------------------------------------------------------------- /docsSrc/Tutorials/Getting_Started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ```fsharp 4 | open TypeSafeInternals 5 | let foo = () 6 | let myAge = 21 7 | let color = Say.FavoriteColor.Red 8 | ``` 9 | 10 | ## Here is the path to downloading 11 | 12 | [lang=bash] 13 | paket install TypeSafeInternals 14 | 15 | 16 | -------------------------------------------------------------------------------- /docsSrc/content/cleanups.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | // Makes code snippets responsive 3 | $("table").addClass("table-responsive"); 4 | }) 5 | 6 | -------------------------------------------------------------------------------- /docsSrc/content/hotload.js: -------------------------------------------------------------------------------- 1 | var refreshSocket = new WebSocket('ws://' + window.location.host) 2 | .onmessage = () => { 3 | location.reload(); 4 | } 5 | -------------------------------------------------------------------------------- /docsSrc/content/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Droid+Sans|Droid+Sans+Mono|Open+Sans:400,600,700); 2 | 3 | /*-------------------------------------------------------------------------- 4 | Formatting for F# code snippets 5 | /*--------------------------------------------------------------------------*/ 6 | 7 | /* strings --- and styles for other string related formats */ 8 | span.s { color:#E0E268; } 9 | /* printf formatters */ 10 | span.pf { color:#E0C57F; } 11 | /* escaped chars */ 12 | span.e { color:#EA8675; } 13 | 14 | /* identifiers --- and styles for more specific identifier types */ 15 | span.i { color:#d1d1d1; } 16 | /* type or module */ 17 | span.t { color:#43AEC6; } 18 | /* function */ 19 | span.f { color:#e1e1e1; } 20 | /* DU case or active pattern */ 21 | span.p { color:#4ec9b0; } 22 | 23 | /* keywords */ 24 | span.k { color:#FAB11D; } 25 | /* comment */ 26 | span.c { color:#808080; } 27 | /* operators */ 28 | span.o { color:#af75c1; } 29 | /* numbers */ 30 | span.n { color:#96C71D; } 31 | /* line number */ 32 | span.l { color:#80b0b0; } 33 | /* mutable var or ref cell */ 34 | span.v { color:#d1d1d1; font-weight: bold; } 35 | /* inactive code */ 36 | span.inactive { color:#808080; } 37 | /* preprocessor */ 38 | span.prep { color:#af75c1; } 39 | /* fsi output */ 40 | span.fsi { color:#808080; } 41 | 42 | /* omitted */ 43 | span.omitted { 44 | background:#3c4e52; 45 | border-radius:5px; 46 | color:#808080; 47 | padding:0px 0px 1px 0px; 48 | } 49 | /* tool tip */ 50 | div.tip { 51 | background:#475b5f; 52 | border-radius:4px; 53 | font:11pt 'Droid Sans', arial, sans-serif; 54 | padding:6px 8px 6px 8px; 55 | display:none; 56 | color:#d1d1d1; 57 | pointer-events:none; 58 | } 59 | table.pre pre { 60 | padding:0px; 61 | margin:0px; 62 | border:none; 63 | } 64 | table.pre, pre.fssnip, pre { 65 | line-height:13pt; 66 | /*border:1px solid #d8d8d8;*/ 67 | border:1px solid #000; 68 | /* border: none; */ 69 | border-collapse:separate; 70 | white-space:pre-wrap; 71 | font: 9pt 'Droid Sans Mono',consolas,monospace; 72 | width:90%; 73 | margin:10px 20px 20px 20px; 74 | background-color:#212d30; 75 | padding:10px; 76 | /*border-radius:5px;*/ 77 | color:#d1d1d1; 78 | max-width: none; 79 | } 80 | pre.fssnip code { 81 | font: 9pt 'Droid Sans Mono',consolas,monospace; 82 | } 83 | table.pre pre { 84 | padding:0px; 85 | margin:0px; 86 | border-radius:0px; 87 | width: 100%; 88 | } 89 | table.pre td { 90 | padding:0px; 91 | white-space:normal; 92 | margin:0px; 93 | } 94 | table.pre td.lines { 95 | width:30px; 96 | } 97 | 98 | .table thead td.fit, 99 | .table th.fit { 100 | white-space: nowrap; 101 | width: 1%; 102 | } 103 | /*-------------------------------------------------------------------------- 104 | Formatting for page & standard document content 105 | /*--------------------------------------------------------------------------*/ 106 | 107 | body { 108 | font-family: 'Open Sans', serif; 109 | background-color: #BADA55; 110 | } 111 | 112 | pre { 113 | word-wrap: inherit; 114 | } 115 | 116 | /* Format the heading - nicer spacing etc. */ 117 | .masthead { 118 | overflow: hidden; 119 | } 120 | .masthead .muted a { 121 | text-decoration:none; 122 | color:#999999; 123 | } 124 | .masthead ul, .masthead li { 125 | margin-bottom:0px; 126 | } 127 | .masthead .nav li { 128 | margin-top: 15px; 129 | font-size:110%; 130 | } 131 | .masthead h3 { 132 | margin-bottom:5px; 133 | font-size:170%; 134 | } 135 | hr { 136 | margin:0px 0px 20px 0px; 137 | } 138 | 139 | /* Make table headings and td.title bold */ 140 | td.title, thead { 141 | font-weight:bold; 142 | } 143 | 144 | /* Format the right-side menu */ 145 | #menu { 146 | margin-top:50px; 147 | font-size:11pt; 148 | padding-left:20px; 149 | } 150 | 151 | #menu .nav-header { 152 | font-size:12pt; 153 | color:#606060; 154 | margin-top:20px; 155 | } 156 | 157 | #menu li { 158 | line-height:25px; 159 | } 160 | 161 | .wrapper { 162 | margin-top: -56px; 163 | padding-top: 56px; 164 | } 165 | 166 | /* Change font sizes for headings etc. */ 167 | #main h1 { font-size: 26pt; margin:10px 0px 15px 0px; font-weight:400; } 168 | #main h2 { font-size: 20pt; margin:20px 0px 0px 0px; font-weight:400; } 169 | #main h3 { font-size: 14pt; margin:15px 0px 0px 0px; font-weight:600; } 170 | #main p { font-size: 11pt; margin:5px 0px 15px 0px; } 171 | #main ul { font-size: 11pt; margin-top:10px; } 172 | #main li { font-size: 11pt; margin: 5px 0px 5px 0px; } 173 | #main strong { font-weight:700; } 174 | 175 | /*-------------------------------------------------------------------------- 176 | Formatting for API reference 177 | /*--------------------------------------------------------------------------*/ 178 | 179 | .type-list .type-name, .module-list .module-name { 180 | width:25%; 181 | font-weight:bold; 182 | } 183 | .member-list .member-name { 184 | width:35%; 185 | } 186 | #main .xmldoc h2 { 187 | font-size:14pt; 188 | margin:10px 0px 0px 0px; 189 | } 190 | #main .xmldoc h3 { 191 | font-size:12pt; 192 | margin:10px 0px 0px 0px; 193 | } 194 | .github-link { 195 | float:right; 196 | text-decoration:none; 197 | } 198 | .github-link img { 199 | border-style:none; 200 | margin-left:10px; 201 | } 202 | .github-link .hover { display:none; } 203 | .github-link:hover .hover { display:block; } 204 | .github-link .normal { display: block; } 205 | .github-link:hover .normal { display: none; } 206 | 207 | /*-------------------------------------------------------------------------- 208 | Links 209 | /*--------------------------------------------------------------------------*/ 210 | 211 | .bootstrap h1 a, .bootstrap h1 a:hover, .bootstrap h1 a:focus, 212 | .bootstrap h2 a, .bootstrap h2 a:hover, .bootstrap h2 a:focus, 213 | .bootstrap h3 a, .bootstrap h3 a:hover, .bootstrap h3 a:focus, 214 | .bootstrap h4 a, .bootstrap h4 a:hover, .bootstrap h4 a:focus, 215 | .bootstrap h5 a, .bootstrap h5 a:hover, .bootstrap h5 a:focus, 216 | .bootstrap h6 a, .bootstrap h6 a:hover, .bootstrap h6 a:focus { color : inherit; text-decoration : inherit; outline:none } 217 | 218 | /*-------------------------------------------------------------------------- 219 | Additional formatting for the homepage 220 | /*--------------------------------------------------------------------------*/ 221 | 222 | #nuget { 223 | margin-top:20px; 224 | font-size: 11pt; 225 | padding:20px; 226 | } 227 | 228 | #nuget pre { 229 | font-size:11pt; 230 | -moz-border-radius: 0px; 231 | -webkit-border-radius: 0px; 232 | border-radius: 0px; 233 | background: #404040; 234 | border-style:none; 235 | color: #e0e0e0; 236 | margin-top:15px; 237 | } 238 | 239 | .date { 240 | font-style: italic; 241 | margin-bottom: 15px; 242 | } 243 | 244 | h1.header { 245 | color: green; 246 | } 247 | 248 | h1.header:hover { 249 | color: green; 250 | } 251 | 252 | h1.header:visited { 253 | color: green; 254 | } 255 | 256 | .categories, .category, .recent-posts { 257 | font-family: 'Droid Sans', arial, sans-serif; 258 | } 259 | 260 | .categories ul, 261 | .recent-posts ul { 262 | margin-left: 0; 263 | } 264 | .categories li, 265 | .category li, 266 | .recent-posts li 267 | { 268 | list-style-type: none; 269 | white-space: nowrap; 270 | } 271 | 272 | .links { 273 | text-align: center; 274 | margin-bottom: 8px; 275 | } 276 | 277 | .copyright { 278 | text-align: center; 279 | color: lightslategray; 280 | margin-bottom: 25px; 281 | } 282 | 283 | .social { 284 | margin-bottom: 30px; 285 | } 286 | 287 | /* Fixes page anchors with bootstrap navbar */ 288 | :target::before { 289 | display: block; 290 | height: 59px; 291 | margin-top: -59px; 292 | content: ""; 293 | } 294 | 295 | /* Hides first br from FSharp.Literate xml-doc rendering */ 296 | .comment-block > br:first-child, 297 | .xmldoc > br:first-child { 298 | display: none; 299 | } 300 | 301 | .main h1 { 302 | padding: .5em 0em 303 | } 304 | 305 | .main h2 { 306 | padding: .5em 0em 307 | } 308 | 309 | .dropdown-submenu { 310 | position: relative; 311 | } 312 | 313 | .dropdown-submenu>a:after { 314 | content: "\f0da"; 315 | padding-left: 5px; 316 | vertical-align: middle; 317 | border: none; 318 | font-weight: 900; 319 | font-family: 'Font Awesome 5 Free'; 320 | } 321 | 322 | .dropdown-submenu>.dropdown-menu { 323 | top: 0; 324 | left: 100%; 325 | margin-top: 0px; 326 | margin-left: 0px; 327 | } 328 | 329 | .fsharp-footer-logo { 330 | width: 20px; 331 | margin-top: -2px; 332 | -webkit-filter: grayscale(100%) brightness(0) invert(1); /* Safari 6.0 - 9.0 */ 333 | filter: grayscale(100%) brightness(0) invert(1); 334 | } 335 | -------------------------------------------------------------------------------- /docsSrc/content/submenu.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | // ------------------------------------------------------- // 3 | // Multi Level dropdowns 4 | // ------------------------------------------------------ // 5 | $("ul.dropdown-menu [data-toggle='dropdown']").on("click", function(event) { 6 | event.preventDefault(); 7 | event.stopPropagation(); 8 | 9 | $(this).siblings().toggleClass("show"); 10 | 11 | 12 | if (!$(this).next().hasClass('show')) { 13 | $(this).parents('.dropdown-menu').first().find('.show').removeClass("show"); 14 | } 15 | $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function(e) { 16 | $('.dropdown-submenu .show').removeClass("show"); 17 | }); 18 | 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /docsSrc/content/themes.js: -------------------------------------------------------------------------------- 1 | 2 | var themes = { 3 | "light" : { 4 | "button-text" : "Swap to Dark", 5 | "button-classes" : "btn btn-dark border-light", 6 | "next-theme" : "dark", 7 | "body-class" : "bootstrap" 8 | }, 9 | "dark" : { 10 | "button-text" : "Swap to Light", 11 | "button-classes" : "btn btn-light", 12 | "next-theme" : "light", 13 | "body-class" : "bootstrap-dark" 14 | } 15 | }; 16 | 17 | var themeStorageKey = 'theme'; 18 | 19 | function swapThemeInDom(theme) { 20 | var newTheme = themes[theme]; 21 | var bootstrapCSS = document.getElementsByTagName('body')[0]; 22 | bootstrapCSS.setAttribute('class', newTheme['body-class']) 23 | } 24 | 25 | function persistNewTheme(theme) { 26 | window.localStorage.setItem(themeStorageKey, theme); 27 | } 28 | 29 | function setToggleButton(theme) { 30 | var newTheme = themes[theme]; 31 | var themeToggleButton = document.getElementById('theme-toggle'); 32 | themeToggleButton.textContent = newTheme['button-text']; 33 | themeToggleButton.className = newTheme['button-classes']; 34 | themeToggleButton.onclick = function() { 35 | setTheme(newTheme['next-theme']); 36 | } 37 | } 38 | 39 | function setTheme(theme) { 40 | try { 41 | swapThemeInDom(theme); 42 | } 43 | catch(e){ 44 | } 45 | try { 46 | persistNewTheme(theme); 47 | } 48 | catch(e) { 49 | } 50 | try { 51 | setToggleButton(theme); 52 | } 53 | catch (e) { 54 | } 55 | } 56 | 57 | function getThemeFromStorage() { 58 | return window.localStorage.getItem(themeStorageKey); 59 | } 60 | 61 | function getThemeFromScheme() { 62 | try { 63 | if (window.matchMedia("(prefers-color-scheme: dark)").matches){ 64 | return 'dark'; 65 | } 66 | else { 67 | return 'light'; 68 | } 69 | } 70 | catch(e) { 71 | return null; 72 | } 73 | } 74 | 75 | function loadTheme() { 76 | var theme = getThemeFromStorage() || getThemeFromScheme() || 'light'; 77 | setTheme(theme); 78 | } 79 | 80 | document.addEventListener('readystatechange', (event) => { 81 | loadTheme() 82 | }); 83 | -------------------------------------------------------------------------------- /docsSrc/content/tips.js: -------------------------------------------------------------------------------- 1 | var currentTip = null; 2 | var currentTipElement = null; 3 | 4 | function hideTip(evt, name, unique) { 5 | var el = document.getElementById(name); 6 | el.style.display = "none"; 7 | currentTip = null; 8 | } 9 | 10 | function findPos(obj) { 11 | // no idea why, but it behaves differently in webbrowser component 12 | if (window.location.search == "?inapp") 13 | return [obj.offsetLeft + 10, obj.offsetTop + 30]; 14 | 15 | var curleft = 0; 16 | var curtop = obj.offsetHeight; 17 | while (obj) { 18 | curleft += obj.offsetLeft; 19 | curtop += obj.offsetTop; 20 | obj = obj.offsetParent; 21 | }; 22 | return [curleft, curtop]; 23 | } 24 | 25 | function hideUsingEsc(e) { 26 | if (!e) { e = event; } 27 | hideTip(e, currentTipElement, currentTip); 28 | } 29 | 30 | function showTip(evt, name, unique, owner) { 31 | document.onkeydown = hideUsingEsc; 32 | if (currentTip == unique) return; 33 | currentTip = unique; 34 | currentTipElement = name; 35 | 36 | var pos = findPos(owner ? owner : (evt.srcElement ? evt.srcElement : evt.target)); 37 | var posx = pos[0]; 38 | var posy = pos[1]; 39 | 40 | var el = document.getElementById(name); 41 | var parent = (document.documentElement == null) ? document.body : document.documentElement; 42 | el.style.position = "absolute"; 43 | el.style.left = posx + "px"; 44 | el.style.top = posy + "px"; 45 | el.style.display = "block"; 46 | } 47 | -------------------------------------------------------------------------------- /docsSrc/files/placeholder.md: -------------------------------------------------------------------------------- 1 | place images or other files here 2 | -------------------------------------------------------------------------------- /docsSrc/index.md: -------------------------------------------------------------------------------- 1 | # TypeSafeInternals 2 | 3 | --- 4 | 5 | ## What is TypeSafeInternals? 6 | 7 | TypeSafeInternals is a library that does this specific thing. 8 | 9 | ## Why use TypeSafeInternals? 10 | 11 | I created it because I had to solve an issue with this other thing. 12 | 13 | --- 14 | 15 |
16 |
17 |
18 |
19 |
Tutorials
20 |

Takes you by the hand through a series of steps to create your first thing.

21 |
22 | 25 |
26 |
27 |
28 |
29 |
30 |
How-To Guides
31 |

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

32 |
33 | 36 |
37 |
38 |
39 |
40 |
41 |
Explanations
42 |

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

43 |
44 | 47 |
48 |
49 |
50 |
51 |
52 |
Api Reference
53 |

Contain technical reference for APIs.

54 |
55 | 58 |
59 |
60 |
61 | -------------------------------------------------------------------------------- /docsTool/CLI.fs: -------------------------------------------------------------------------------- 1 | namespace DocsTool 2 | 3 | module CLIArgs = 4 | open Argu 5 | open Fake.IO.Globbing.Operators 6 | 7 | type WatchArgs = 8 | | ProjectGlob of string 9 | | DocsSourceDirectory of string 10 | | GitHubRepoUrl of string 11 | | ProjectName of string 12 | | ReleaseVersion of string 13 | with 14 | interface IArgParserTemplate with 15 | member this.Usage = 16 | match this with 17 | | ProjectGlob _ -> "The glob for the dlls to generate API documentation." 18 | | DocsSourceDirectory _ -> "The docs source directory." 19 | | GitHubRepoUrl _ -> "The GitHub repository url." 20 | | ProjectName _ -> "The project name." 21 | | ReleaseVersion _ -> "The project's Release Version name." 22 | 23 | type BuildArgs = 24 | | SiteBaseUrl of string 25 | | ProjectGlob of string 26 | | DocsOutputDirectory of string 27 | | DocsSourceDirectory of string 28 | | GitHubRepoUrl of string 29 | | ProjectName of string 30 | | ReleaseVersion of string 31 | with 32 | interface IArgParserTemplate with 33 | member this.Usage = 34 | match this with 35 | | SiteBaseUrl _ -> "The public site's base url." 36 | | ProjectGlob _ -> "The glob for the dlls to generate API documentation" 37 | | DocsOutputDirectory _ -> "The docs output directory." 38 | | DocsSourceDirectory _ -> "The docs source directory." 39 | | GitHubRepoUrl _ -> "The GitHub repository url." 40 | | ProjectName _ -> "The project name." 41 | | ReleaseVersion _ -> "The project's Release Version name." 42 | 43 | type CLIArguments = 44 | | [] Watch of ParseResults 45 | | [] Build of ParseResults 46 | with 47 | interface IArgParserTemplate with 48 | member this.Usage = 49 | match this with 50 | | Watch _ -> "Builds the docs, serves the content, and watches for changes to the content." 51 | | Build _ -> "Builds the docs" 52 | -------------------------------------------------------------------------------- /docsTool/Prelude.fs: -------------------------------------------------------------------------------- 1 | namespace DocsTool 2 | 3 | module Uri = 4 | open System 5 | let simpleCombine (slug : string) (baseUri : Uri) = 6 | sprintf "%s/%s" (baseUri.AbsoluteUri.TrimEnd('/')) (slug.TrimStart('/')) 7 | 8 | let create (url : string) = 9 | match Uri.TryCreate(url, UriKind.Absolute) with 10 | | (true, v) -> v 11 | | _ -> failwithf "Bad url %s" url 12 | 13 | 14 | 15 | module Diposeable = 16 | open System 17 | open Fake.Core 18 | let dispose (d : #IDisposable) = d.Dispose() 19 | 20 | type DisposableList = 21 | { 22 | Disposables : IDisposable list 23 | } interface IDisposable with 24 | member x.Dispose () = 25 | x.Disposables |> List.iter(dispose) 26 | static member Create(disposables) = 27 | { 28 | Disposables = disposables 29 | } :> IDisposable 30 | 31 | type DisposableDirectory (directory : string) = 32 | do 33 | Trace.tracefn "Created disposable directory %s" directory 34 | static member Create() = 35 | let tempPath = IO.Path.Combine(IO.Path.GetTempPath(), Guid.NewGuid().ToString("n")) 36 | IO.Directory.CreateDirectory tempPath |> ignore 37 | 38 | new DisposableDirectory(tempPath) 39 | member x.Directory = directory 40 | member x.DirectoryInfo = IO.DirectoryInfo(directory) 41 | 42 | interface IDisposable with 43 | member x.Dispose() = 44 | Trace.tracefn "Deleting directory %s" directory 45 | IO.Directory.Delete(x.Directory,true) 46 | -------------------------------------------------------------------------------- /docsTool/Program.fs: -------------------------------------------------------------------------------- 1 | open System 2 | open Fake.IO.FileSystemOperators 3 | open Fake.IO 4 | open Fake.Core 5 | 6 | type Configuration = { 7 | SiteBaseUrl : Uri 8 | GitHubRepoUrl : Uri 9 | RepositoryRoot : IO.DirectoryInfo 10 | DocsOutputDirectory : IO.DirectoryInfo 11 | DocsSourceDirectory : IO.DirectoryInfo 12 | ProjectName : string 13 | ProjectFilesGlob : IGlobbingPattern 14 | ReleaseVersion : string 15 | PublishPath : IO.DirectoryInfo 16 | } 17 | 18 | module GenerateDocs = 19 | open DocsTool 20 | open Fake.IO.Globbing.Operators 21 | open Fable.React 22 | open FSharp.Literate 23 | open System.IO 24 | open FSharp.MetadataFormat 25 | 26 | let docsApiDir docsDir = docsDir @@ "Api_Reference" 27 | 28 | type GeneratedDoc = { 29 | SourcePath : FileInfo option 30 | OutputPath : FileInfo 31 | Content : ReactElement list 32 | Title : string 33 | } 34 | 35 | let docsFileGlob docsSrcDir = 36 | !! (docsSrcDir @@ "**/*.fsx") 37 | ++ (docsSrcDir @@ "**/*.md") 38 | 39 | let render html = 40 | fragment [] [ 41 | RawText "" 42 | RawText "\n" 43 | html ] 44 | |> Fable.ReactServer.renderToString 45 | 46 | let renderWithMasterTemplate masterCfg navBar titletext bodytext pageSource = 47 | Master.masterTemplate masterCfg navBar titletext bodytext pageSource 48 | |> render 49 | 50 | let renderWithMasterAndWrite masterCfg (outPath : FileInfo) navBar titletext bodytext pageSource = 51 | let contents = renderWithMasterTemplate masterCfg navBar titletext bodytext pageSource 52 | IO.Directory.CreateDirectory(outPath.DirectoryName) |> ignore 53 | 54 | IO.File.WriteAllText(outPath.FullName, contents) 55 | Fake.Core.Trace.tracefn "Rendered to %s" outPath.FullName 56 | 57 | let generateNav (cfg : Configuration) (generatedDocs : GeneratedDoc list) = 58 | let docsDir = cfg.DocsOutputDirectory.FullName 59 | let pages = 60 | generatedDocs 61 | |> List.map(fun gd -> gd.OutputPath) 62 | |> List.filter(fun f -> f.FullName.StartsWith(docsDir "content") |> not) 63 | |> List.filter(fun f -> f.FullName.StartsWith(docsDir "files") |> not) 64 | |> List.filter(fun f -> f.FullName.StartsWith(docsDir "index.html") |> not) 65 | 66 | let topLevelNavs : Nav.TopLevelNav = { 67 | DocsRoot = IO.DirectoryInfo docsDir 68 | DocsPages = pages 69 | } 70 | 71 | let navCfg : Nav.NavConfig = { 72 | SiteBaseUrl = cfg.SiteBaseUrl 73 | GitHubRepoUrl = cfg.GitHubRepoUrl 74 | ProjectName = cfg.ProjectName 75 | TopLevelNav = topLevelNavs 76 | } 77 | 78 | Nav.generateNav navCfg 79 | 80 | let renderGeneratedDocs isWatchMode (cfg : Configuration) (generatedDocs : GeneratedDoc list) = 81 | let nav = generateNav cfg generatedDocs 82 | let masterCfg : Master.MasterTemplateConfig = { 83 | SiteBaseUrl = cfg.SiteBaseUrl 84 | GitHubRepoUrl = cfg.GitHubRepoUrl 85 | ProjectName = cfg.ProjectName 86 | ReleaseVersion = cfg.ReleaseVersion 87 | ReleaseDate = DateTimeOffset.Now 88 | RepositoryRoot = cfg.RepositoryRoot 89 | IsWatchMode = isWatchMode 90 | } 91 | generatedDocs 92 | |> Seq.iter(fun gd -> 93 | let pageSource = 94 | gd.SourcePath 95 | |> Option.map(fun sp -> 96 | sp.FullName.Replace(cfg.RepositoryRoot.FullName, "").Replace("\\", "/") 97 | ) 98 | renderWithMasterAndWrite masterCfg gd.OutputPath nav gd.Title gd.Content pageSource 99 | ) 100 | 101 | 102 | let copyAssets (cfg : Configuration) = 103 | Shell.copyDir (cfg.DocsOutputDirectory.FullName "content") ( cfg.DocsSourceDirectory.FullName "content") (fun _ -> true) 104 | Shell.copyDir (cfg.DocsOutputDirectory.FullName "files") ( cfg.DocsSourceDirectory.FullName "files") (fun _ -> true) 105 | 106 | 107 | let regexReplace (cfg : Configuration) source = 108 | let replacements = 109 | [ 110 | "{{siteBaseUrl}}", (cfg.SiteBaseUrl.ToString().TrimEnd('/')) 111 | ] 112 | (source, replacements) 113 | ||> List.fold(fun state (pattern, replacement) -> 114 | Text.RegularExpressions.Regex.Replace(state, pattern, replacement) 115 | ) 116 | 117 | let stringContainsInsenstive (filter : string) (textToSearch : string) = 118 | textToSearch.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase) >= 0 119 | 120 | let generateDocs (docSourcePaths : IGlobbingPattern) (cfg : Configuration) = 121 | let parse (fileName : string) source = 122 | let doc = 123 | let fsiArgs = 124 | [| 125 | yield "--noframework" // error FS1222: When mscorlib.dll or FSharp.Core.dll is explicitly referenced the --noframework option must also be passed 126 | yield sprintf "-I:\"%s\"" cfg.PublishPath.FullName 127 | |] 128 | 129 | let dlls = 130 | cfg.PublishPath.EnumerateFiles() 131 | |> Seq.map(fun fi -> fi.FullName) 132 | |> Seq.filter(fun f -> f.EndsWith(".dll")) 133 | |> Seq.map (sprintf "-r:%s") 134 | 135 | let compilerOptions = 136 | [| 137 | yield "--targetprofile:netstandard" 138 | yield! dlls 139 | |] 140 | let fsiEvaluator = FSharp.Literate.FsiEvaluator(fsiArgs) 141 | match Path.GetExtension fileName with 142 | | ".fsx" -> 143 | Literate.ParseScriptString( 144 | source, 145 | path = fileName, 146 | compilerOptions = (compilerOptions |> String.concat " "), 147 | fsiEvaluator = fsiEvaluator) 148 | | ".md" -> 149 | let source = regexReplace cfg source 150 | Literate.ParseMarkdownString( 151 | source, 152 | path = fileName, 153 | compilerOptions = (compilerOptions |> String.concat " "), 154 | fsiEvaluator = fsiEvaluator 155 | ) 156 | | others -> failwithf "FSharp.Literal does not support %s file extensions" others 157 | FSharp.Literate.Literate.FormatLiterateNodes(doc, OutputKind.Html, "", true, true) 158 | 159 | let format (doc: LiterateDocument) = 160 | if not <| Seq.isEmpty doc.Errors 161 | then 162 | failwithf "error while formatting file %s. Errors are:\n%A" doc.SourceFile doc.Errors 163 | else 164 | Formatting.format doc.MarkdownDocument true OutputKind.Html 165 | + doc.FormattedTips 166 | 167 | 168 | 169 | docSourcePaths 170 | |> Array.ofSeq 171 | |> Seq.map(fun filePath -> 172 | 173 | Fake.Core.Trace.tracefn "Rendering %s" filePath 174 | let file = IO.File.ReadAllText filePath 175 | let outPath = 176 | let changeExtension ext path = IO.Path.ChangeExtension(path,ext) 177 | filePath.Replace(cfg.DocsSourceDirectory.FullName, cfg.DocsOutputDirectory.FullName) 178 | |> changeExtension ".html" 179 | |> FileInfo 180 | let fs = 181 | file 182 | |> parse filePath 183 | |> format 184 | let contents = 185 | [div [] [ 186 | fs 187 | |> RawText 188 | ]] 189 | 190 | { 191 | SourcePath = FileInfo filePath |> Some 192 | OutputPath = outPath 193 | Content = contents 194 | Title = sprintf "%s-%s" outPath.Name cfg.ProjectName 195 | } 196 | ) 197 | |> Seq.toList 198 | 199 | /// The reason we do dotnet publish is because it will put all the referenced dlls into one folder. This makes it easy for tools to find the reference and we don't have to use FCS or any dotnet tools to try to analyze the project file and find where all the references are. 200 | let dotnetPublish (cfg : Configuration) = 201 | cfg.ProjectFilesGlob 202 | |> Seq.iter(fun p -> 203 | Fake.DotNet.DotNet.publish 204 | (fun opts -> 205 | { opts 206 | with 207 | OutputPath = Some cfg.PublishPath.FullName 208 | Framework = Some "netstandard2.1" 209 | }) 210 | p 211 | ) 212 | 213 | let generateAPI (cfg : Configuration) = 214 | 215 | let generate (projInfo : string) = 216 | Trace.tracefn "Generating API Docs for %s" projInfo 217 | let libDirs = [cfg.PublishPath.FullName] 218 | let projName = IO.Path.GetFileNameWithoutExtension(projInfo) 219 | let targetApiDir = docsApiDir cfg.DocsOutputDirectory.FullName @@ projName 220 | let projDll = cfg.PublishPath.FullName @@ sprintf "%s.dll" projName 221 | let generatorOutput = 222 | MetadataFormat.Generate( 223 | projDll, 224 | libDirs = libDirs, 225 | sourceFolder = cfg.RepositoryRoot.FullName, 226 | sourceRepo = (cfg.GitHubRepoUrl |> Uri.simpleCombine "tree/master" |> string), 227 | markDownComments = false 228 | ) 229 | 230 | let fi = FileInfo <| targetApiDir @@ (sprintf "%s.html" generatorOutput.AssemblyGroup.Name) 231 | let indexDoc = { 232 | SourcePath = None 233 | OutputPath = fi 234 | Content = [Namespaces.generateNamespaceDocs generatorOutput.AssemblyGroup generatorOutput.Properties] 235 | Title = sprintf "%s-%s" fi.Name cfg.ProjectName 236 | } 237 | 238 | let moduleDocs = 239 | generatorOutput.ModuleInfos 240 | |> List.map (fun m -> 241 | let fi = FileInfo <| targetApiDir @@ (sprintf "%s.html" m.Module.UrlName) 242 | let content = Modules.generateModuleDocs m generatorOutput.Properties 243 | { 244 | SourcePath = None 245 | OutputPath = fi 246 | Content = content 247 | Title = sprintf "%s-%s" m.Module.Name cfg.ProjectName 248 | } 249 | ) 250 | let typeDocs = 251 | generatorOutput.TypesInfos 252 | |> List.map (fun m -> 253 | let fi = FileInfo <| targetApiDir @@ (sprintf "%s.html" m.Type.UrlName) 254 | let content = Types.generateTypeDocs m generatorOutput.Properties 255 | { 256 | SourcePath = None 257 | OutputPath = fi 258 | Content = content 259 | Title = sprintf "%s-%s" m.Type.Name cfg.ProjectName 260 | } 261 | ) 262 | [ indexDoc ] @ moduleDocs @ typeDocs 263 | cfg.ProjectFilesGlob 264 | |> Seq.toArray 265 | |> Array.Parallel.collect(generate >> List.toArray) 266 | |> Array.toList 267 | 268 | let renderDocs (cfg : Configuration) = 269 | copyAssets cfg 270 | let generateDocs = 271 | async { 272 | try 273 | return generateDocs (docsFileGlob cfg.DocsSourceDirectory.FullName) cfg 274 | with e -> 275 | eprintfn "generateDocs failure %A" e 276 | return raise e 277 | } 278 | 279 | let generateAPI = 280 | async { 281 | return generateAPI cfg 282 | } 283 | 284 | dotnetPublish cfg 285 | Async.Parallel [generateDocs; generateAPI] 286 | |> Async.RunSynchronously 287 | |> Array.toList 288 | |> List.collect id 289 | 290 | let buildDocs (cfg : Configuration) = 291 | renderDocs cfg 292 | |> renderGeneratedDocs false cfg 293 | 294 | let watchDocs (refreshWebpageEvent : Event<_>) (cfg : Configuration) = 295 | let initialDocs = renderDocs cfg 296 | let renderGeneratedDocs = renderGeneratedDocs true 297 | initialDocs |> renderGeneratedDocs cfg 298 | 299 | let docsSrcWatcher = 300 | docsFileGlob cfg.DocsSourceDirectory.FullName 301 | |> ChangeWatcher.run (fun changes -> 302 | printfn "changes %A" changes 303 | changes 304 | |> Seq.iter (fun m -> 305 | printfn "watching %s" m.FullPath 306 | let generated = generateDocs (!! m.FullPath) cfg 307 | initialDocs 308 | |> List.filter(fun x -> generated |> List.exists(fun y -> y.OutputPath = x.OutputPath) |> not ) 309 | |> List.append generated 310 | |> List.distinctBy(fun gd -> gd.OutputPath.FullName) 311 | |> renderGeneratedDocs cfg 312 | ) 313 | refreshWebpageEvent.Trigger "m.FullPath" 314 | ) 315 | 316 | let contentWatcher = 317 | !! (cfg.DocsSourceDirectory.FullName "content" "**/*") 318 | ++ (cfg.DocsSourceDirectory.FullName "files" "**/*") 319 | |> ChangeWatcher.run(fun changes -> 320 | printfn "changes %A" changes 321 | copyAssets cfg 322 | refreshWebpageEvent.Trigger "Assets" 323 | ) 324 | 325 | let typesToWatch = [ 326 | ".fs" 327 | ".fsx" 328 | ".fsproj" 329 | ] 330 | let apiDocsWatcher = 331 | // NOTE: ChangeWatch doesn't seem to like globs in some case and wants full paths 332 | let glob = 333 | cfg.ProjectFilesGlob // Get all src projects 334 | |> Seq.map(fun p -> (FileInfo p).Directory.FullName "**") // Create glob for all files in fsproj folder 335 | |> Seq.fold ((++)) (!! "") // Expand to get all files 336 | |> Seq.filter(fun file -> typesToWatch |> Seq.exists file.EndsWith) // Filter for only F# style files 337 | |> Seq.fold ((++)) (!! "") // Turn into glob for ChangeWatcher 338 | glob 339 | |> ChangeWatcher.run 340 | (fun changes -> 341 | changes 342 | |> Seq.iter(fun c -> Trace.logf "Regenerating API docs due to %s" c.FullPath ) 343 | dotnetPublish cfg 344 | let generated = generateAPI cfg 345 | initialDocs 346 | |> List.filter(fun x -> generated |> List.exists(fun y -> y.OutputPath = x.OutputPath) |> not ) 347 | |> List.append generated 348 | |> List.distinctBy(fun gd -> gd.OutputPath.FullName) 349 | |> renderGeneratedDocs cfg 350 | refreshWebpageEvent.Trigger "Api" 351 | ) 352 | 353 | [ 354 | docsSrcWatcher 355 | contentWatcher 356 | apiDocsWatcher 357 | ] 358 | |> Diposeable.DisposableList.Create 359 | 360 | 361 | open FSharp.Formatting.Common 362 | open System.Diagnostics 363 | 364 | let setupFsharpFormattingLogging () = 365 | let setupListener listener = 366 | [ 367 | FSharp.Formatting.Common.Log.source 368 | Yaaf.FSharp.Scripting.Log.source 369 | ] 370 | |> Seq.iter (fun source -> 371 | source.Switch.Level <- System.Diagnostics.SourceLevels.All 372 | Log.AddListener listener source) 373 | let noTraceOptions = TraceOptions.None 374 | Log.ConsoleListener() 375 | |> Log.SetupListener noTraceOptions System.Diagnostics.SourceLevels.Verbose 376 | |> setupListener 377 | 378 | 379 | 380 | let refreshWebpageEvent = new Event() 381 | 382 | open Argu 383 | open Fake.IO.Globbing.Operators 384 | open DocsTool 385 | open DocsTool.CLIArgs 386 | open DocsTool.Diposeable 387 | [] 388 | let main argv = 389 | try 390 | use tempDocsOutDir = DisposableDirectory.Create() 391 | use publishPath = DisposableDirectory.Create() 392 | use __ = AppDomain.CurrentDomain.ProcessExit.Subscribe(fun _ -> 393 | dispose tempDocsOutDir 394 | dispose publishPath 395 | ) 396 | use __ = Console.CancelKeyPress.Subscribe(fun _ -> 397 | dispose tempDocsOutDir 398 | dispose publishPath 399 | ) 400 | let defaultConfig = { 401 | SiteBaseUrl = Uri(sprintf "http://%s:%d/" WebServer.hostname WebServer.port ) 402 | GitHubRepoUrl = Uri "https://github.com" 403 | RepositoryRoot = IO.DirectoryInfo (__SOURCE_DIRECTORY__ @@ "..") 404 | DocsOutputDirectory = tempDocsOutDir.DirectoryInfo 405 | DocsSourceDirectory = IO.DirectoryInfo "docsSrc" 406 | ProjectName = "" 407 | ProjectFilesGlob = !! "" 408 | PublishPath = publishPath.DirectoryInfo 409 | ReleaseVersion = "0.1.0" 410 | } 411 | 412 | let errorHandler = ProcessExiter(colorizer = function ErrorCode.HelpText -> None | _ -> Some ConsoleColor.Red) 413 | let programName = 414 | let name = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name 415 | if Environment.isWindows then 416 | sprintf "%s.exe" name 417 | else 418 | name 419 | 420 | let parser = ArgumentParser.Create(programName = programName, errorHandler = errorHandler) 421 | let parsedArgs = parser.Parse argv 422 | match parsedArgs.GetSubCommand() with 423 | | Build args -> 424 | let config = 425 | (defaultConfig, args.GetAllResults()) 426 | ||> List.fold(fun state next -> 427 | match next with 428 | | BuildArgs.SiteBaseUrl url -> { state with SiteBaseUrl = Uri url } 429 | | BuildArgs.ProjectGlob glob -> { state with ProjectFilesGlob = !! glob} 430 | | BuildArgs.DocsOutputDirectory outdir -> { state with DocsOutputDirectory = IO.DirectoryInfo outdir} 431 | | BuildArgs.DocsSourceDirectory srcdir -> { state with DocsSourceDirectory = IO.DirectoryInfo srcdir} 432 | | BuildArgs.GitHubRepoUrl url -> { state with GitHubRepoUrl = Uri url} 433 | | BuildArgs.ProjectName repo -> { state with ProjectName = repo} 434 | | BuildArgs.ReleaseVersion version -> { state with ReleaseVersion = version} 435 | ) 436 | GenerateDocs.buildDocs config 437 | | Watch args -> 438 | let config = 439 | (defaultConfig, args.GetAllResults()) 440 | ||> List.fold(fun state next -> 441 | match next with 442 | | WatchArgs.ProjectGlob glob -> {state with ProjectFilesGlob = !! glob} 443 | | WatchArgs.DocsSourceDirectory srcdir -> { state with DocsSourceDirectory = IO.DirectoryInfo srcdir} 444 | | WatchArgs.GitHubRepoUrl url -> { state with GitHubRepoUrl = Uri url} 445 | | WatchArgs.ProjectName repo -> { state with ProjectName = repo} 446 | | WatchArgs.ReleaseVersion version -> { state with ReleaseVersion = version} 447 | ) 448 | use ds = GenerateDocs.watchDocs refreshWebpageEvent config 449 | WebServer.serveDocs refreshWebpageEvent config.DocsOutputDirectory.FullName 450 | 0 451 | with e -> 452 | eprintfn "Fatal error: %A" e 453 | 1 454 | -------------------------------------------------------------------------------- /docsTool/README.md: -------------------------------------------------------------------------------- 1 | # Docs Tool 2 | 3 | ## Example 4 | [MiniScaffold docs example](https://www.jimmybyrd.me/miniscaffold-docs-test/) 5 | 6 | ## Docs High Level Design 7 | 8 | This template is based heavily on [What nobody tells you about documentation](https://www.divio.com/blog/documentation/). In `docsSrc` folder you'll see a similar structure to what is described below: 9 | 10 | - **Tutorials** 11 | - is learning-oriented 12 | - allows the newcomer to get started 13 | - is a lesson 14 | - Analogy: teaching a small child how to cook 15 | - **How-To Guides** 16 | - is goal-oriented 17 | - shows how to solve a specific problem 18 | - is a series of steps 19 | - Analogy: a recipe in a cookery book 20 | - **Explanation** 21 | - is understanding-oriented 22 | - explains 23 | - provides background and context 24 | - Analogy: an article on culinary social history 25 | - **Reference** 26 | - is information-oriented 27 | - describes the machinery 28 | - is accurate and complete 29 | - Analogy: a reference encyclopedia article 30 | 31 | 32 | The folders in `docsSrc` are: 33 | 34 | - `content` - custom css, javascript, and similar go here. 35 | - `Explanations` - A content section as defined above. 36 | - `files` - extra files like screenshots, images, videos. 37 | - `How_Tos` - A content section as defined above. 38 | - `Tutorials` - A content section as defined above. 39 | - `index.md` - The entry page to your documentation 40 | 41 | The navbar is generated by the folders in docsSrc, excluding `content` and `files` folders. Looking at the [example](https://www.jimmybyrd.me/miniscaffold-docs-test/) we can the navbar containing: 42 | 43 | - `Api References` 44 | - `Explanations` 45 | - `How Tos` 46 | - `Tutorials` 47 | 48 | The odd one not generated from the convention of your folders in docsSrc is Api References. This is generated by the [XML Doc Comments](https://docs.microsoft.com/en-us/dotnet/csharp/codedoc) in your libraries under the `src` folder. 49 | 50 | 51 | ## Running docs tool 52 | 53 | ``` 54 | USAGE: docsTool [--help] [ []] 55 | 56 | SUBCOMMANDS: 57 | 58 | watch Builds the docs, serves the content, and watches for changes to the content. 59 | build Builds the docs 60 | 61 | Use 'docsTool --help' for additional information. 62 | ``` 63 | 64 | ### build 65 | 66 | Builds the docs 67 | 68 | ``` 69 | USAGE: docsTool build [--help] [--sitebaseurl ] [--projectglob ] [--docsoutputdirectory ] [--docssourcedirectory ] [--githubrepourl ] [--projectname ] 70 | [--releaseversion ] 71 | 72 | OPTIONS: 73 | 74 | --sitebaseurl 75 | The public site's base url. 76 | --projectglob 77 | The glob for the dlls to generate API documentation 78 | --docsoutputdirectory 79 | The docs output directory. 80 | --docssourcedirectory 81 | The docs source directory. 82 | --githubrepourl 83 | The GitHub repository url. 84 | --projectname 85 | The project name. 86 | --releaseversion 87 | The project's Release Version name. 88 | --help display this list of options. 89 | 90 | ``` 91 | 92 | 93 | ### watch 94 | 95 | Builds the docs, serves the content, and watches for changes to the content. 96 | 97 | ``` 98 | 99 | USAGE: docsTool watch [--help] [--projectglob ] [--docsoutputdirectory ] [--docssourcedirectory ] [--githubrepourl ] [--projectname ] [--releaseversion ] 100 | 101 | OPTIONS: 102 | 103 | --projectglob 104 | The glob for the dlls to generate API documentation. 105 | --docsoutputdirectory 106 | The docs output directory. 107 | --docssourcedirectory 108 | The docs source directory. 109 | --githubrepourl 110 | The GitHub repository url. 111 | --projectname 112 | The project name. 113 | --releaseversion 114 | The project's Release Version name. 115 | --help display this list of options. 116 | ``` 117 | 118 | -------------------------------------------------------------------------------- /docsTool/WebServer.fs: -------------------------------------------------------------------------------- 1 | namespace DocsTool 2 | 3 | module WebServer = 4 | open Microsoft.AspNetCore.Hosting 5 | open Microsoft.AspNetCore.Builder 6 | open Microsoft.Extensions.FileProviders 7 | open Microsoft.AspNetCore.Http 8 | open System 9 | open System.Net.WebSockets 10 | open System.Diagnostics 11 | open System.Runtime.InteropServices 12 | 13 | let hostname = "localhost" 14 | let port = 5000 15 | 16 | /// Helper to determine if port is in use 17 | let waitForPortInUse (hostname : string) port = 18 | let mutable portInUse = false 19 | while not portInUse do 20 | Async.Sleep(10) |> Async.RunSynchronously 21 | use client = new Net.Sockets.TcpClient() 22 | try 23 | client.Connect(hostname,port) 24 | portInUse <- client.Connected 25 | client.Close() 26 | with e -> 27 | client.Close() 28 | 29 | /// Async version of IApplicationBuilder.Use 30 | let useAsync (middlware : HttpContext -> (unit -> Async) -> Async) (app:IApplicationBuilder) = 31 | app.Use(fun env next -> 32 | middlware env (next.Invoke >> Async.AwaitTask) 33 | |> Async.StartAsTask 34 | :> System.Threading.Tasks.Task 35 | ) 36 | 37 | let createWebsocketForLiveReload (refreshWebpageEvent : Event) (httpContext : HttpContext) (next : unit -> Async) = async { 38 | if httpContext.WebSockets.IsWebSocketRequest then 39 | let! websocket = httpContext.WebSockets.AcceptWebSocketAsync() |> Async.AwaitTask 40 | use d = 41 | refreshWebpageEvent.Publish 42 | |> Observable.subscribe (fun m -> 43 | let segment = ArraySegment(m |> Text.Encoding.UTF8.GetBytes) 44 | websocket.SendAsync(segment, WebSocketMessageType.Text, true, httpContext.RequestAborted) 45 | |> Async.AwaitTask 46 | |> Async.Start 47 | 48 | ) 49 | while websocket.State <> WebSocketState.Closed do 50 | do! Async.Sleep(1000) 51 | else 52 | do! next () 53 | } 54 | 55 | let configureWebsocket (refreshWebpageEvent : Event) (appBuilder : IApplicationBuilder) = 56 | appBuilder.UseWebSockets() 57 | |> useAsync (createWebsocketForLiveReload refreshWebpageEvent) 58 | |> ignore 59 | 60 | let startWebserver (refreshWebpageEvent : Event) docsDir (url : string) = 61 | WebHostBuilder() 62 | .UseKestrel() 63 | .UseUrls(url) 64 | .Configure(fun app -> 65 | let opts = 66 | StaticFileOptions( 67 | FileProvider = new PhysicalFileProvider(docsDir) 68 | ) 69 | app.UseStaticFiles(opts) |> ignore 70 | configureWebsocket refreshWebpageEvent app 71 | ) 72 | .Build() 73 | .Run() 74 | 75 | let openBrowser url = 76 | let waitForExit (proc : Process) = 77 | proc.WaitForExit() 78 | if proc.ExitCode <> 0 then eprintf "opening browser failed, open your browser and navigate to url to see the docs site." 79 | try 80 | let psi = ProcessStartInfo(FileName = url, UseShellExecute = true) 81 | Process.Start psi 82 | |> waitForExit 83 | with e -> 84 | //https://github.com/dotnet/corefx/issues/10361 85 | if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then 86 | let url = url.Replace("&", "&^") 87 | let psi = ProcessStartInfo("cmd", (sprintf "/c %s" url), CreateNoWindow=true) 88 | Process.Start psi 89 | |> waitForExit 90 | elif RuntimeInformation.IsOSPlatform(OSPlatform.Linux) then 91 | Process.Start("xdg-open", url) 92 | |> waitForExit 93 | elif RuntimeInformation.IsOSPlatform(OSPlatform.OSX) then 94 | Process.Start("open", url) 95 | |> waitForExit 96 | else 97 | failwithf "failed to open browser on current OS" 98 | 99 | let serveDocs refreshEvent docsDir = 100 | async { 101 | waitForPortInUse hostname port 102 | sprintf "http://%s:%d/index.html" hostname port |> openBrowser 103 | } |> Async.Start 104 | startWebserver refreshEvent docsDir (sprintf "http://%s:%d" hostname port) 105 | -------------------------------------------------------------------------------- /docsTool/docsTool.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docsTool/paket.references: -------------------------------------------------------------------------------- 1 | group Docs 2 | Argu 3 | FSharp.Core 4 | Fake.IO.FileSystem 5 | Fake.DotNet.Cli 6 | FSharp.Formatting 7 | FSharp.Literate 8 | Fable.React 9 | -------------------------------------------------------------------------------- /docsTool/templates/helpers.fs: -------------------------------------------------------------------------------- 1 | module Helpers 2 | open System 3 | open Fable.React 4 | open Fable.React.Props 5 | open FSharp.MetadataFormat 6 | 7 | 8 | let createAnchorIcon name = 9 | let normalized = name 10 | let href = sprintf "#%s" normalized 11 | a [Href href; Id normalized] [ 12 | str "#" 13 | ] 14 | 15 | let createAnchor fullName name = 16 | let fullNameNormalize = fullName 17 | a [ 18 | Name fullNameNormalize 19 | Href (sprintf "#%s" fullNameNormalize) 20 | Class "anchor" 21 | ] [ 22 | str name 23 | ] 24 | 25 | let renderNamespace (ns: Namespace) = [ 26 | h3 [] [ str "Namespace" ] 27 | str ns.Name 28 | ] 29 | 30 | let inline isObsolete< ^t when ^t : (member IsObsolete: bool)> t = 31 | (^t : (member IsObsolete: bool) (t)) 32 | 33 | let inline obsoleteMessage< ^t when ^t : (member ObsoleteMessage: string)> t = 34 | (^t : (member ObsoleteMessage:string) (t)) 35 | 36 | let inline renderObsoleteMessage item = 37 | if isObsolete item 38 | then 39 | let text = match obsoleteMessage item with | "" | null -> "This member is obsolete" | s -> s 40 | [ 41 | div [Class "alert alert-warning"] [ 42 | strong [] [ str "OBSOLETE: "] 43 | str text 44 | ] 45 | ] 46 | else 47 | [] 48 | -------------------------------------------------------------------------------- /docsTool/templates/master.fs: -------------------------------------------------------------------------------- 1 | module Master 2 | 3 | open System 4 | open Fable.React 5 | open Fable.React.Props 6 | open DocsTool 7 | 8 | type MasterTemplateConfig = { 9 | SiteBaseUrl : Uri 10 | GitHubRepoUrl : Uri 11 | ProjectName : string 12 | ReleaseVersion : string 13 | ReleaseDate : DateTimeOffset 14 | RepositoryRoot: IO.DirectoryInfo 15 | IsWatchMode : bool 16 | } 17 | 18 | type FAIcon = 19 | | Solid of name: string 20 | | Brand of name: string 21 | 22 | let footerLink uri image linkText = 23 | let faClass, img = 24 | match image with 25 | | Solid name -> "fas", name 26 | | Brand name -> "fab", name 27 | a [Href uri; Class "text-white"] [ 28 | i [Class (sprintf "%s fa-%s fa-fw mr-2" faClass img)] [] 29 | str linkText 30 | ] 31 | 32 | let repoFileLink repoUrl filePathFromRepoRoot = 33 | let link = repoUrl |> Uri.simpleCombine (sprintf "blob/master/%s" filePathFromRepoRoot) 34 | footerLink link 35 | 36 | let linkColumn headerTitle items = 37 | div [Class "col-12 col-md-4 mb-4 mb-md-0"] [ 38 | div [Class "text-light"] [ 39 | h2 [Class "h5"] [ str headerTitle ] 40 | ul [Class "list-group list-group-flush"] 41 | (items |> List.choose (function | [] -> None 42 | | items -> Some(li [Class "list-group-item bg-dark ml-0 pl-0"] items))) 43 | ] 44 | ] 45 | 46 | let renderFooter (cfg : MasterTemplateConfig) (pageSource : string option) = 47 | let hasFile relPath = 48 | match cfg.RepositoryRoot.GetFiles(relPath) with 49 | | [||] -> false 50 | | [|file|] -> true 51 | | files -> false 52 | 53 | let repoFileLink relPath image title = 54 | if hasFile relPath 55 | then [ repoFileLink cfg.GitHubRepoUrl relPath image title ] 56 | else [] 57 | 58 | footer [Class "footer font-small m-0 py-4 bg-dark"] [ 59 | div [Class "container"] [ 60 | div [Class "row"] [ 61 | linkColumn "Project Resources" [ 62 | repoFileLink "README.md" (Solid "book-reader") "README" 63 | repoFileLink "CHANGELOG.md" (Solid "sticky-note") "Release Notes / Changelog" 64 | repoFileLink "LICENSE.md" (Solid "id-card") "License" 65 | repoFileLink "CONTRIBUTING.md" (Solid "directions") "Contributing" 66 | repoFileLink "CODE_OF_CONDUCT.md" (Solid "users") "Code of Conduct" 67 | ] 68 | linkColumn "Other Links" [ 69 | [footerLink "https://docs.microsoft.com/en-us/dotnet/fsharp/" (Brand "microsoft") "F# Documentation"] 70 | [footerLink "https://fsharp.org/guides/slack/" (Brand "slack") "F# Slack"] 71 | [a [Href "http://foundation.fsharp.org/"; Class "text-white"] [ 72 | img [Class "fsharp-footer-logo mr-2"; Src "https://fsharp.org/img/logo/fsharp.svg"; Alt "FSharp Logo"] 73 | str "F# Software Foundation" 74 | ]] 75 | ] 76 | linkColumn "Metadata" [ 77 | [str "Generated for version " 78 | a [Class "text-white"; Href (cfg.GitHubRepoUrl |> Uri.simpleCombine (sprintf "releases/tag/%s" cfg.ReleaseVersion))] [str cfg.ReleaseVersion] 79 | str (sprintf " on %s" (cfg.ReleaseDate.ToString("yyyy/MM/dd")))] 80 | match pageSource with 81 | | Some p -> 82 | let page = cfg.GitHubRepoUrl |> Uri.simpleCombine "edit/master" |> Uri |> Uri.simpleCombine p 83 | [ str "Found an issue? " 84 | a [Class "text-white"; Href (page |> string)] [ str "Edit this page." ] ] 85 | | None -> 86 | () 87 | ] 88 | ] 89 | div [Class "row"] [ 90 | div [Class "col text-center"] [ 91 | small [Class "text-light"] [ 92 | i [Class "fas fa-copyright mr-1"] [] 93 | str (sprintf "%s TypeSafeInternals, All rights reserved" (DateTimeOffset.UtcNow.ToString("yyyy"))) 94 | ] 95 | ] 96 | ] 97 | ] 98 | ] 99 | 100 | let masterTemplate (cfg : MasterTemplateConfig) navBar titletext bodyText pageSource = 101 | html [Lang "en"] [ 102 | head [] [ 103 | title [] [ str (sprintf "%s docs / %s" cfg.ProjectName titletext) ] 104 | meta [Name "viewport"; HTMLAttr.Content "width=device-width, initial-scale=1" ] 105 | link [ 106 | Href (cfg.SiteBaseUrl |> Uri.simpleCombine (sprintf "/content/toggle-bootstrap.min.css?version=%i" cfg.ReleaseDate.Ticks) ) 107 | Type "text/css" 108 | Rel "stylesheet" 109 | ] 110 | link [ 111 | Href (cfg.SiteBaseUrl |> Uri.simpleCombine (sprintf "/content/toggle-bootstrap-dark.min.css?version=%i" cfg.ReleaseDate.Ticks) ) 112 | Type "text/css" 113 | Rel "stylesheet" 114 | ] 115 | link [ 116 | Href "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" 117 | Rel "stylesheet" 118 | Integrity "sha384-KA6wR/X5RY4zFAHpv/CnoG2UW1uogYfdnP67Uv7eULvTveboZJg0qUpmJZb5VqzN" 119 | CrossOrigin "anonymous" 120 | ] 121 | link [ 122 | Href (cfg.SiteBaseUrl |> Uri.simpleCombine (sprintf "/content/style.css?version=%i" cfg.ReleaseDate.Ticks) ) 123 | Type "text/css" 124 | Rel "stylesheet" 125 | ] 126 | 127 | ] 128 | body [] [ 129 | yield navBar 130 | yield div [Class "wrapper d-flex flex-column justify-content-between min-vh-100"] [ 131 | main [Class "container main mb-4"] bodyText 132 | renderFooter cfg pageSource 133 | ] 134 | yield script [Src (cfg.SiteBaseUrl |> Uri.simpleCombine (sprintf "/content/themes.js?version=%i" cfg.ReleaseDate.Ticks)) ] [] 135 | yield script [ 136 | Src "https://code.jquery.com/jquery-3.4.1.slim.min.js" 137 | Integrity "sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" 138 | CrossOrigin "anonymous" 139 | ] [] 140 | yield script [ 141 | Src "https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" 142 | Integrity "sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" 143 | CrossOrigin "anonymous" 144 | ] [] 145 | yield script [ 146 | Src "https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" 147 | Integrity "sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" 148 | CrossOrigin "anonymous" 149 | ] [] 150 | yield script [Src (cfg.SiteBaseUrl |> Uri.simpleCombine (sprintf "/content/tips.js?version=%i" cfg.ReleaseDate.Ticks)) ] [] 151 | if cfg.IsWatchMode then 152 | yield script [Src (cfg.SiteBaseUrl |> Uri.simpleCombine (sprintf "/content/hotload.js?version=%i" cfg.ReleaseDate.Ticks)) ] [] 153 | yield script [Src (cfg.SiteBaseUrl |> Uri.simpleCombine (sprintf "/content/submenu.js?version=%i" cfg.ReleaseDate.Ticks)) ] [] 154 | yield script [Src (cfg.SiteBaseUrl |> Uri.simpleCombine (sprintf "/content/cleanups.js?version=%i" cfg.ReleaseDate.Ticks)) ] [] 155 | ] 156 | ] 157 | -------------------------------------------------------------------------------- /docsTool/templates/modules.fs: -------------------------------------------------------------------------------- 1 | module Modules 2 | open System 3 | open Fable.React 4 | open Fable.React.Props 5 | open FSharp.MetadataFormat 6 | open PartNested 7 | open PartMembers 8 | open Helpers 9 | 10 | 11 | let generateModuleDocs (moduleInfo : ModuleInfo) (props) = 12 | let members = moduleInfo.Module.AllMembers 13 | let comment = moduleInfo.Module.Comment 14 | 15 | let byCategory = 16 | members 17 | |> List.groupBy(fun m -> m.Category) 18 | |> List.sortBy(fun (g,v) -> if String.IsNullOrEmpty g then "ZZZ" else g) 19 | |> List.mapi(fun i (key, value) -> { 20 | Index = i 21 | GroupKey = key 22 | Members = value |> List.sortBy(fun m -> m.Name) 23 | Name = if String.IsNullOrEmpty key then "Other module members" else key 24 | }) 25 | let nestModules = moduleInfo.Module.NestedModules 26 | let nestTypes = moduleInfo.Module.NestedTypes 27 | [ 28 | yield div [ Class "container-fluid py-3" ] [ 29 | yield div [ Class "row" ] [ 30 | yield div [ Class "col-12" ] [ 31 | yield h1 [] [ 32 | str moduleInfo.Module.Name 33 | ] 34 | yield! renderObsoleteMessage moduleInfo.Module 35 | yield! renderNamespace moduleInfo.Namespace 36 | yield dl [] [ 37 | if moduleInfo.ParentModule.IsSome then 38 | yield dt [] [ 39 | str "Parent Module" 40 | ] 41 | yield dd [] [ 42 | a [ 43 | Href (sprintf "%s.html" moduleInfo.ParentModule.Value.UrlName) 44 | ] [ 45 | str moduleInfo.ParentModule.Value.Name 46 | ] 47 | ] 48 | if moduleInfo.Module.Attributes |> Seq.isEmpty |> not then 49 | yield dt [] [ 50 | str "Attributes" 51 | ] 52 | yield dd [] [ 53 | for attr in moduleInfo.Module.Attributes do 54 | yield str (attr.Format()) 55 | yield br [] 56 | ] 57 | ] 58 | 59 | yield div [ 60 | Class "xmldoc" 61 | ] [ 62 | for sec in comment.Sections do 63 | if byCategory |> Seq.exists (fun g -> g.GroupKey = sec.Key) |> not then 64 | if sec.Key <> "" then 65 | yield h2 [] [ 66 | RawText sec.Key 67 | ] 68 | yield RawText sec.Value 69 | ] 70 | 71 | 72 | if byCategory |> Seq.length > 1 then 73 | yield h2 [] [ 74 | str "Table of contents" 75 | ] 76 | 77 | yield ul [] [ 78 | for g in byCategory do 79 | yield li [] [ 80 | a [ 81 | Href (g.Index.ToString() |> sprintf "#section%s") 82 | ] [ 83 | str g.Name 84 | ] 85 | ] 86 | ] 87 | 88 | if (nestTypes |> Seq.length) + (nestModules |> Seq.length) > 0 then 89 | yield h2 [] [ 90 | str "Nested types and modules" 91 | ] 92 | 93 | yield! (partNested (nestTypes |> Seq.toArray) (nestModules |> Seq.toArray)) 94 | 95 | for g in byCategory do 96 | if byCategory |> Seq.length > 1 then 97 | yield h2 [] [ 98 | str g.Name 99 | a [ 100 | Name (sprintf "section%d" g.Index) 101 | ] [ 102 | str " " 103 | ] 104 | ] 105 | 106 | let info = comment.Sections |> Seq.tryFind(fun kvp -> kvp.Key = g.GroupKey) 107 | 108 | match info with 109 | | Some info -> 110 | yield div [ 111 | Class "xmldoc" 112 | ] [ 113 | str info.Value 114 | ] 115 | | None -> 116 | yield nothing 117 | 118 | yield! partMembers "Functions and values" "Function or value" (g.Members |> Seq.filter(fun m -> m.Kind = MemberKind.ValueOrFunction)) 119 | 120 | yield! partMembers "Type extensions" "Type extension" (g.Members |> Seq.filter(fun m -> m.Kind = MemberKind.TypeExtension)) 121 | 122 | yield! partMembers "Active patterns" "Active pattern" (g.Members |> Seq.filter(fun m -> m.Kind = MemberKind.ActivePattern)) 123 | ] 124 | ] 125 | ] 126 | ] 127 | -------------------------------------------------------------------------------- /docsTool/templates/namespaces.fs: -------------------------------------------------------------------------------- 1 | module Namespaces 2 | 3 | open System 4 | open Fable.React 5 | open Fable.React.Props 6 | open FSharp.MetadataFormat 7 | 8 | 9 | type ByCategory = { 10 | Name : string 11 | Index : string 12 | Types : Type array 13 | Modules : Module array 14 | } 15 | 16 | let generateNamespaceDocs (asm : AssemblyGroup) (props) = 17 | let parts = 18 | asm.Namespaces 19 | |> Seq.mapi(fun nsi ns -> 20 | let allByCategories = 21 | ns.Types 22 | |> Seq.map(fun t -> t.Category) 23 | |> Seq.append (ns.Modules |> Seq.map(fun m -> m.Category)) 24 | |> Seq.distinct 25 | |> Seq.sortBy(fun s -> 26 | if String.IsNullOrEmpty(s) then "ZZZ" 27 | else s) 28 | |> Seq.mapi(fun ci c -> 29 | { 30 | Name = if String.IsNullOrEmpty(c) then "Other namespace members" else c 31 | Index = sprintf "%d_%d" nsi ci 32 | Types = ns.Types |> Seq.filter(fun t -> t.Category = c) |> Seq.toArray 33 | Modules = ns.Modules |> Seq.filter(fun m -> m.Category = c) |> Seq.toArray 34 | }) 35 | |> Seq.filter(fun c -> c.Types.Length + c.Modules.Length > 0) 36 | |> Seq.toArray 37 | [ 38 | yield h2 [] [ 39 | Helpers.createAnchor ns.Name ns.Name 40 | ] 41 | if allByCategories.Length > 1 then 42 | yield ul [] [ 43 | for c in allByCategories do 44 | yield 45 | li [] [ 46 | a [Href (sprintf "#section%s" c.Index)] [ 47 | str c.Name 48 | ] 49 | ] 50 | ] 51 | 52 | 53 | for c in allByCategories do 54 | if allByCategories.Length > 1 then 55 | yield h3 [] [ 56 | a [Class "anchor"; Name (sprintf "section%s" c.Index); Href (sprintf "#section%s" c.Index)] [ 57 | str c.Name 58 | ] 59 | ] 60 | yield! PartNested.partNested c.Types c.Modules 61 | ] 62 | ) 63 | |> Seq.collect id 64 | div [ Class "container-fluid py-3" ] [ 65 | div [ Class "row" ] [ 66 | div [ Class "col-12" ] [ 67 | yield h1 [] [ 68 | Helpers.createAnchor asm.Name asm.Name 69 | ] 70 | yield! parts 71 | ] 72 | ] 73 | ] 74 | -------------------------------------------------------------------------------- /docsTool/templates/nav.fs: -------------------------------------------------------------------------------- 1 | module Nav 2 | 3 | open System 4 | open DocsTool 5 | open Fable.React 6 | open Fable.React.Props 7 | 8 | type NameOfArticle = string 9 | type UrlPath = string 10 | 11 | type TopLevelNav = { 12 | DocsRoot : IO.DirectoryInfo 13 | DocsPages : IO.FileInfo list 14 | } 15 | 16 | type NavConfig = { 17 | SiteBaseUrl : Uri 18 | GitHubRepoUrl : Uri 19 | ProjectName : string 20 | TopLevelNav : TopLevelNav 21 | } 22 | 23 | let normalizeText text = 24 | System.Text.RegularExpressions.Regex.Replace(text, @"[^0-9a-zA-Z\.]+", " ") 25 | 26 | let normalizeStr = normalizeText >> str 27 | 28 | let navItem link inner = 29 | li [ 30 | Class "nav-item" 31 | ] [ 32 | a [ 33 | Class "nav-link" 34 | Href link 35 | ] inner 36 | ] 37 | 38 | let navItemText text link = 39 | navItem link [ normalizeStr text ] 40 | 41 | let navItemIconOnly link ariaLabel inner = 42 | li [Class "nav-item"] [ 43 | a [ 44 | Class "nav-link" 45 | HTMLAttr.Custom("aria-label", ariaLabel) 46 | Href link 47 | ] inner 48 | ] 49 | 50 | let dropDownNavMenu text items = 51 | li [ Class "nav-item dropdown" ][ 52 | a [ 53 | Id (sprintf "navbarDropdown-%s" text) 54 | Href "#" 55 | DataToggle "dropdown" 56 | AriaHasPopup true 57 | AriaExpanded false 58 | Class "nav-link dropdown-toggle" ] 59 | [ normalizeStr text ] 60 | ul [ HTMLAttr.Custom ("aria-labelledby", "dropdownMenu1") 61 | Class "dropdown-menu border-0 shadow" ] items ] 62 | 63 | let dropDownNavItem text link = 64 | li [ 65 | Class "nav-item" 66 | ] [ 67 | a [ 68 | Class "dropdown-item" 69 | Href link 70 | ] [ 71 | normalizeStr text 72 | ] 73 | ] 74 | let dropdownSubMenu text items = 75 | li [ Class "dropdown-submenu" ] [ 76 | a [ Id (sprintf "navbarDropdown-%s" text) 77 | Href "#" 78 | Role "button" 79 | DataToggle "dropdown" 80 | AriaHasPopup true 81 | AriaExpanded false 82 | Class "dropdown-item dropdown-toggle" ] [ 83 | normalizeStr text ] 84 | ul [ 85 | HTMLAttr.Custom ("aria-labelledby", "dropdownMenu2") 86 | Class "dropdown-menu border-0 shadow" ] items 87 | ] 88 | 89 | type NavTree = 90 | | File of title:string * link:string 91 | | Folder of title: string * NavTree list 92 | 93 | let rec sortNavTree (navtree : NavTree list) = 94 | navtree 95 | |> List.map(fun navTree -> 96 | match navTree with 97 | | File (t,l) -> File (t,l) 98 | | Folder(title, nodes) -> Folder(title, sortNavTree nodes) 99 | ) 100 | |> List.sortBy(fun navtree -> 101 | match navtree with 102 | | File(title,_) -> title 103 | | Folder(title, _) -> title 104 | ) 105 | 106 | let navTreeFromPaths (rootPath : IO.DirectoryInfo) (files : IO.FileInfo list) = 107 | let rec addPath subFilePath parts nodes = 108 | match parts with 109 | | [] -> nodes 110 | | hp :: tp -> 111 | addHeadPath subFilePath hp tp nodes 112 | and addHeadPath subFilePath (part : string) remainingParts (nodes : NavTree list)= 113 | match nodes with 114 | | [] -> 115 | if part.EndsWith("html") then 116 | File(IO.Path.GetFileNameWithoutExtension part, subFilePath) 117 | else 118 | Folder(part, addPath subFilePath remainingParts []) 119 | |> List.singleton 120 | | Folder(title, subnodes) :: nodes when title = part -> Folder(title, addPath subFilePath remainingParts subnodes ) :: nodes 121 | | hn :: tn -> hn :: addHeadPath subFilePath part remainingParts tn 122 | 123 | ([], files) 124 | ||> List.fold(fun state file -> 125 | let subFilePath = file.FullName.Replace(rootPath.FullName, "") 126 | let pathParts = subFilePath.Split(IO.Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries) |> Array.toList 127 | addPath subFilePath pathParts state 128 | ) 129 | 130 | 131 | 132 | let generateNavMenus siteBaseUrl (navTree : NavTree list) = 133 | let rec innerDo depth (navTree : NavTree list) = 134 | navTree 135 | |> List.map(fun nav -> 136 | match nav with 137 | | File (title, link) when depth = 0 -> navItemText title (siteBaseUrl |> Uri.simpleCombine link) 138 | | File (title, link) -> dropDownNavItem title (siteBaseUrl |> Uri.simpleCombine link) 139 | | Folder (title, subtree) when depth = 0 -> 140 | innerDo (depth + 1) subtree 141 | |> dropDownNavMenu title 142 | | Folder (title, subtree) -> 143 | innerDo (depth + 1) subtree 144 | |> dropdownSubMenu title 145 | ) 146 | innerDo 0 navTree 147 | 148 | 149 | 150 | let generateNav (navCfg : NavConfig) = 151 | nav [ 152 | Class "navbar navbar-expand-md sticky-top navbar-dark bg-dark" 153 | ] [ 154 | a [ 155 | Class "navbar-brand" 156 | Href (navCfg.SiteBaseUrl |> Uri.simpleCombine "/index.html") 157 | ] [ 158 | i [ Class "fa fa-car text-white mr-2"] [] 159 | str (navCfg.ProjectName) 160 | ] 161 | button [ 162 | Class "navbar-toggler" 163 | Type "button" 164 | DataToggle "collapse" 165 | HTMLAttr.Custom("data-target","#navbarNav" ) 166 | HTMLAttr.Custom("aria-controls","navbarNav" ) 167 | HTMLAttr.Custom("aria-expanded","false" ) 168 | HTMLAttr.Custom("aria-label","Toggle navigation" ) 169 | ] [ 170 | span [Class "navbar-toggler-icon"] [] 171 | ] 172 | div [ Class "collapse navbar-collapse" 173 | Id "navbarNav" ] [ 174 | ul [ Class "navbar-nav mr-auto" ] [ 175 | yield! navTreeFromPaths navCfg.TopLevelNav.DocsRoot navCfg.TopLevelNav.DocsPages |> sortNavTree |> generateNavMenus navCfg.SiteBaseUrl 176 | ] 177 | ul [ Class "navbar-nav"] [ 178 | button [Id "theme-toggle"; Class ""] [ 179 | str "" 180 | ] 181 | navItemIconOnly (string navCfg.GitHubRepoUrl) (sprintf "%s Repository on Github" navCfg.ProjectName) [ 182 | i [ Class "fab fa-github fa-lg fa-fw text-light"] [] 183 | ] 184 | ] 185 | ] 186 | ] 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /docsTool/templates/partMembers.fs: -------------------------------------------------------------------------------- 1 | module PartMembers 2 | 3 | open System 4 | open Fable.React 5 | open Fable.React.Props 6 | open FSharp.MetadataFormat 7 | open System.Collections.Generic 8 | open Helpers 9 | 10 | type ModuleByCategory = { 11 | Index : int 12 | GroupKey : string 13 | Members : list 14 | Name : string 15 | } 16 | 17 | 18 | let signature (m : Member) = seq { 19 | if m.Details.Signature |> String.IsNullOrEmpty |> not then 20 | yield 21 | code [ Class "function-or-value"] [ 22 | str m.Details.Signature 23 | ] 24 | } 25 | 26 | let repoSourceLink (m: Member) = seq { 27 | if m.Details.FormatSourceLocation |> String.IsNullOrEmpty |> not then 28 | yield a [ 29 | Href m.Details.FormatSourceLocation 30 | Class "float-right" 31 | HTMLAttr.Custom("aria-label", "View source on GitHub") 32 | ] [ 33 | yield i [ 34 | Class "fab fa-github text-dark" 35 | ] [] 36 | ] 37 | } 38 | 39 | let replaceh2withh5 (content : string) = 40 | content.Replace("

", "

") 41 | 42 | 43 | let normalize (content : string) = 44 | content 45 | |> replaceh2withh5 46 | 47 | 48 | 49 | let commentBlock (c: Comment) = 50 | let (|EmptyDefaultBlock|NonEmptyDefaultBlock|Section|) (KeyValue(section, content)) = 51 | match section, content with 52 | | "", c when String.IsNullOrEmpty c -> EmptyDefaultBlock 53 | | "", c -> NonEmptyDefaultBlock c 54 | | section, content -> Section (section, content) 55 | 56 | let renderSection (s : KeyValuePair): Fable.React.ReactElement list = 57 | match s with 58 | | EmptyDefaultBlock -> [] 59 | | NonEmptyDefaultBlock content -> [ div [ Class "comment-block" ] [ RawText (normalize content) ] ] 60 | | Section(name, content) -> [ h5 [] [ str name ] // h2 is obnoxiously large for this context, go with the smaller h5 61 | RawText (normalize content) ] 62 | c.Sections 63 | |> List.collect renderSection 64 | 65 | let compiledName (m: Member) = seq { 66 | if m.Details.FormatCompiledName |> String.IsNullOrEmpty |> not then 67 | yield p [] [ 68 | strong [] [ str "CompiledName:" ] 69 | code [] [ str m.Details.FormatCompiledName ] 70 | ] 71 | } 72 | 73 | let partMembers (header : string) (tableHeader : string) (members : #seq) = [ 74 | if members |> Seq.length > 0 then 75 | yield h3 [] [ 76 | str header 77 | ] 78 | 79 | yield table [ 80 | Class "table" 81 | ] [ 82 | thead [] [ 83 | 84 | tr [] [ 85 | th [Class "fit"] [ 86 | 87 | ] 88 | th [] [ 89 | str tableHeader 90 | ] 91 | 92 | th [] [ 93 | str "Signature" 94 | ] 95 | 96 | th [] [ 97 | str "Description" 98 | ] 99 | ] 100 | ] 101 | tbody [] [ 102 | for it in members do 103 | let id = Guid.NewGuid().ToString() 104 | yield tr [] [ 105 | td [] [ 106 | Helpers.createAnchorIcon (it.Details.FormatUsage(40)) 107 | ] 108 | td [ 109 | Class "member-name" 110 | ] [ 111 | code [ 112 | Class "function-or-value" 113 | HTMLAttr.Custom("data-guid", id) 114 | ] [ 115 | str (it.Details.FormatUsage(40)) 116 | ] 117 | ] 118 | td [ 119 | Class "member-name" 120 | ] [ 121 | yield! signature it 122 | ] 123 | 124 | td [ 125 | Class "xmldoc" 126 | ] [ 127 | yield! renderObsoleteMessage it 128 | yield! repoSourceLink it 129 | yield! commentBlock it.Comment 130 | yield! compiledName it 131 | ] 132 | ] 133 | ] 134 | ] 135 | ] 136 | -------------------------------------------------------------------------------- /docsTool/templates/partNested.fs: -------------------------------------------------------------------------------- 1 | module PartNested 2 | 3 | open System 4 | open Fable.React 5 | open Fable.React.Props 6 | open FSharp.MetadataFormat 7 | open Helpers 8 | 9 | let partNested (types : Type array) (modules : Module array) = 10 | [ 11 | if types.Length > 0 then 12 | yield table [ Class "table" ] [ 13 | thead [] [ 14 | tr [] [ 15 | th [Class "fit"] [ 16 | 17 | ] 18 | th [] [ 19 | str "Type" 20 | ] 21 | th [] [ 22 | str "Description" 23 | ] 24 | ] 25 | ] 26 | tbody [] [ 27 | for t in types do 28 | yield tr [] [ 29 | td [] [ 30 | Helpers.createAnchorIcon t.Name 31 | ] 32 | td [Class "type-name"] [ 33 | a [Href (sprintf "%s.html" t.UrlName)] [ 34 | str t.Name 35 | ] 36 | ] 37 | td [Class "xmldoc"] [ 38 | yield! renderObsoleteMessage t 39 | yield RawText t.Comment.Blurb 40 | ] 41 | ] 42 | ] 43 | ] 44 | if modules.Length > 0 then 45 | yield table [ Class "table" ] [ 46 | thead [] [ 47 | tr [] [ 48 | th [Class "fit"] [ 49 | 50 | ] 51 | th [] [ 52 | str "Module" 53 | ] 54 | th [] [ 55 | str "Description" 56 | ] 57 | ] 58 | ] 59 | tbody [] [ 60 | for t in modules do 61 | yield tr [] [ 62 | td [] [ 63 | Helpers.createAnchorIcon t.Name 64 | ] 65 | td [Class "Modules-name"] [ 66 | a [Href (sprintf "%s.html" t.UrlName)] [ 67 | str t.Name 68 | ] 69 | ] 70 | td [Class "xmldoc"] [ 71 | yield! renderObsoleteMessage t 72 | yield RawText t.Comment.Blurb 73 | ] 74 | ] 75 | ] 76 | ] 77 | ] 78 | -------------------------------------------------------------------------------- /docsTool/templates/types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | open System 4 | open Fable.React 5 | open Fable.React.Props 6 | open FSharp.MetadataFormat 7 | open PartMembers 8 | open Helpers 9 | 10 | let generateTypeDocs (model : TypeInfo) (props) = 11 | let members = model.Type.AllMembers 12 | let comment = model.Type.Comment 13 | let ``type`` = model.Type 14 | let byCategory = 15 | members 16 | |> List.groupBy (fun m -> m.Category) 17 | |> List.sortBy (fun (k,v) -> if String.IsNullOrEmpty(k) then "ZZZ" else k ) 18 | |> List.mapi (fun i (k,v) -> { 19 | Index = i 20 | GroupKey = k 21 | Members = v |> List.sortBy (fun m -> if m.Kind = MemberKind.StaticParameter then "" else m.Name) 22 | Name = if String.IsNullOrEmpty(k) then "Other type members" else k 23 | }) 24 | [ 25 | yield h1 [] [ 26 | str model.Type.Name 27 | ] 28 | 29 | yield p [] [ 30 | yield! renderObsoleteMessage model.Type 31 | yield! renderNamespace model.Namespace 32 | if model.HasParentModule then 33 | yield br [] 34 | yield span [] [ 35 | str "Parent Module: " 36 | 37 | a [ 38 | Href (sprintf "%s.html" model.ParentModule.Value.UrlName) 39 | ] [ 40 | str model.ParentModule.Value.Name 41 | ] 42 | ] 43 | 44 | 45 | if ``type``.Attributes |> Seq.isEmpty |> not then 46 | yield br [] 47 | yield span [] [ 48 | yield str "Attributes: " 49 | 50 | yield br [] 51 | 52 | for attr in ``type``.Attributes do 53 | yield str (attr.Format()) 54 | yield br [] 55 | ] 56 | ] 57 | 58 | yield div [ 59 | Class "xmldoc" 60 | ] [ 61 | for sec in comment.Sections do 62 | if byCategory |> Seq.exists (fun m -> m.GroupKey = sec.Key) |> not then 63 | if sec.Key <> "" then 64 | yield h2 [] [ 65 | str sec.Key 66 | ] 67 | yield RawText sec.Value 68 | ] 69 | 70 | if byCategory |> Seq.length > 1 then 71 | yield h2 [] [ 72 | str "Table of contents" 73 | ] 74 | 75 | yield ul [] [ 76 | for g in byCategory do 77 | yield li [] [ 78 | a [ 79 | Href (sprintf "#section%d" g.Index) 80 | ] [ 81 | str g.Name 82 | ] 83 | ] 84 | ] 85 | 86 | for g in byCategory do 87 | if byCategory |> Seq.length > 1 then 88 | yield h2 [] [ 89 | str g.Name 90 | 91 | a [ 92 | Name (sprintf "section%d" g.Index) 93 | ] [ 94 | str " " 95 | ] 96 | ] 97 | 98 | match comment.Sections |> Seq.tryFind (fun kvp -> kvp.Key = g.GroupKey) with 99 | | Some info -> 100 | yield div [ 101 | Class "xmldoc" 102 | ] [ 103 | str info.Value 104 | ] 105 | | None -> yield nothing 106 | 107 | yield! partMembers "Union Cases" "Union Case" (g.Members |> Seq.filter(fun m -> m.Kind = MemberKind.UnionCase)) 108 | yield! partMembers "Record Fields" "Record Field" (g.Members |> Seq.filter(fun m -> m.Kind = MemberKind.RecordField)) 109 | yield! partMembers "Static parameters" "Static parameters" (g.Members |> Seq.filter(fun m -> m.Kind = MemberKind.StaticParameter)) 110 | yield! partMembers "Contructors" "Constructor" (g.Members |> Seq.filter(fun m -> m.Kind = MemberKind.Constructor)) 111 | yield! partMembers "Instance members" "Instance member" (g.Members |> Seq.filter(fun m -> m.Kind = MemberKind.InstanceMember)) 112 | yield! partMembers "Static members" "Static member" (g.Members |> Seq.filter(fun m -> m.Kind = MemberKind.StaticMember)) 113 | ] 114 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.401", 4 | "rollForward": "latestMinor" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | source https://api.nuget.org/v3/index.json 3 | storage: none 4 | nuget FSharp.Core 5.0 5 | nuget Microsoft.SourceLink.GitHub 1.0.0 copy_local: true 6 | nuget Expecto 9.0.2 7 | nuget YoloDev.Expecto.TestSdk 0.9.2 8 | nuget Microsoft.NET.Test.Sdk 16.8.0 9 | nuget altcover ~> 7 10 | nuget Myriad.Core 0.7.4 11 | nuget Myriad.Sdk 0.7.4 12 | nuget Npgsql.Fsharp 3.10.0 13 | nuget NuGet.ProjectModel 5.9.1 14 | 15 | // [ FAKE GROUP ] 16 | group Build 17 | storage: none 18 | source https://www.nuget.org/api/v2 19 | source https://api.nuget.org/v3/index.json 20 | nuget FSharp.Core 21 | nuget Fake.Core.Target 5.22.0 22 | nuget Fake.DotNet.Cli 5.22.0 23 | nuget Fake.Core.ReleaseNotes 5.22.0 24 | nuget Fake.DotNet.AssemblyInfoFile 5.22.0 25 | nuget Fake.DotNet.Paket 5.22.0 26 | nuget Fake.DotNet.Testing.Expecto 5.22.0 27 | nuget Fake.Tools.Git 5.22.0 28 | nuget Fake.JavaScript.Npm 5.22.0 29 | nuget Fake.Api.GitHub 5.22.0 30 | nuget Fake.BuildServer.GitHubActions 5.22.0 31 | nuget MSBuild.StructuredLogger 32 | nuget Octokit 33 | nuget Argu 34 | 35 | group Docs 36 | storage: none 37 | source https://www.nuget.org/api/v2 38 | source https://api.nuget.org/v3/index.json 39 | nuget Argu 40 | nuget FSharp.Core 41 | nuget Fake.IO.FileSystem 5.20.4-alpha.1642 42 | nuget FAKE.Core.Environment 5.20.4-alpha.1642 43 | nuget Fake.DotNet.Cli 5.20.4-alpha.1642 44 | nuget FSharp.Formatting 4.0.0-rc1 45 | nuget FSharp.Literate 4.0.0-rc1 46 | nuget Fable.React 47 | 48 | group Analyzers 49 | source https://www.nuget.org/api/v2 50 | source https://api.nuget.org/v3/index.json 51 | nuget BinaryDefense.FSharp.Analyzers.Hashing 0.2.1 52 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | true 6 | false 7 | 8 | true 9 | true 10 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TheAngryByrd.Myriad.Plugins.TypeSafeInternals/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "TheAngryByrd.Myriad.Plugins.TypeSafeInternals" 17 | let [] AssemblyProduct = "TheAngryByrd.TypeSafeInternals" 18 | let [] AssemblyVersion = "0.1.1" 19 | let [] AssemblyMetadata_ReleaseDate = "2022-02-04T00:00:00.0000000-05:00" 20 | let [] AssemblyFileVersion = "0.1.1" 21 | let [] AssemblyInformationalVersion = "0.1.1" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "64d300a111804f7e04d4103f2e9bcfe72136c83a" 24 | -------------------------------------------------------------------------------- /src/TheAngryByrd.Myriad.Plugins.TypeSafeInternals/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | true 6 | 7 | 8 | TheAngryByrd.Myriad.Plugins.TypeSafeInternals uses Myriad to generate type safe reflection calls to internal functions/properties/methods 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | true 17 | %(Identity) 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/TheAngryByrd.Myriad.Plugins.TypeSafeInternals/build/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.InTest.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/TheAngryByrd.Myriad.Plugins.TypeSafeInternals/build/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/TheAngryByrd.Myriad.Plugins.TypeSafeInternals/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | Microsoft.SourceLink.GitHub 3 | Myriad.Core 4 | Myriad.Sdk 5 | NuGet.ProjectModel 6 | -------------------------------------------------------------------------------- /src/TheAngryByrd.TypeSafeInternals/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "TheAngryByrd.TypeSafeInternals" 17 | let [] AssemblyProduct = "TheAngryByrd.TypeSafeInternals" 18 | let [] AssemblyVersion = "0.1.1" 19 | let [] AssemblyMetadata_ReleaseDate = "2022-02-04T00:00:00.0000000-05:00" 20 | let [] AssemblyFileVersion = "0.1.1" 21 | let [] AssemblyInformationalVersion = "0.1.1" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "64d300a111804f7e04d4103f2e9bcfe72136c83a" 24 | -------------------------------------------------------------------------------- /src/TheAngryByrd.TypeSafeInternals/Library.fs: -------------------------------------------------------------------------------- 1 | namespace TheAngryByrd.TypeSafeInternals 2 | 3 | open System 4 | open System.Reflection 5 | 6 | module Delegate = 7 | let public BindingFlagsToSeeAll: BindingFlags = 8 | BindingFlags.Static 9 | ||| BindingFlags.FlattenHierarchy 10 | ||| BindingFlags.Instance 11 | ||| BindingFlags.NonPublic 12 | ||| BindingFlags.Public 13 | 14 | 15 | let createStaticArity0<'returnType> (ty: Type) name = 16 | ty 17 | .GetMethod(name, BindingFlagsToSeeAll) 18 | .CreateDelegate(typeof>) 19 | |> unbox> 20 | |> FuncConvert.FromFunc 21 | 22 | let createStaticArity1<'input1, 'returnType> (ty: Type) name = 23 | ty 24 | .GetMethod(name, BindingFlagsToSeeAll) 25 | .CreateDelegate(typeof>) 26 | |> unbox> 27 | |> FuncConvert.FromFunc 28 | 29 | let createStaticArity2<'input1, 'input2, 'returnType> (ty: Type) name = 30 | ty 31 | .GetMethod(name, BindingFlagsToSeeAll) 32 | .CreateDelegate(typeof>) 33 | |> unbox> 34 | |> FuncConvert.FromFunc 35 | 36 | let createStaticArity3<'input1, 'input2, 'input3, 'returnType> (ty: Type) name = 37 | ty 38 | .GetMethod(name, BindingFlagsToSeeAll) 39 | .CreateDelegate(typeof>) 40 | |> unbox> 41 | |> FuncConvert.FromFunc 42 | 43 | let createStaticArity4<'input1, 'input2, 'input3, 'input4, 'returnType> (ty: Type) name = 44 | ty 45 | .GetMethod(name, BindingFlagsToSeeAll) 46 | .CreateDelegate(typeof>) 47 | |> unbox> 48 | |> FuncConvert.FromFunc 49 | 50 | let createStaticArity5<'input1, 'input2, 'input3, 'input4, 'input5, 'returnType> 51 | (ty: Type) 52 | name 53 | = 54 | ty 55 | .GetMethod(name, BindingFlagsToSeeAll) 56 | .CreateDelegate(typeof>) 57 | |> unbox> 58 | |> FuncConvert.FromFunc 59 | 60 | 61 | let createStaticArity0ReturningUnit (ty: Type) name = 62 | ty.GetMethod(name, BindingFlagsToSeeAll).CreateDelegate(typeof) 63 | |> unbox 64 | |> FuncConvert.FromAction 65 | 66 | let createStaticArity1ReturningUnit<'input1> (ty: Type) name = 67 | ty.GetMethod(name, BindingFlagsToSeeAll).CreateDelegate(typeof>) 68 | |> unbox> 69 | |> FuncConvert.FromAction 70 | 71 | let createStaticArity2ReturningUnit<'input1, 'input2> (ty: Type) name = 72 | ty 73 | .GetMethod(name, BindingFlagsToSeeAll) 74 | .CreateDelegate(typeof>) 75 | |> unbox> 76 | |> FuncConvert.FromAction 77 | 78 | let createStaticArity3ReturningUnit<'input1, 'input2, 'input3> (ty: Type) name = 79 | ty 80 | .GetMethod(name, BindingFlagsToSeeAll) 81 | .CreateDelegate(typeof>) 82 | |> unbox> 83 | |> FuncConvert.FromAction 84 | 85 | let createStaticArity4ReturningUnit<'input1, 'input2, 'input3, 'input4> (ty: Type) name = 86 | ty 87 | .GetMethod(name, BindingFlagsToSeeAll) 88 | .CreateDelegate(typeof>) 89 | |> unbox> 90 | |> FuncConvert.FromAction 91 | 92 | let createStaticArity5ReturningUnit<'input1, 'input2, 'input3, 'input4, 'input5> 93 | (ty: Type) 94 | name 95 | = 96 | ty 97 | .GetMethod(name, BindingFlagsToSeeAll) 98 | .CreateDelegate(typeof>) 99 | |> unbox> 100 | |> FuncConvert.FromAction 101 | 102 | 103 | /// 104 | /// This will generate a function based on a Getter for a Property 105 | /// 106 | /// The type where the method resides 107 | /// The name of the Property 108 | /// 109 | /// 110 | /// 111 | let createGetter<'instanceType, 'returnType> (ty: Type) name = 112 | ty 113 | .GetProperty(name, BindingFlagsToSeeAll) 114 | .GetGetMethod(true) 115 | .CreateDelegate(typeof>, null) 116 | |> unbox> 117 | |> FuncConvert.FromFunc 118 | -------------------------------------------------------------------------------- /src/TheAngryByrd.TypeSafeInternals/TheAngryByrd.TypeSafeInternals.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | TheAngryByrd.TypeSafeInternals 8 | TheAngryByrd.TypeSafeInternals uses Myriad to generate type safe reflection calls to internal functions/properties/methods 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/TheAngryByrd.TypeSafeInternals/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | Microsoft.SourceLink.GitHub -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests" 17 | let [] AssemblyProduct = "TheAngryByrd.TypeSafeInternals" 18 | let [] AssemblyVersion = "0.1.0" 19 | let [] AssemblyMetadata_ReleaseDate = "2021-05-24T00:00:00.0000000-04:00" 20 | let [] AssemblyFileVersion = "0.1.0" 21 | let [] AssemblyInformationalVersion = "0.1.0" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "b60a838c25b86d83ca8a536f02f5e33d2125efe7" 24 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests/Generated.fs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // This code was generated by myriad. 3 | // Changes to this file will be lost when the code is regenerated. 4 | //------------------------------------------------------------------------------ 5 | namespace TypeSafeInternals 6 | 7 | module Npgsql = 8 | module FSharp = 9 | module Sql = 10 | let private loadedAssembly = 11 | System.Reflection.Assembly.Load( 12 | "Npgsql.FSharp, Version=3.10.0.0, Culture=neutral, PublicKeyToken=null" 13 | ) 14 | 15 | let private loadedModule = 16 | loadedAssembly.GetTypes() 17 | |> Seq.find (fun (t: System.Type) -> t.FullName = "Npgsql.FSharp.SqlModule") 18 | 19 | let private p_defaultConStringCachedFunc: unit 20 | -> Npgsql.FSharp.Sql.ConnectionStringBuilder = 21 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity0 22 | loadedModule 23 | "defaultConString" 24 | 25 | let p_defaultConString () = p_defaultConStringCachedFunc () 26 | 27 | let private p_defaultPropsCachedFunc: unit -> Npgsql.FSharp.Sql.SqlProps = 28 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity0 29 | loadedModule 30 | "defaultProps" 31 | 32 | let p_defaultProps () = p_defaultPropsCachedFunc () 33 | 34 | let private p_newConnectionCachedFunc: Npgsql.FSharp.Sql.SqlProps 35 | -> Npgsql.NpgsqlConnection = 36 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity1 37 | loadedModule 38 | "newConnection" 39 | 40 | let p_newConnection props = p_newConnectionCachedFunc props 41 | 42 | let private p_getConnectionCachedFunc: Npgsql.FSharp.Sql.SqlProps 43 | -> Npgsql.NpgsqlConnection = 44 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity1 45 | loadedModule 46 | "getConnection" 47 | 48 | let p_getConnection props = p_getConnectionCachedFunc props 49 | 50 | let private p_populateRowCachedFunc: Npgsql.NpgsqlCommand 51 | -> list 52 | -> unit = 53 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity2ReturningUnit> 54 | loadedModule 55 | "populateRow" 56 | 57 | let p_populateRow cmd row = p_populateRowCachedFunc cmd row 58 | 59 | let private p_populateCmdCachedFunc: Npgsql.NpgsqlCommand 60 | -> Npgsql.FSharp.Sql.SqlProps 61 | -> unit = 62 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity2ReturningUnit 63 | loadedModule 64 | "populateCmd" 65 | 66 | let p_populateCmd cmd props = p_populateCmdCachedFunc cmd props 67 | 68 | module ConnectionStringBuilder = 69 | let private loadedType = typeof 70 | 71 | let get_Host: Npgsql.FSharp.Sql.ConnectionStringBuilder -> string = 72 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter 73 | loadedType 74 | "Host" 75 | 76 | let get_Database: Npgsql.FSharp.Sql.ConnectionStringBuilder -> string = 77 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter 78 | loadedType 79 | "Database" 80 | 81 | let get_Username: Npgsql.FSharp.Sql.ConnectionStringBuilder -> option = 82 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 83 | loadedType 84 | "Username" 85 | 86 | let get_Password: Npgsql.FSharp.Sql.ConnectionStringBuilder -> option = 87 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 88 | loadedType 89 | "Password" 90 | 91 | let get_Port: Npgsql.FSharp.Sql.ConnectionStringBuilder -> option = 92 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 93 | loadedType 94 | "Port" 95 | 96 | let get_Config: Npgsql.FSharp.Sql.ConnectionStringBuilder -> option = 97 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 98 | loadedType 99 | "Config" 100 | 101 | let get_SslMode: Npgsql.FSharp.Sql.ConnectionStringBuilder 102 | -> option = 103 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 104 | loadedType 105 | "SslMode" 106 | 107 | let get_TrustServerCertificate: Npgsql.FSharp.Sql.ConnectionStringBuilder 108 | -> option = 109 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 110 | loadedType 111 | "TrustServerCertificate" 112 | 113 | let get_ConvertInfinityDateTime: Npgsql.FSharp.Sql.ConnectionStringBuilder 114 | -> option = 115 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 116 | loadedType 117 | "ConvertInfinityDateTime" 118 | 119 | module SqlProps = 120 | let private loadedType = typeof 121 | 122 | let get_ConnectionString: Npgsql.FSharp.Sql.SqlProps -> string = 123 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter 124 | loadedType 125 | "ConnectionString" 126 | 127 | let get_SqlQuery: Npgsql.FSharp.Sql.SqlProps -> list = 128 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 129 | loadedType 130 | "SqlQuery" 131 | 132 | let get_Parameters: Npgsql.FSharp.Sql.SqlProps 133 | -> list = 134 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 135 | loadedType 136 | "Parameters" 137 | 138 | let get_IsFunction: Npgsql.FSharp.Sql.SqlProps -> System.Boolean = 139 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter 140 | loadedType 141 | "IsFunction" 142 | 143 | let get_NeedPrepare: Npgsql.FSharp.Sql.SqlProps -> System.Boolean = 144 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter 145 | loadedType 146 | "NeedPrepare" 147 | 148 | let get_CancellationToken: Npgsql.FSharp.Sql.SqlProps 149 | -> System.Threading.CancellationToken = 150 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter 151 | loadedType 152 | "CancellationToken" 153 | 154 | let get_ClientCertificate: Npgsql.FSharp.Sql.SqlProps 155 | -> option = 156 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 157 | loadedType 158 | "ClientCertificate" 159 | 160 | let get_ExistingConnection: Npgsql.FSharp.Sql.SqlProps 161 | -> option = 162 | TheAngryByrd.TypeSafeInternals.Delegate.createGetter> 163 | loadedType 164 | "ExistingConnection" 165 | 166 | module Utils = 167 | let private loadedAssembly = 168 | System.Reflection.Assembly.Load( 169 | "Npgsql.FSharp, Version=3.10.0.0, Culture=neutral, PublicKeyToken=null" 170 | ) 171 | 172 | let private loadedModule = 173 | loadedAssembly.GetTypes() 174 | |> Seq.find (fun (t: System.Type) -> t.FullName = "Npgsql.FSharp.Utils") 175 | 176 | let private p_sqlMapCachedFunc<'a> : option<'a> 177 | -> ('a -> Npgsql.FSharp.SqlValue) 178 | -> Npgsql.FSharp.SqlValue = 179 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity2, ('a -> Npgsql.FSharp.SqlValue), Npgsql.FSharp.SqlValue> 180 | loadedModule 181 | "sqlMap" 182 | 183 | let p_sqlMap option f = p_sqlMapCachedFunc option f 184 | 185 | module PostgresUri = 186 | let private loadedAssembly = 187 | System.Reflection.Assembly.Load( 188 | "Npgsql.FSharp, Version=3.10.0.0, Culture=neutral, PublicKeyToken=null" 189 | ) 190 | 191 | let private loadedModule = 192 | loadedAssembly.GetTypes() 193 | |> Seq.find (fun (t: System.Type) -> t.FullName = "PostgresUri") 194 | 195 | let private p_extractHostCachedFunc: System.Uri -> option = 196 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity1> 197 | loadedModule 198 | "extractHost" 199 | 200 | let p_extractHost uri = p_extractHostCachedFunc uri 201 | 202 | let private p_extractUserCachedFunc: System.Uri -> option = 203 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity1> 204 | loadedModule 205 | "extractUser" 206 | 207 | let p_extractUser uri = p_extractUserCachedFunc uri 208 | 209 | let private p_extractDatabaseCachedFunc: System.Uri -> option = 210 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity1> 211 | loadedModule 212 | "extractDatabase" 213 | 214 | let p_extractDatabase uri = p_extractDatabaseCachedFunc uri 215 | 216 | let private p_extractPortCachedFunc: System.Uri -> option = 217 | TheAngryByrd.TypeSafeInternals.Delegate.createStaticArity1> 218 | loadedModule 219 | "extractPort" 220 | 221 | let p_extractPort uri = p_extractPortCachedFunc uri 222 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | namespace TypeSafeInternals.Tests 2 | 3 | module ExpectoTemplate = 4 | 5 | open Expecto 6 | 7 | [] 8 | let main argv = 9 | Tests.runTestsInAssembly defaultConfig argv 10 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests/PrivateDlls.txt: -------------------------------------------------------------------------------- 1 | Npgsql.FSharp 2 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | namespace TypeSafeInternals.Tests 2 | 3 | open System 4 | open Expecto 5 | open TypeSafeInternals 6 | open TheAngryByrd.Myriad.Plugins.TypeSafeInternals 7 | 8 | 9 | module GenerationTests = 10 | [] 11 | let tests = 12 | testList "Generation tests" [ 13 | testCase "Can generate correct module paths to static functions" 14 | <| fun _ -> 15 | 16 | printfn "%A" 17 | <| Npgsql.FSharp.Sql.p_defaultProps () 18 | 19 | printfn "%A" 20 | <| Npgsql.FSharp.Sql.p_defaultConString () 21 | 22 | testCase "Can create getter for Visible Records with Internal properties" 23 | <| fun _ -> 24 | 25 | let props = Npgsql.FSharp.Sql.p_defaultProps () 26 | let ct = Npgsql.FSharp.Sql.SqlProps.get_CancellationToken props 27 | 28 | Expect.equal System.Threading.CancellationToken.None ct "" 29 | ] 30 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | PrivateDlls.txt 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests/myriad.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAngryByrd/TypeSafeInternals/b06969f88c675b1d2a41fb944cca4ca8e8d07577/tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests/myriad.toml -------------------------------------------------------------------------------- /tests/TheAngryByrd.Myriad.Plugins.TypeSafeInternals.Tests/paket.references: -------------------------------------------------------------------------------- 1 | Expecto 2 | FSharp.Core 3 | Microsoft.NET.Test.Sdk 4 | YoloDev.Expecto.TestSdk 5 | altcover 6 | Myriad.Core 7 | Myriad.Sdk 8 | Npgsql.Fsharp 9 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.TypeSafeInternals.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "TheAngryByrd.TypeSafeInternals.Tests" 17 | let [] AssemblyProduct = "TheAngryByrd.TypeSafeInternals" 18 | let [] AssemblyVersion = "0.1.0" 19 | let [] AssemblyMetadata_ReleaseDate = "2021-05-24T00:00:00.0000000-04:00" 20 | let [] AssemblyFileVersion = "0.1.0" 21 | let [] AssemblyInformationalVersion = "0.1.0" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "b60a838c25b86d83ca8a536f02f5e33d2125efe7" 24 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.TypeSafeInternals.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | namespace TypeSafeInternals.Tests 2 | 3 | module ExpectoTemplate = 4 | 5 | open Expecto 6 | 7 | [] 8 | let main argv = 9 | Tests.runTestsInAssembly defaultConfig argv 10 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.TypeSafeInternals.Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | namespace TypeSafeInternals.Tests 2 | 3 | open System 4 | open Expecto 5 | 6 | module SayTests = 7 | [] 8 | let tests = 9 | testList "samples" [ 10 | testCase "Add two integers" 11 | <| fun _ -> () 12 | ] 13 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.TypeSafeInternals.Tests/TheAngryByrd.TypeSafeInternals.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/TheAngryByrd.TypeSafeInternals.Tests/paket.references: -------------------------------------------------------------------------------- 1 | Expecto 2 | FSharp.Core 3 | Microsoft.NET.Test.Sdk 4 | YoloDev.Expecto.TestSdk 5 | altcover --------------------------------------------------------------------------------