├── .config └── dotnet-tools.json ├── .devcontainer ├── Dockerfile ├── devcontainer.json ├── install-dotnets.sh └── settings.vscode.json ├── .editorconfig ├── .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 └── settings.json ├── CHANGELOG.md ├── Directory.Build.props ├── FsOpenTelemetry.sln ├── LICENSE.md ├── README.md ├── build.cmd ├── build.sh ├── build ├── build.fs ├── build.fsproj └── paket.references ├── docs └── coverage │ ├── FsOpenTelemetry_Say.htm │ ├── class.js │ ├── icon_cube.svg │ ├── icon_down-dir_active.svg │ ├── icon_fork.svg │ ├── icon_info-circled.svg │ ├── icon_minus.svg │ ├── icon_plus.svg │ ├── icon_search-minus.svg │ ├── icon_search-plus.svg │ ├── icon_up-dir.svg │ ├── icon_up-dir_active.svg │ ├── icon_wrench.svg │ ├── index.htm │ ├── main.js │ └── report.css ├── 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 └── FsOpenTelemetry │ ├── AssemblyInfo.fs │ ├── FsOpenTelemetry.fs │ ├── FsOpenTelemetry.fsproj │ └── paket.references └── tests ├── Directory.Build.props └── FsOpenTelemetry.Tests ├── AssemblyInfo.fs ├── FsOpenTelemetry.Tests.fsproj ├── Main.fs ├── TestClass.fs ├── Tests.fs ├── TestsActivitySource.fs └── paket.references /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "sourcelink": { 6 | "version": "3.1.1", 7 | "commands": [ 8 | "sourcelink" 9 | ] 10 | }, 11 | "dotnet-reportgenerator-globaltool": { 12 | "version": "4.2.15", 13 | "commands": [ 14 | "reportgenerator" 15 | ] 16 | }, 17 | "paket": { 18 | "version": "7.2.0", 19 | "commands": [ 20 | "paket" 21 | ] 22 | }, 23 | "fcswatch-cli": { 24 | "version": "0.7.14", 25 | "commands": [ 26 | "fcswatch" 27 | ] 28 | }, 29 | "fsharp-analyzers": { 30 | "version": "0.9.0", 31 | "commands": [ 32 | "fsharp-analyzers" 33 | ] 34 | }, 35 | "fantomas": { 36 | "version": "5.2.1", 37 | "commands": [ 38 | "fantomas" 39 | ] 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /.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 | trim_trailing_whitespace = true 15 | 16 | [*.{fs,fsi,fsx,config}] 17 | # https://fsprojects.github.io/fantomas/docs/end-users/Configuration.html 18 | charset = utf-8 19 | trim_trailing_whitespace = true 20 | max_line_length=100 21 | fsharp_multiline_block_brackets_on_same_column=true 22 | fsharp_experimental_stroustrup_style=true 23 | fsharp_keep_max_number_of_blank_lines=2 24 | fsharp_max_array_or_list_number_of_items=1 25 | fsharp_array_or_list_multiline_formatter=number_of_items 26 | fsharp_max_infix_operator_expression=10 27 | fsharp_multi_line_lambda_closing_newline=true 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 | -------------------------------------------------------------------------------- /.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 FsOpenTelemetry? 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@v3 14 | - name: Setup necessary dotnet SDKs 15 | uses: actions/setup-dotnet@v3 16 | with: 17 | global-json-file: global.json 18 | dotnet-version: | 19 | 6.x 20 | 7.x 21 | 22 | - name: Build via Bash 23 | if: runner.os != 'Windows' 24 | run: | 25 | chmod +x ./build.sh 26 | ./build.sh 27 | env: 28 | CI: true 29 | - name: Build via Windows 30 | if: runner.os == 'Windows' 31 | run: ./build.cmd 32 | env: 33 | CI: true 34 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.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/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "FSharp.fsacRuntime":"netcore", 3 | "FSharp.enableAnalyzers": true, 4 | "FSharp.analyzersPath": [ 5 | "./packages/analyzers" 6 | ], 7 | "editor.formatOnSave": true 8 | } 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.1.0] - 2017-03-17 10 | First release 11 | 12 | ### Added 13 | - This release already has lots of features 14 | 15 | [0.1.0]: https://github.com/user/MyCoolNewLib.git/releases/tag/v0.1.0 16 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | f#, fsharp 5 | https://github.com/TheAngryByrd/FsOpenTelemetry 6 | https://github.com/TheAngryByrd/FsOpenTelemetry/blob/master/LICENSE.md 7 | false 8 | git 9 | TheAngryByrd 10 | https://github.com/TheAngryByrd/FsOpenTelemetry 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /FsOpenTelemetry.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C397A34C-84F1-49E7-AEBC-2F9F2B196216}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsOpenTelemetry", "src\FsOpenTelemetry\FsOpenTelemetry.fsproj", "{5D30E174-2538-47AC-8443-318C8C5DC2C9}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ACBEE43C-7A88-4FB1-9B06-DB064D22B29F}" 11 | EndProject 12 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsOpenTelemetry.Tests", "tests\FsOpenTelemetry.Tests\FsOpenTelemetry.Tests.fsproj", "{1CA2E092-2320-451D-A4F0-9ED7C7C528CA}" 13 | EndProject 14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "docsTool", "docsTool\docsTool.fsproj", "{8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}" 15 | EndProject 16 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "build", "build\build.fsproj", "{40D2259D-991D-44C4-B45D-C88CE0710C23}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Debug|x64 = Debug|x64 22 | Debug|x86 = Debug|x86 23 | Release|Any CPU = Release|Any CPU 24 | Release|x64 = Release|x64 25 | Release|x86 = Release|x86 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x64.ActiveCfg = Debug|x64 34 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x64.Build.0 = Debug|x64 35 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x86.ActiveCfg = Debug|x86 36 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x86.Build.0 = Debug|x86 37 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x64.ActiveCfg = Release|x64 40 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x64.Build.0 = Release|x64 41 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x86.ActiveCfg = Release|x86 42 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x86.Build.0 = Release|x86 43 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x64.ActiveCfg = Debug|x64 46 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x64.Build.0 = Debug|x64 47 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x86.ActiveCfg = Debug|x86 48 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x86.Build.0 = Debug|x86 49 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x64.ActiveCfg = Release|x64 52 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x64.Build.0 = Release|x64 53 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x86.ActiveCfg = Release|x86 54 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x86.Build.0 = Release|x86 55 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|x64.ActiveCfg = Debug|Any CPU 58 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|x64.Build.0 = Debug|Any CPU 59 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|x86.ActiveCfg = Debug|Any CPU 60 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Debug|x86.Build.0 = Debug|Any CPU 61 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|x64.ActiveCfg = Release|Any CPU 64 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|x64.Build.0 = Release|Any CPU 65 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|x86.ActiveCfg = Release|Any CPU 66 | {8855EC73-F6A1-43D3-AFBC-04A3E09F9BD9}.Release|x86.Build.0 = Release|Any CPU 67 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Debug|x64.ActiveCfg = Debug|Any CPU 70 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Debug|x64.Build.0 = Debug|Any CPU 71 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Debug|x86.ActiveCfg = Debug|Any CPU 72 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Debug|x86.Build.0 = Debug|Any CPU 73 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Release|Any CPU.Build.0 = Release|Any CPU 75 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Release|x64.ActiveCfg = Release|Any CPU 76 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Release|x64.Build.0 = Release|Any CPU 77 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Release|x86.ActiveCfg = Release|Any CPU 78 | {40D2259D-991D-44C4-B45D-C88CE0710C23}.Release|x86.Build.0 = Release|Any CPU 79 | EndGlobalSection 80 | GlobalSection(NestedProjects) = preSolution 81 | {5D30E174-2538-47AC-8443-318C8C5DC2C9} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216} 82 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA} = {ACBEE43C-7A88-4FB1-9B06-DB064D22B29F} 83 | EndGlobalSection 84 | EndGlobal 85 | -------------------------------------------------------------------------------- /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 | # FsOpenTelemetry 2 | 3 | ## What is this? 4 | 5 | FsOpenTelemetry is a single file you can copy paste or add through [Paket Github dependencies](https://fsprojects.github.io/Paket/github-dependencies.html) to provide your F# library with safe helpers for Activity and ActivitySource. 6 | 7 | ## Why does this exist? 8 | 9 | One of the [best practices](https://docs.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs#best-practices-2) from the Microsoft Distributed Tracing documentation states to use the `?.` since "`activity` returned by `ActivitySource.StartActivity` may be null". Since F# does not have this operator you have to put `if activity <> null then doThing` everywhere. This uses extensions methods on an existing [Activity](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.activity?view=net-6.0) so calls are straight forward without doing the null checks. 10 | 11 | This additionally adds several additional helpers such as [RecordException](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#record-exception), [Adding many of the semantic conventions as constants](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions), and automatically creating `Activity` with [Source Code attributes](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/span-general.md#source-code-attributes) filled out. 12 | 13 | ## How do I get started? 14 | 15 | ### 1. Put the file into your project 16 | 17 | #### Option 1 18 | 19 | Copy/paste [FsOpenTelemetry.fs](https://github.com/TheAngryByrd/FsOpenTelemetry/blob/master/src/FsOpenTelemetry/FsOpenTelemetry.fs) into your library. 20 | 21 | #### Option 2 22 | 23 | Read over [Paket Github dependencies](https://fsprojects.github.io/Paket/github-dependencies.html). 24 | 25 | Add the following line to your `paket.depedencies` file. 26 | 27 | ```paket 28 | github TheAngryByrd/FsOpenTelemetry src/FsOpenTelemetry/FsOpenTelemetry.fs 29 | ``` 30 | 31 | Then add the following line to projects with `paket.references` file you want FsOpenTelemetry to be available to. 32 | 33 | ```paket 34 | File: FsOpenTelemetry.fs 35 | ``` 36 | 37 | ### 2. Replace its namespace with yours 38 | 39 | To alleviate potential naming conflicts, it's best to replace FsOpenTelemetry namespace with your own. 40 | 41 | Here is an example with FAKE 5: 42 | 43 | ```fsharp 44 | Target.create "Replace" <| fun _ -> 45 | Shell.replaceInFiles 46 | [ "FsOpenTelemetry", "MyLib.DistributedTracing" ] 47 | (!! "paket-files/TheAngryByrd/FsOpenTelemetry/src/FsOpenTelemetry/FsOpenTelemetry.fs") 48 | ``` 49 | 50 | --- 51 | 52 | ## Builds 53 | 54 | GitHub Actions | 55 | :---: | 56 | [![GitHub Actions](https://github.com/TheAngryByrd/FsOpenTelemetry/workflows/Build%20master/badge.svg)](https://github.com/TheAngryByrd/FsOpenTelemetry/actions?query=branch%3Amaster) | 57 | [![Build History](https://buildstats.info/github/chart/TheAngryByrd/FsOpenTelemetry)](https://github.com/TheAngryByrd/FsOpenTelemetry/actions?query=branch%3Amaster) | 58 | 59 | --- 60 | 61 | ### Developing 62 | 63 | Make sure the following **requirements** are installed on your system: 64 | 65 | - [dotnet SDK](https://www.microsoft.com/net/download/core) 3.0 or higher 66 | - [Mono](http://www.mono-project.com/) if you're on Linux or macOS. 67 | 68 | or 69 | 70 | - [VSCode Dev Container](https://code.visualstudio.com/docs/remote/containers) 71 | 72 | 73 | --- 74 | 75 | ### Environment Variables 76 | 77 | - `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. 78 | - `CONFIGURATION=Debug ./build.sh` will result in `-c` additions to commands such as in `dotnet build -c Debug` 79 | - `GITHUB_TOKEN` will be used to upload release notes and Nuget packages to GitHub. 80 | - Be sure to set this before releasing 81 | - `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. 82 | - `DISABLE_COVERAGE=1 ./build.sh` 83 | 84 | 85 | --- 86 | 87 | ### Building 88 | 89 | 90 | ```sh 91 | > build.cmd // on windows 92 | $ ./build.sh // on unix 93 | ``` 94 | 95 | The bin of your library should look similar to: 96 | 97 | ``` 98 | $ tree src/MyCoolNewLib/bin/ 99 | src/MyCoolNewLib/bin/ 100 | └── Debug 101 | └── net50 102 | ├── MyCoolNewLib.deps.json 103 | ├── MyCoolNewLib.dll 104 | ├── MyCoolNewLib.pdb 105 | └── MyCoolNewLib.xml 106 | 107 | ``` 108 | 109 | --- 110 | 111 | ### Build Targets 112 | 113 | - `Clean` - Cleans artifact and temp directories. 114 | - `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). 115 | - [`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). 116 | - `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). 117 | - `GenerateCoverageReport` - Code coverage is run during `DotnetTest` and this generates a report via [ReportGenerator](https://github.com/danielpalme/ReportGenerator). 118 | - `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. 119 | - `GenerateAssemblyInfo` - Generates [AssemblyInfo](https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.applicationservices.assemblyinfo?view=netframework-4.8) for libraries. 120 | - `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). 121 | - `SourceLinkTest` - Runs a Source Link test tool to verify Source Links were properly generated. 122 | - `PublishToNuGet` - Publishes the NuGet packages generated in `DotnetPack` to NuGet via [paket push](https://fsprojects.github.io/Paket/paket-push.html). 123 | - `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`. 124 | - `GitHubRelease` - Publishes a [GitHub Release](https://help.github.com/en/articles/creating-releases) with the Release Notes and any NuGet packages. 125 | - `FormatCode` - Runs [Fantomas](https://github.com/fsprojects/fantomas) on the solution file. 126 | - `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`. 127 | - `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. 128 | - `ReleaseDocs` - Will stage, commit, and push docs generated in the `BuildDocs` target. 129 | - [`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. 130 | --- 131 | 132 | 133 | ### Releasing 134 | 135 | - [Start a git repo with a remote](https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/) 136 | 137 | ```sh 138 | git add . 139 | git commit -m "Scaffold" 140 | git remote add origin https://github.com/user/MyCoolNewLib.git 141 | git push -u origin master 142 | ``` 143 | 144 | - [Create your NuGeT API key](https://docs.microsoft.com/en-us/nuget/nuget-org/publish-a-package#create-api-keys) 145 | - [Add your NuGet API key to paket](https://fsprojects.github.io/Paket/paket-config.html#Adding-a-NuGet-API-key) 146 | 147 | ```sh 148 | paket config add-token "https://www.nuget.org" 4003d786-cc37-4004-bfdf-c4f3e8ef9b3a 149 | ``` 150 | 151 | - or set the environment variable `NUGET_TOKEN` to your key 152 | 153 | 154 | - [Create a GitHub OAuth Token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) 155 | - You can then set the environment variable `GITHUB_TOKEN` to upload release notes and artifacts to github 156 | - Otherwise it will fallback to username/password 157 | 158 | - 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. 159 | 160 | 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. 161 | 162 | Here's an example of adding an "Unreleased" section to a `CHANGELOG.md` with a `0.1.0` section already released. 163 | 164 | ```markdown 165 | ## [Unreleased] 166 | 167 | ### Added 168 | - Does cool stuff! 169 | 170 | ### Fixed 171 | - Fixes that silly oversight 172 | 173 | ## [0.1.0] - 2017-03-17 174 | First release 175 | 176 | ### Added 177 | - This release already has lots of features 178 | 179 | [Unreleased]: https://github.com/user/MyCoolNewLib.git/compare/v0.1.0...HEAD 180 | [0.1.0]: https://github.com/user/MyCoolNewLib.git/releases/tag/v0.1.0 181 | ``` 182 | 183 | - You can then use the `Release` target, specifying the version number either in the `RELEASE_VERSION` environment 184 | variable, or else as a parameter after the target name. This will: 185 | - update `CHANGELOG.md`, moving changes from the `Unreleased` section into a new `0.2.0` section 186 | - 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 187 | - make a commit bumping the version: `Bump version to 0.2.0` and adds the new changelog section to the commit's body 188 | - publish the package to NuGet 189 | - push a git tag 190 | - create a GitHub release for that git tag 191 | 192 | macOS/Linux Parameter: 193 | 194 | ```sh 195 | ./build.sh Release 0.2.0 196 | ``` 197 | 198 | macOS/Linux Environment Variable: 199 | 200 | ```sh 201 | RELEASE_VERSION=0.2.0 ./build.sh Release 202 | ``` 203 | 204 | 205 | -------------------------------------------------------------------------------- /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 | 3 | set -eu 4 | set -o pipefail 5 | 6 | echo "Restoring dotnet tools..." 7 | dotnet tool restore 8 | 9 | FAKE_DETAILED_ERRORS=true dotnet run --project ./build/build.fsproj -- -t "$@" 10 | -------------------------------------------------------------------------------- /build/build.fs: -------------------------------------------------------------------------------- 1 | 2 | open System 3 | open Fake.Core 4 | open Fake.DotNet 5 | open Fake.Tools 6 | open Fake.IO 7 | open Fake.IO.FileSystemOperators 8 | open Fake.IO.Globbing.Operators 9 | open Fake.Core.TargetOperators 10 | open Fake.Api 11 | open Fake.BuildServer 12 | open Argu 13 | 14 | let environVarAsBoolOrDefault varName defaultValue = 15 | let truthyConsts = [ 16 | "1" 17 | "Y" 18 | "YES" 19 | "T" 20 | "TRUE" 21 | ] 22 | try 23 | let envvar = (Environment.environVar varName).ToUpper() 24 | truthyConsts |> List.exists((=)envvar) 25 | with 26 | | _ -> defaultValue 27 | 28 | //----------------------------------------------------------------------------- 29 | // Metadata and Configuration 30 | //----------------------------------------------------------------------------- 31 | 32 | let productName = "FsOpenTelemetry" 33 | let sln = __SOURCE_DIRECTORY__ ".." "FsOpenTelemetry.sln" 34 | 35 | 36 | let srcCodeGlob = 37 | !! (__SOURCE_DIRECTORY__ ".." "src/**/*.fs") 38 | ++ (__SOURCE_DIRECTORY__ ".." "src/**/*.fsx") 39 | -- (__SOURCE_DIRECTORY__ ".." "src/**/obj/**/*.fs") 40 | 41 | let testsCodeGlob = 42 | !! (__SOURCE_DIRECTORY__ ".." "tests/**/*.fs") 43 | ++ (__SOURCE_DIRECTORY__ ".." "tests/**/*.fsx") 44 | -- (__SOURCE_DIRECTORY__ ".." "tests/**/obj/**/*.fs") 45 | 46 | let srcGlob =__SOURCE_DIRECTORY__ ".." "src/**/*.??proj" 47 | let testsGlob = __SOURCE_DIRECTORY__ ".." "tests/**/*.??proj" 48 | 49 | let srcAndTest = 50 | !! srcGlob 51 | ++ testsGlob 52 | 53 | let distDir = __SOURCE_DIRECTORY__ ".." "dist" 54 | let distGlob = distDir "*.nupkg" 55 | 56 | let coverageThresholdPercent = 0 57 | let coverageReportDir = __SOURCE_DIRECTORY__ ".." "docs" "coverage" 58 | 59 | 60 | let docsDir = __SOURCE_DIRECTORY__ ".." "docs" 61 | let docsSrcDir = __SOURCE_DIRECTORY__ ".." "docsSrc" 62 | let docsToolDir = __SOURCE_DIRECTORY__ ".." "docsTool" 63 | 64 | let gitOwner = "TheAngryByrd" 65 | let gitRepoName = "FsOpenTelemetry" 66 | 67 | let gitHubRepoUrl = sprintf "https://github.com/%s/%s" gitOwner gitRepoName 68 | 69 | let releaseBranch = "master" 70 | 71 | let tagFromVersionNumber versionNumber = sprintf "v%s" versionNumber 72 | 73 | let changelogFilename = __SOURCE_DIRECTORY__ ".." "CHANGELOG.md" 74 | let changelog = Fake.Core.Changelog.load changelogFilename 75 | let mutable latestEntry = 76 | if Seq.isEmpty changelog.Entries 77 | then Changelog.ChangelogEntry.New("0.0.1", "0.0.1-alpha.1", Some DateTime.Today, None, [], false) 78 | else changelog.LatestEntry 79 | let mutable linkReferenceForLatestEntry = "" 80 | let mutable changelogBackupFilename = "" 81 | 82 | let publishUrl = "https://www.nuget.org" 83 | 84 | let docsSiteBaseUrl = sprintf "https://%s.github.io/%s" gitOwner gitRepoName 85 | 86 | let disableCodeCoverage = environVarAsBoolOrDefault "DISABLE_COVERAGE" false 87 | 88 | let githubToken = Environment.environVarOrNone "GITHUB_TOKEN" 89 | 90 | 91 | let nugetToken = Environment.environVarOrNone "NUGET_TOKEN" 92 | 93 | //----------------------------------------------------------------------------- 94 | // Helpers 95 | //----------------------------------------------------------------------------- 96 | 97 | let isRelease (targets : Target list) = 98 | targets 99 | |> Seq.map(fun t -> t.Name) 100 | |> Seq.exists ((=)"Release") 101 | 102 | let invokeAsync f = async { f () } 103 | 104 | let configuration (targets : Target list) = 105 | let defaultVal = if isRelease targets then "Release" else "Debug" 106 | match Environment.environVarOrDefault "CONFIGURATION" defaultVal with 107 | | "Debug" -> DotNet.BuildConfiguration.Debug 108 | | "Release" -> DotNet.BuildConfiguration.Release 109 | | config -> DotNet.BuildConfiguration.Custom config 110 | 111 | let failOnBadExitAndPrint (p : ProcessResult) = 112 | if p.ExitCode <> 0 then 113 | p.Errors |> Seq.iter Trace.traceError 114 | failwithf "failed with exitcode %d" p.ExitCode 115 | 116 | // CI Servers can have bizzare failures that have nothing to do with your code 117 | let rec retryIfInCI times fn = 118 | match Environment.environVarOrNone "CI" with 119 | | Some _ -> 120 | if times > 1 then 121 | try 122 | fn() 123 | with 124 | | _ -> retryIfInCI (times - 1) fn 125 | else 126 | fn() 127 | | _ -> fn() 128 | 129 | let isReleaseBranchCheck () = 130 | if Git.Information.getBranchName "" <> releaseBranch then failwithf "Not on %s. If you want to release please switch to this branch." releaseBranch 131 | 132 | module Changelog = 133 | 134 | let isEmptyChange = function 135 | | Changelog.Change.Added s 136 | | Changelog.Change.Changed s 137 | | Changelog.Change.Deprecated s 138 | | Changelog.Change.Fixed s 139 | | Changelog.Change.Removed s 140 | | Changelog.Change.Security s 141 | | Changelog.Change.Custom (_, s) -> 142 | String.IsNullOrWhiteSpace s.CleanedText 143 | 144 | let isChangelogEmpty () = 145 | let isEmpty = 146 | (latestEntry.Changes |> Seq.forall isEmptyChange) 147 | || latestEntry.Changes |> Seq.isEmpty 148 | if isEmpty then failwith "No changes in CHANGELOG. Please add your changes under a heading specified in https://keepachangelog.com/" 149 | 150 | let mkLinkReference (newVersion : SemVerInfo) (changelog : Changelog.Changelog) = 151 | if changelog.Entries |> List.isEmpty then 152 | // No actual changelog entries yet: link reference will just point to the Git tag 153 | sprintf "[%s]: %s/releases/tag/%s" newVersion.AsString gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) 154 | else 155 | let versionTuple version = (version.Major, version.Minor, version.Patch) 156 | // Changelog entries come already sorted, most-recent first, by the Changelog module 157 | let prevEntry = changelog.Entries |> List.skipWhile (fun entry -> entry.SemVer.PreRelease.IsSome && versionTuple entry.SemVer = versionTuple newVersion) |> List.tryHead 158 | let linkTarget = 159 | match prevEntry with 160 | | Some entry -> sprintf "%s/compare/%s...%s" gitHubRepoUrl (tagFromVersionNumber entry.SemVer.AsString) (tagFromVersionNumber newVersion.AsString) 161 | | None -> sprintf "%s/releases/tag/%s" gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) 162 | sprintf "[%s]: %s" newVersion.AsString linkTarget 163 | 164 | let mkReleaseNotes (linkReference : string) (latestEntry : Changelog.ChangelogEntry) = 165 | if String.isNullOrEmpty linkReference then latestEntry.ToString() 166 | else 167 | // Add link reference target to description before building release notes, since in main changelog file it's at the bottom of the file 168 | let description = 169 | match latestEntry.Description with 170 | | None -> linkReference 171 | | Some desc when desc.Contains(linkReference) -> desc 172 | | Some desc -> sprintf "%s\n\n%s" (desc.Trim()) linkReference 173 | { latestEntry with Description = Some description }.ToString() 174 | 175 | let getVersionNumber envVarName ctx = 176 | let args = ctx.Context.Arguments 177 | let verArg = 178 | args 179 | |> List.tryHead 180 | |> Option.defaultWith (fun () -> Environment.environVarOrDefault envVarName "") 181 | if SemVer.isValid verArg then verArg 182 | elif verArg.StartsWith("v") && SemVer.isValid verArg.[1..] then 183 | let target = ctx.Context.FinalTarget 184 | 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 185 | verArg.[1..] 186 | elif String.isNullOrEmpty verArg then 187 | let target = ctx.Context.FinalTarget 188 | 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 189 | failwith "No version number found" 190 | else 191 | Trace.traceErrorfn "Please specify a valid version number: %A could not be recognized as a version number" verArg 192 | failwith "Invalid version number" 193 | 194 | 195 | module dotnet = 196 | let watch cmdParam program args = 197 | DotNet.exec cmdParam (sprintf "watch %s" program) args 198 | 199 | let run cmdParam args = 200 | DotNet.exec cmdParam "run" args 201 | 202 | let tool optionConfig command args = 203 | DotNet.exec optionConfig (sprintf "%s" command) args 204 | |> failOnBadExitAndPrint 205 | 206 | let reportgenerator optionConfig args = 207 | tool optionConfig "reportgenerator" args 208 | 209 | let sourcelink optionConfig args = 210 | tool optionConfig "sourcelink" args 211 | 212 | let fcswatch optionConfig args = 213 | tool optionConfig "fcswatch" args 214 | 215 | let fsharpAnalyzer optionConfig args = 216 | tool optionConfig "fsharp-analyzers" args 217 | 218 | let fantomas args = 219 | DotNet.exec id "fantomas" args 220 | 221 | module FSharpAnalyzers = 222 | type Arguments = 223 | | Project of string 224 | | Analyzers_Path of string 225 | | Fail_On_Warnings of string list 226 | | Ignore_Files of string list 227 | | Verbose 228 | with 229 | interface IArgParserTemplate with 230 | member s.Usage = "" 231 | 232 | 233 | open DocsTool.CLIArgs 234 | module DocsTool = 235 | open Argu 236 | let buildparser = ArgumentParser.Create(programName = "docstool") 237 | let buildCLI () = 238 | [ 239 | BuildArgs.SiteBaseUrl docsSiteBaseUrl 240 | BuildArgs.ProjectGlob srcGlob 241 | BuildArgs.DocsOutputDirectory docsDir 242 | BuildArgs.DocsSourceDirectory docsSrcDir 243 | BuildArgs.GitHubRepoUrl gitHubRepoUrl 244 | BuildArgs.ProjectName gitRepoName 245 | BuildArgs.ReleaseVersion latestEntry.NuGetVersion 246 | ] 247 | |> buildparser.PrintCommandLineArgumentsFlat 248 | 249 | let build () = 250 | dotnet.run (fun args -> 251 | { args with WorkingDirectory = docsToolDir } 252 | ) (sprintf " -- build %s" (buildCLI())) 253 | |> failOnBadExitAndPrint 254 | 255 | let watchparser = ArgumentParser.Create(programName = "docstool") 256 | let watchCLI () = 257 | [ 258 | WatchArgs.ProjectGlob srcGlob 259 | WatchArgs.DocsSourceDirectory docsSrcDir 260 | WatchArgs.GitHubRepoUrl gitHubRepoUrl 261 | WatchArgs.ProjectName gitRepoName 262 | WatchArgs.ReleaseVersion latestEntry.NuGetVersion 263 | ] 264 | |> watchparser.PrintCommandLineArgumentsFlat 265 | 266 | let watch () = 267 | dotnet.watch (fun args -> 268 | { args with WorkingDirectory = docsToolDir } 269 | ) "run" (sprintf "-- watch %s" (watchCLI())) 270 | |> failOnBadExitAndPrint 271 | 272 | let allReleaseChecks () = 273 | isReleaseBranchCheck () 274 | Changelog.isChangelogEmpty () 275 | 276 | //----------------------------------------------------------------------------- 277 | // Target Implementations 278 | //----------------------------------------------------------------------------- 279 | 280 | 281 | let clean _ = 282 | ["bin"; "temp" ; distDir; coverageReportDir] 283 | |> Shell.cleanDirs 284 | 285 | !! srcGlob 286 | ++ testsGlob 287 | |> Seq.collect(fun p -> 288 | ["bin";"obj"] 289 | |> Seq.map(fun sp -> IO.Path.GetDirectoryName p sp )) 290 | |> Shell.cleanDirs 291 | 292 | [ 293 | "paket-files/paket.restore.cached" 294 | ] 295 | |> Seq.iter Shell.rm 296 | 297 | let dotnetRestore _ = 298 | [sln] 299 | |> Seq.map(fun dir -> fun () -> 300 | let args = 301 | [ 302 | ] |> String.concat " " 303 | DotNet.restore(fun c -> 304 | { c with 305 | Common = 306 | c.Common 307 | |> DotNet.Options.withCustomParams 308 | (Some(args)) 309 | }) dir) 310 | |> Seq.iter(retryIfInCI 10) 311 | 312 | let updateChangelog ctx = 313 | let description, unreleasedChanges = 314 | match changelog.Unreleased with 315 | | None -> None, [] 316 | | Some u -> u.Description, u.Changes 317 | let verStr = ctx |> Changelog.getVersionNumber "RELEASE_VERSION" 318 | let newVersion = SemVer.parse verStr 319 | changelog.Entries 320 | |> List.tryFind (fun entry -> entry.SemVer = newVersion) 321 | |> Option.iter (fun entry -> 322 | 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)") 323 | failwith "Can't release with a duplicate version number" 324 | ) 325 | changelog.Entries 326 | |> List.tryFind (fun entry -> entry.SemVer > newVersion) 327 | |> Option.iter (fun entry -> 328 | 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)") 329 | failwith "Can't release with a version number older than an existing release" 330 | ) 331 | let versionTuple version = (version.Major, version.Minor, version.Patch) 332 | let prereleaseEntries = changelog.Entries |> List.filter (fun entry -> entry.SemVer.PreRelease.IsSome && versionTuple entry.SemVer = versionTuple newVersion) 333 | let prereleaseChanges = prereleaseEntries |> List.collect (fun entry -> entry.Changes |> List.filter (not << Changelog.isEmptyChange)) 334 | let assemblyVersion, nugetVersion = Changelog.parseVersions newVersion.AsString 335 | linkReferenceForLatestEntry <- Changelog.mkLinkReference newVersion changelog 336 | let newEntry = Changelog.ChangelogEntry.New(assemblyVersion.Value, nugetVersion.Value, Some System.DateTime.Today, description, unreleasedChanges @ prereleaseChanges, false) 337 | let newChangelog = Changelog.Changelog.New(changelog.Header, changelog.Description, None, newEntry :: changelog.Entries) 338 | latestEntry <- newEntry 339 | 340 | // Save changelog to temporary file before making any edits 341 | changelogBackupFilename <- System.IO.Path.GetTempFileName() 342 | changelogFilename |> Shell.copyFile changelogBackupFilename 343 | Target.activateFinal "DeleteChangelogBackupFile" 344 | 345 | newChangelog 346 | |> Changelog.save changelogFilename 347 | 348 | // Now update the link references at the end of the file 349 | linkReferenceForLatestEntry <- Changelog.mkLinkReference newVersion changelog 350 | let linkReferenceForUnreleased = sprintf "[Unreleased]: %s/compare/%s...%s" gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) "HEAD" 351 | let tailLines = File.read changelogFilename |> List.ofSeq |> List.rev 352 | 353 | let isRef (line : string) = System.Text.RegularExpressions.Regex.IsMatch(line, @"^\[.+?\]:\s?[a-z]+://.*$") 354 | let linkReferenceTargets = 355 | tailLines 356 | |> List.skipWhile String.isNullOrWhiteSpace 357 | |> List.takeWhile isRef 358 | |> List.rev // Now most recent entry is at the head of the list 359 | 360 | let newLinkReferenceTargets = 361 | match linkReferenceTargets with 362 | | [] -> 363 | [linkReferenceForUnreleased; linkReferenceForLatestEntry] 364 | | first :: rest when first |> String.startsWith "[Unreleased]:" -> 365 | linkReferenceForUnreleased :: linkReferenceForLatestEntry :: rest 366 | | first :: rest -> 367 | linkReferenceForUnreleased :: linkReferenceForLatestEntry :: first :: rest 368 | 369 | let blankLineCount = tailLines |> Seq.takeWhile String.isNullOrWhiteSpace |> Seq.length 370 | let linkRefCount = linkReferenceTargets |> List.length 371 | let skipCount = blankLineCount + linkRefCount 372 | let updatedLines = List.rev (tailLines |> List.skip skipCount) @ newLinkReferenceTargets 373 | File.write false changelogFilename updatedLines 374 | 375 | // If build fails after this point but before we push the release out, undo our modifications 376 | Target.activateBuildFailure "RevertChangelog" 377 | 378 | let revertChangelog _ = 379 | if String.isNotNullOrEmpty changelogBackupFilename then 380 | changelogBackupFilename |> Shell.copyFile changelogFilename 381 | 382 | let deleteChangelogBackupFile _ = 383 | if String.isNotNullOrEmpty changelogBackupFilename then 384 | Shell.rm changelogBackupFilename 385 | 386 | let dotnetBuild ctx = 387 | let args = 388 | [ 389 | sprintf "/p:PackageVersion=%s" latestEntry.NuGetVersion 390 | "--no-restore" 391 | ] 392 | DotNet.build(fun c -> 393 | { c with 394 | Configuration = configuration (ctx.Context.AllExecutingTargets) 395 | Common = 396 | c.Common 397 | |> DotNet.Options.withAdditionalArgs args 398 | 399 | }) sln 400 | 401 | let fsharpAnalyzers _ = 402 | let argParser = ArgumentParser.Create(programName = "fsharp-analyzers") 403 | !! srcGlob 404 | |> Seq.iter(fun proj -> 405 | let args = 406 | [ 407 | FSharpAnalyzers.Analyzers_Path (__SOURCE_DIRECTORY__ ".." "packages/analyzers") 408 | FSharpAnalyzers.Arguments.Project proj 409 | FSharpAnalyzers.Arguments.Fail_On_Warnings [ 410 | "BDH0002" 411 | ] 412 | FSharpAnalyzers.Verbose 413 | ] 414 | |> argParser.PrintCommandLineArgumentsFlat 415 | dotnet.fsharpAnalyzer id args 416 | ) 417 | 418 | let dotnetTest ctx = 419 | let excludeCoverage = 420 | !! testsGlob 421 | |> Seq.map IO.Path.GetFileNameWithoutExtension 422 | |> String.concat "|" 423 | let args = 424 | [ 425 | "--no-build" 426 | sprintf "/p:AltCover=%b" (not disableCodeCoverage) 427 | sprintf "/p:AltCoverThreshold=%d" coverageThresholdPercent 428 | sprintf "/p:AltCoverAssemblyExcludeFilter=%s" excludeCoverage 429 | "/p:AltCoverLocalSource=true" 430 | ] 431 | DotNet.test(fun c -> 432 | 433 | { c with 434 | Configuration = configuration (ctx.Context.AllExecutingTargets) 435 | Common = 436 | c.Common 437 | |> DotNet.Options.withAdditionalArgs args 438 | }) sln 439 | 440 | let generateCoverageReport _ = 441 | let coverageReports = 442 | !!"tests/**/coverage*.xml" 443 | |> String.concat ";" 444 | let sourceDirs = 445 | !! srcGlob 446 | |> Seq.map Path.getDirectory 447 | |> String.concat ";" 448 | let independentArgs = 449 | [ 450 | sprintf "-reports:\"%s\"" coverageReports 451 | sprintf "-targetdir:\"%s\"" coverageReportDir 452 | // Add source dir 453 | sprintf "-sourcedirs:\"%s\"" sourceDirs 454 | // Ignore Tests and if AltCover.Recorder.g sneaks in 455 | sprintf "-assemblyfilters:\"%s\"" "-*.Tests;-AltCover.Recorder.g" 456 | sprintf "-Reporttypes:%s" "Html" 457 | ] 458 | let args = 459 | independentArgs 460 | |> String.concat " " 461 | dotnet.reportgenerator id args 462 | 463 | let watchTests _ = 464 | !! testsGlob 465 | |> Seq.map(fun proj -> fun () -> 466 | dotnet.watch 467 | (fun opt -> 468 | opt |> DotNet.Options.withWorkingDirectory (IO.Path.GetDirectoryName proj)) 469 | "test" 470 | "" 471 | |> ignore 472 | ) 473 | |> Seq.iter (invokeAsync >> Async.Catch >> Async.Ignore >> Async.Start) 474 | 475 | printfn "Press Ctrl+C (or Ctrl+Break) to stop..." 476 | let cancelEvent = Console.CancelKeyPress |> Async.AwaitEvent |> Async.RunSynchronously 477 | cancelEvent.Cancel <- true 478 | 479 | let generateAssemblyInfo _ = 480 | 481 | let (|Fsproj|Csproj|Vbproj|) (projFileName:string) = 482 | match projFileName with 483 | | f when f.EndsWith("fsproj") -> Fsproj 484 | | f when f.EndsWith("csproj") -> Csproj 485 | | f when f.EndsWith("vbproj") -> Vbproj 486 | | _ -> failwith (sprintf "Project file %s not supported. Unknown project type." projFileName) 487 | 488 | let releaseChannel = 489 | match latestEntry.SemVer.PreRelease with 490 | | Some pr -> pr.Name 491 | | _ -> "release" 492 | let getAssemblyInfoAttributes projectName = 493 | [ 494 | AssemblyInfo.Title (projectName) 495 | AssemblyInfo.Product productName 496 | AssemblyInfo.Version latestEntry.AssemblyVersion 497 | AssemblyInfo.Metadata("ReleaseDate", latestEntry.Date.Value.ToString("o")) 498 | AssemblyInfo.FileVersion latestEntry.AssemblyVersion 499 | AssemblyInfo.InformationalVersion latestEntry.AssemblyVersion 500 | AssemblyInfo.Metadata("ReleaseChannel", releaseChannel) 501 | AssemblyInfo.Metadata("GitHash", Git.Information.getCurrentSHA1(null)) 502 | ] 503 | 504 | let getProjectDetails (projectPath : string) = 505 | let projectName = IO.Path.GetFileNameWithoutExtension(projectPath) 506 | ( 507 | projectPath, 508 | projectName, 509 | IO.Path.GetDirectoryName(projectPath), 510 | (getAssemblyInfoAttributes projectName) 511 | ) 512 | 513 | srcAndTest 514 | |> Seq.map getProjectDetails 515 | |> Seq.iter (fun (projFileName, _, folderName, attributes) -> 516 | match projFileName with 517 | | Fsproj -> AssemblyInfoFile.createFSharp (folderName "AssemblyInfo.fs") attributes 518 | | Csproj -> AssemblyInfoFile.createCSharp ((folderName "Properties") "AssemblyInfo.cs") attributes 519 | | Vbproj -> AssemblyInfoFile.createVisualBasic ((folderName "My Project") "AssemblyInfo.vb") attributes 520 | ) 521 | 522 | let dotnetPack ctx = 523 | // Get release notes with properly-linked version number 524 | let releaseNotes = latestEntry |> Changelog.mkReleaseNotes linkReferenceForLatestEntry 525 | let args = 526 | [ 527 | sprintf "/p:PackageVersion=%s" latestEntry.NuGetVersion 528 | sprintf "/p:PackageReleaseNotes=\"%s\"" releaseNotes 529 | ] 530 | DotNet.pack (fun c -> 531 | { c with 532 | Configuration = configuration (ctx.Context.AllExecutingTargets) 533 | OutputPath = Some distDir 534 | Common = 535 | c.Common 536 | |> DotNet.Options.withAdditionalArgs args 537 | }) sln 538 | 539 | let sourceLinkTest _ = 540 | !! distGlob 541 | |> Seq.iter (fun nupkg -> 542 | dotnet.sourcelink id (sprintf "test %s" nupkg) 543 | ) 544 | 545 | let publishToNuget _ = 546 | allReleaseChecks () 547 | Paket.push(fun c -> 548 | { c with 549 | ToolType = ToolType.CreateLocalTool() 550 | PublishUrl = publishUrl 551 | WorkingDir = "dist" 552 | ApiKey = match nugetToken with 553 | | Some s -> s 554 | | _ -> c.ApiKey // assume paket-config was set properly 555 | } 556 | ) 557 | // If build fails after this point, we've pushed a release out with this version of CHANGELOG.md so we should keep it around 558 | Target.deactivateBuildFailure "RevertChangelog" 559 | 560 | let gitRelease _ = 561 | allReleaseChecks () 562 | 563 | let releaseNotesGitCommitFormat = latestEntry.ToString() 564 | 565 | Git.Staging.stageFile "" "CHANGELOG.md" 566 | |> ignore 567 | 568 | !! "src/**/AssemblyInfo.fs" 569 | |> Seq.iter (Git.Staging.stageFile "" >> ignore) 570 | 571 | Git.Commit.exec "" (sprintf "Bump version to %s\n\n%s" latestEntry.NuGetVersion releaseNotesGitCommitFormat) 572 | Git.Branches.push "" 573 | 574 | let tag = tagFromVersionNumber latestEntry.NuGetVersion 575 | 576 | Git.Branches.tag "" tag 577 | Git.Branches.pushTag "" "origin" tag 578 | 579 | let githubRelease _ = 580 | allReleaseChecks () 581 | let token = 582 | match githubToken with 583 | | Some s -> s 584 | | _ -> failwith "please set the github_token environment variable to a github personal access token with repo access." 585 | 586 | let files = !! distGlob 587 | // Get release notes with properly-linked version number 588 | let releaseNotes = latestEntry |> Changelog.mkReleaseNotes linkReferenceForLatestEntry 589 | 590 | GitHub.createClientWithToken token 591 | |> GitHub.draftNewRelease gitOwner gitRepoName (tagFromVersionNumber latestEntry.NuGetVersion) (latestEntry.SemVer.PreRelease <> None) (releaseNotes |> Seq.singleton) 592 | |> GitHub.uploadFiles files 593 | |> GitHub.publishDraft 594 | |> Async.RunSynchronously 595 | 596 | let formatCode _ = 597 | let result = 598 | [ 599 | srcCodeGlob 600 | testsCodeGlob 601 | ] 602 | |> Seq.collect id 603 | // Ignore AssemblyInfo 604 | |> Seq.filter(fun f -> f.EndsWith("AssemblyInfo.fs") |> not) 605 | |> String.concat " " 606 | |> dotnet.fantomas 607 | 608 | if not result.OK then 609 | printfn "Errors while formatting all files: %A" result.Messages 610 | 611 | let checkFormatCode _ = 612 | let result = 613 | [ 614 | srcCodeGlob 615 | testsCodeGlob 616 | ] 617 | |> Seq.collect id 618 | // Ignore AssemblyInfo 619 | |> Seq.filter(fun f -> f.EndsWith("AssemblyInfo.fs") |> not) 620 | |> String.concat " " 621 | |> sprintf "%s --check" 622 | |> dotnet.fantomas 623 | 624 | if result.ExitCode = 0 then 625 | Trace.log "No files need formatting" 626 | elif result.ExitCode = 99 then 627 | failwith "Some files need formatting, check output for more info" 628 | else 629 | Trace.logf "Errors while formatting: %A" result.Errors 630 | 631 | let buildDocs _ = 632 | DocsTool.build () 633 | 634 | let watchDocs _ = 635 | DocsTool.watch () 636 | 637 | let releaseDocs ctx = 638 | isReleaseBranchCheck () // Docs changes don't need a full release to the library 639 | 640 | Git.Staging.stageAll docsDir 641 | Git.Commit.exec "" (sprintf "Documentation release of version %s" latestEntry.NuGetVersion) 642 | if isRelease (ctx.Context.AllExecutingTargets) |> not then 643 | // We only want to push if we're only calling "ReleaseDocs" target 644 | // If we're calling "Release" target, we'll let the "GitRelease" target do the git push 645 | Git.Branches.push "" 646 | 647 | 648 | let initTargets () = 649 | BuildServer.install [ 650 | GitHubActions.Installer 651 | ] 652 | /// Defines a dependency - y is dependent on x. Finishes the chain. 653 | let (==>!) x y = x ==> y |> ignore 654 | /// Defines a soft dependency. x must run before y, if it is present, but y does not require x to be run. Finishes the chain. 655 | let (?=>!) x y = x ?=> y |> ignore 656 | //----------------------------------------------------------------------------- 657 | // Hide Secrets in Logger 658 | //----------------------------------------------------------------------------- 659 | Option.iter(TraceSecrets.register "" ) githubToken 660 | Option.iter(TraceSecrets.register "") nugetToken 661 | //----------------------------------------------------------------------------- 662 | // Target Declaration 663 | //----------------------------------------------------------------------------- 664 | 665 | Target.create "Clean" clean 666 | Target.create "DotnetRestore" dotnetRestore 667 | Target.create "UpdateChangelog" updateChangelog 668 | Target.createBuildFailure "RevertChangelog" revertChangelog // Do NOT put this in the dependency chain 669 | Target.createFinal "DeleteChangelogBackupFile" deleteChangelogBackupFile // Do NOT put this in the dependency chain 670 | Target.create "DotnetBuild" dotnetBuild 671 | Target.create "FSharpAnalyzers" fsharpAnalyzers 672 | Target.create "DotnetTest" dotnetTest 673 | Target.create "GenerateCoverageReport" generateCoverageReport 674 | Target.create "WatchTests" watchTests 675 | Target.create "GenerateAssemblyInfo" generateAssemblyInfo 676 | Target.create "DotnetPack" dotnetPack 677 | Target.create "SourceLinkTest" sourceLinkTest 678 | Target.create "PublishToNuGet" publishToNuget 679 | Target.create "GitRelease" gitRelease 680 | Target.create "GitHubRelease" githubRelease 681 | Target.create "FormatCode" formatCode 682 | Target.create "CheckFormatCode" checkFormatCode 683 | Target.create "Release" ignore 684 | Target.create "BuildDocs" buildDocs 685 | Target.create "WatchDocs" watchDocs 686 | Target.create "ReleaseDocs" releaseDocs 687 | 688 | //----------------------------------------------------------------------------- 689 | // Target Dependencies 690 | //----------------------------------------------------------------------------- 691 | 692 | 693 | // Only call Clean if DotnetPack was in the call chain 694 | // Ensure Clean is called before DotnetRestore 695 | "Clean" ?=>! "DotnetRestore" 696 | "Clean" ==>! "DotnetPack" 697 | 698 | // Only call GenerateAssemblyInfo if Publish was in the call chain 699 | // Ensure GenerateAssemblyInfo is called after DotnetRestore and before DotnetBuild 700 | "DotnetRestore" ?=>! "GenerateAssemblyInfo" 701 | "GenerateAssemblyInfo" ?=>! "DotnetBuild" 702 | "GenerateAssemblyInfo" ==>! "PublishToNuGet" 703 | 704 | // Only call UpdateChangelog if Publish was in the call chain 705 | // Ensure UpdateChangelog is called after DotnetRestore and before GenerateAssemblyInfo 706 | "DotnetRestore" ?=>! "UpdateChangelog" 707 | "UpdateChangelog" ?=>! "GenerateAssemblyInfo" 708 | "UpdateChangelog" ==>! "PublishToNuGet" 709 | 710 | "BuildDocs" ==>! "ReleaseDocs" 711 | "BuildDocs" ?=>! "PublishToNuget" 712 | "DotnetPack" ?=>! "BuildDocs" 713 | "GenerateCoverageReport" ?=>! "ReleaseDocs" 714 | 715 | 716 | "DotnetRestore" 717 | ==> "CheckFormatCode" 718 | ==> "DotnetBuild" 719 | // ==> "FSharpAnalyzers" 720 | ==> "DotnetTest" 721 | =?> ("GenerateCoverageReport", not disableCodeCoverage) 722 | // ==> "DotnetPack" 723 | // ==> "SourceLinkTest" 724 | // ==> "PublishToNuGet" 725 | // ==> "GitRelease" 726 | // ==> "GitHubRelease" 727 | ==>! "Release" 728 | 729 | "DotnetRestore" 730 | ==>! "WatchTests" 731 | 732 | //----------------------------------------------------------------------------- 733 | // Target Start 734 | //----------------------------------------------------------------------------- 735 | [] 736 | let main argv = 737 | argv 738 | |> Array.toList 739 | |> Context.FakeExecutionContext.Create false "build.fsx" 740 | |> Context.RuntimeContext.Fake 741 | |> Context.setExecutionContext 742 | initTargets () 743 | Target.runOrDefaultWithArguments "DotnetTest" 744 | 745 | 0 // return an integer exit code 746 | 747 | -------------------------------------------------------------------------------- /build/build.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net7.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 | -------------------------------------------------------------------------------- /docs/coverage/FsOpenTelemetry_Say.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FsOpenTelemetry.Say - Coverage Report 8 | 9 |
10 |

< Summary

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Class:FsOpenTelemetry.Say
Assembly:FsOpenTelemetry
File(s):/Users/jimmybyrd/Documents/repos/public/TheAngryByrd/FsOpenTelemetry/src/FsOpenTelemetry/Library.fs
Covered lines:4
Uncovered lines:0
Coverable lines:4
Total lines:53
Line coverage:100% (4 of 4)
Covered branches:0
Total branches:0
29 |

Metrics

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
helloPerson(...)10100%0%1
Invoke(...)10100%0%1
Invoke(...)10100%0%1
Invoke(...)10100%0%1
Invoke(...)10100%0%1
add(...)10100%0%1
nothing(...)10100%0%1
42 |

File(s)

43 |

/Users/jimmybyrd/Documents/repos/public/TheAngryByrd/FsOpenTelemetry/src/FsOpenTelemetry/Library.fs

44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
#LineLine coverage
 1namespace FsOpenTelemetry
 2
 3open System
 4open System.Security.Cryptography
 5open System.Text
 6
 7/// <summary> Initial module </summary>
 8module Say =
 9
 10    /// <summary> Finite list of Colors </summary>
 11    type FavoriteColor =
 12        | Red
 13        | Yellow
 14        | Blue
 15
 16    /// <summary> A person with many different field types </summary>
 17    type Person =
 18        { Name: string
 19          FavoriteNumber: int
 20          FavoriteColor: FavoriteColor
 21          DateOfBirth: DateTimeOffset }
 22
 23    /// <summary>Says hello to a specific person</summary>
 24    let helloPerson (person: Person) =
 525        sprintf
 526            "Hello %s. You were born on %s and your favorite number is %d. You like %A."
 27            person.Name
 28            (person.DateOfBirth.ToString("yyyy/MM/dd"))
 29            person.FavoriteNumber
 30            person.FavoriteColor
 31
 32    /// <summary>
 33    /// Adds two integers <paramref name="a"/> and <paramref name="b"/> and returns the result.
 34    /// </summary>
 35    ///
 36    /// <remarks>
 37    /// This usually contains some really important information that you'll miss if you don't read the docs.
 38    /// </remarks>
 39    ///
 40    /// <param name="a">An integer.</param>
 41    /// <param name="b">An integer.</param>
 42    ///
 43    /// <returns>
 44    /// The sum of two integers.
 45    /// </returns>
 46    ///
 47    /// <exceptions cref="M:System.OverflowException">Thrown when one parameter is max
 48    /// and the other is greater than 0.</exceptions>
 149    let add a b = a + b
 50
 51
 52    /// I do nothing
 153    let nothing name = name |> ignore
102 |
103 |
114 | 115 | -------------------------------------------------------------------------------- /docs/coverage/icon_cube.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_down-dir_active.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_fork.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_info-circled.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_minus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_plus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_search-minus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_search-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_up-dir.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_up-dir_active.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/icon_wrench.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/coverage/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Summary - Coverage Report 8 | 9 |
10 |

Summary

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
Generated on:11/24/2021 - 7:16:56 PM
Parser:OpenCoverParser
Assemblies:1
Classes:1
Files:1
Covered lines:4
Uncovered lines:0
Coverable lines:4
Total lines:53
Line coverage:100% (4 of 4)
Covered branches:0
Total branches:0
31 |

Risk Hotspots

32 | 33 | 34 |

No risk hotspots found.

35 |

Coverage

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
NameCoveredUncoveredCoverableTotalLine coverageBranch coverage
FsOpenTelemetry40453100%
 
 
FsOpenTelemetry.Say40453100%
 
 
55 |
56 |
57 | 58 | -------------------------------------------------------------------------------- /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 FsOpenTelemetry 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 FsOpenTelemetry 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 | # FsOpenTelemetry 2 | 3 | --- 4 | 5 | ## What is FsOpenTelemetry? 6 | 7 | FsOpenTelemetry is a library that does this specific thing. 8 | 9 | ## Why use FsOpenTelemetry? 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 "net5.0" 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 FsOpenTelemetry, 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": "7.0.200" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /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.11.1 8 | nuget Microsoft.NET.Test.Sdk 16.8.0 9 | nuget altcover ~> 7 10 | 11 | // [ FAKE GROUP ] 12 | group Build 13 | storage: none 14 | source https://www.nuget.org/api/v2 15 | source https://api.nuget.org/v3/index.json 16 | nuget Fake.IO.FileSystem 5.20.4 17 | nuget Fake.Core.Target 5.20.4 18 | nuget Fake.Core.ReleaseNotes 5.20.4 19 | nuget FAKE.Core.Environment 5.20.4 20 | nuget Fake.DotNet.Cli 5.20.4 21 | nuget FAKE.Core.Process 5.20.4 22 | nuget Fake.DotNet.AssemblyInfoFile 5.20.4 23 | nuget Fake.Tools.Git 5.20.4 24 | nuget Fake.DotNet.Paket 5.20.4 25 | nuget Fake.Api.GitHub 5.20.4 26 | nuget Fake.BuildServer.GitHubActions 5.20.4 27 | nuget Argu 28 | nuget Octokit 0.48 29 | nuget MSBuild.StructuredLogger 30 | 31 | group Docs 32 | storage: none 33 | source https://www.nuget.org/api/v2 34 | source https://api.nuget.org/v3/index.json 35 | nuget Argu 36 | nuget FSharp.Compiler.Service 34.1.0 37 | nuget FSharp.Core ~> 4.6 38 | nuget Fake.IO.FileSystem 5.20.4 39 | nuget FAKE.Core.Environment 5.20.4 40 | nuget Fake.DotNet.Cli 5.20.4 41 | nuget FSharp.Formatting 4.0.0-rc1 42 | nuget FSharp.Literate 4.0.0-rc1 43 | nuget Fable.React 44 | 45 | group Analyzers 46 | source https://www.nuget.org/api/v2 47 | source https://api.nuget.org/v3/index.json 48 | nuget BinaryDefense.FSharp.Analyzers.Hashing 0.2.2 49 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | true 6 | false 7 | README.md 8 | 9 | true 10 | true 11 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/FsOpenTelemetry/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 = "FsOpenTelemetry" 17 | let [] AssemblyProduct = "FsOpenTelemetry" 18 | let [] AssemblyVersion = "0.1.0" 19 | let [] AssemblyMetadata_ReleaseDate = "2017-03-17T00:00:00.0000000" 20 | let [] AssemblyFileVersion = "0.1.0" 21 | let [] AssemblyInformationalVersion = "0.1.0" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "bb8964b54bee133e9af64d316dc2cfee16df7f72" 24 | -------------------------------------------------------------------------------- /src/FsOpenTelemetry/FsOpenTelemetry.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | 6 | 7 | FsOpenTelemetry 8 | FsOpenTelemetry does the thing! 9 | 10 | 11 | 12 | true 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/FsOpenTelemetry/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | Microsoft.SourceLink.GitHub 3 | 4 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/FsOpenTelemetry.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 = "FsOpenTelemetry.Tests" 17 | let [] AssemblyProduct = "FsOpenTelemetry" 18 | let [] AssemblyVersion = "0.1.0" 19 | let [] AssemblyMetadata_ReleaseDate = "2017-03-17T00:00:00.0000000" 20 | let [] AssemblyFileVersion = "0.1.0" 21 | let [] AssemblyInformationalVersion = "0.1.0" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "bb8964b54bee133e9af64d316dc2cfee16df7f72" 24 | -------------------------------------------------------------------------------- /tests/FsOpenTelemetry.Tests/FsOpenTelemetry.Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net7.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/FsOpenTelemetry.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | namespace FsOpenTelemetry.Tests 2 | 3 | module ExpectoTemplate = 4 | 5 | open Expecto 6 | 7 | [] 8 | let main argv = 9 | Tests.runTestsInAssembly defaultConfig argv 10 | -------------------------------------------------------------------------------- /tests/FsOpenTelemetry.Tests/TestClass.fs: -------------------------------------------------------------------------------- 1 | namespace SomeTestClasses 2 | 3 | open System.Diagnostics 4 | open FsOpenTelemetry 5 | open FsOpenTelemetry.Tests 6 | 7 | type TestClass() = 8 | let typ = typeof 9 | 10 | // let rec activity () = 11 | // Tracing.activitySource.StartActivityForQuote(<@ activity @>) 12 | 13 | member x.GetForName() = 14 | Tracing.activitySource.StartActivityExt("MyCustomName") 15 | 16 | member x.GetForNameCustomNamespace() = 17 | let customNamespace = "MyNamespace" 18 | Tracing.activitySource.StartActivityExt("MyCustomName", name_space = customNamespace) 19 | 20 | member x.GetForType() = 21 | Tracing.activitySource.StartActivityForType(typ) 22 | 23 | member x.GetForTypeArg() = 24 | Tracing.activitySource.StartActivityForType() 25 | 26 | // don't 27 | member x.GetForQuote() : Activity = null 28 | 29 | member x.GetForFunc() = 30 | Tracing.activitySource.StartActivityForFunc() 31 | 32 | member x.GetForFuncAsync() = async { return Tracing.activitySource.StartActivityForFunc() } 33 | 34 | module TestModule = 35 | type Marker = 36 | class 37 | end 38 | 39 | let typ = typeof 40 | 41 | // let inline activity () = 42 | // Tracing.activitySource.StartActivityForQuote(<@ activity @>) 43 | 44 | let getForName () = 45 | Tracing.activitySource.StartActivityExt("MyCustomName") 46 | 47 | let getForNameCustomNamespace () = 48 | let customNamespace = "MyNamespace" 49 | Tracing.activitySource.StartActivityExt("MyCustomName", name_space = customNamespace) 50 | 51 | let getForType () = 52 | Tracing.activitySource.StartActivityForType(typ) 53 | 54 | let getForTypeArg () = 55 | Tracing.activitySource.StartActivityForType() 56 | 57 | // // don't 58 | let getForQuote () : Activity = null 59 | // activity () 60 | 61 | let getForFunc () = 62 | Tracing.activitySource.StartActivityForFunc() 63 | 64 | 65 | let getForFuncAsync () = async { return Tracing.activitySource.StartActivityForFunc() } 66 | -------------------------------------------------------------------------------- /tests/FsOpenTelemetry.Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | namespace FsOpenTelemetry.Tests 2 | 3 | open System 4 | open Expecto 5 | open FsOpenTelemetry 6 | 7 | module Expect = 8 | open System.Diagnostics 9 | 10 | let expectDisplayName (activity: Activity) expected = 11 | Expect.equal activity.DisplayName expected "Incorrect DisplayName" 12 | 13 | let sourceCodeTags expectedNamespace expectedFilePath expectedFunction (activity: Activity) = 14 | let code_namespace = 15 | activity.GetTagItem SemanticConventions.General.SourceCode.code_namespace 16 | |> string 17 | 18 | let code_filepath = 19 | activity.GetTagItem SemanticConventions.General.SourceCode.code_filepath 20 | |> string 21 | 22 | let code_lineno = 23 | activity.GetTagItem SemanticConventions.General.SourceCode.code_lineno 24 | |> string 25 | |> Int32.Parse 26 | 27 | let code_function = 28 | activity.GetTagItem SemanticConventions.General.SourceCode.code_function 29 | |> string 30 | 31 | Expect.equal code_namespace expectedNamespace "Incorrect namespace" 32 | Expect.stringContains code_filepath expectedFilePath "Incorrect filepath" 33 | Expect.isGreaterThan code_lineno -1 "" // Assume parsing and greater than 0 is good enough, otherwise too easy to break tests with moving code around 34 | Expect.equal code_function expectedFunction "Incorrect function" 35 | 36 | 37 | module SayTests = 38 | [] 39 | let tests = 40 | testList "Tests" [ 41 | testList "Classes" [ 42 | testCase "GetForName" 43 | <| fun () -> 44 | let testClass = SomeTestClasses.TestClass() 45 | let activity = testClass.GetForName() 46 | Expect.expectDisplayName activity "MyCustomName" 47 | 48 | let ns = 49 | // "SomeTestClasses.TestClass" // ? Figure out why this doesn't give clean namespaces 50 | ".$TestClass" 51 | 52 | Expect.sourceCodeTags ns "TestClass.fs" "GetForName" activity 53 | 54 | testCase "GetForNameCustomNamespace" 55 | <| fun () -> 56 | let testClass = SomeTestClasses.TestClass() 57 | let activity = testClass.GetForNameCustomNamespace() 58 | Expect.expectDisplayName activity "MyCustomName" 59 | 60 | Expect.sourceCodeTags 61 | "MyNamespace" 62 | "TestClass.fs" 63 | "GetForNameCustomNamespace" 64 | activity 65 | 66 | testCase "GetForType" 67 | <| fun () -> 68 | let testClass = SomeTestClasses.TestClass() 69 | let activity = testClass.GetForType() 70 | 71 | Expect.expectDisplayName activity "SomeTestClasses.TestClass.GetForType" 72 | 73 | Expect.sourceCodeTags 74 | "SomeTestClasses.TestClass" 75 | "TestClass.fs" 76 | "GetForType" 77 | activity 78 | 79 | testCase "GetForTypeArg" 80 | <| fun () -> 81 | let testClass = SomeTestClasses.TestClass() 82 | let activity = testClass.GetForTypeArg() 83 | 84 | Expect.expectDisplayName activity "SomeTestClasses.TestClass.GetForTypeArg" 85 | 86 | Expect.sourceCodeTags 87 | "SomeTestClasses.TestClass" 88 | "TestClass.fs" 89 | "GetForTypeArg" 90 | activity 91 | 92 | ptestCase "GetForQuote" 93 | <| fun () -> 94 | let testClass = SomeTestClasses.TestClass() 95 | let activity = testClass.GetForQuote() 96 | 97 | Expect.expectDisplayName activity "SomeTestClasses.TestClass.GetForTypeArg" 98 | 99 | Expect.sourceCodeTags 100 | "SomeTestClasses.TestClass" 101 | "TestClass.fs" 102 | "GetForTypeArg" 103 | activity 104 | 105 | testCase "GetForFunc" 106 | <| fun () -> 107 | let testClass = SomeTestClasses.TestClass() 108 | let activity = testClass.GetForFunc() 109 | 110 | Expect.expectDisplayName 111 | activity 112 | ".$TestClass.GetForFunc" 113 | 114 | Expect.sourceCodeTags 115 | ".$TestClass" 116 | "TestClass.fs" 117 | "GetForFunc" 118 | activity 119 | 120 | testCaseAsync "GetForFuncAsync" 121 | <| async { 122 | let testClass = SomeTestClasses.TestClass() 123 | let! activity = testClass.GetForFuncAsync() 124 | 125 | Expect.expectDisplayName 126 | activity 127 | ".$TestClass.GetForFuncAsync" 128 | 129 | Expect.sourceCodeTags 130 | ".$TestClass" 131 | "TestClass.fs" 132 | "GetForFuncAsync" 133 | activity 134 | } 135 | ] 136 | 137 | testList "Module" [ 138 | testCase "GetForName" 139 | <| fun () -> 140 | let activity = SomeTestClasses.TestModule.getForName () 141 | Expect.expectDisplayName activity "MyCustomName" 142 | 143 | let ns = "SomeTestClasses.TestModule" 144 | 145 | Expect.sourceCodeTags ns "TestClass.fs" "getForName" activity 146 | 147 | testCase "GetForNameCustomNamespace" 148 | <| fun () -> 149 | let activity = SomeTestClasses.TestModule.getForNameCustomNamespace () 150 | Expect.expectDisplayName activity "MyCustomName" 151 | 152 | Expect.sourceCodeTags 153 | "MyNamespace" 154 | "TestClass.fs" 155 | "getForNameCustomNamespace" 156 | activity 157 | 158 | testCase "GetForType" 159 | <| fun () -> 160 | let activity = SomeTestClasses.TestModule.getForType () 161 | 162 | Expect.expectDisplayName activity "SomeTestClasses.TestModule+Marker.getForType" 163 | 164 | Expect.sourceCodeTags 165 | "SomeTestClasses.TestModule+Marker" 166 | "TestClass.fs" 167 | "getForType" 168 | activity 169 | 170 | testCase "GetForTypeArg" 171 | <| fun () -> 172 | let activity = SomeTestClasses.TestModule.getForTypeArg () 173 | 174 | Expect.expectDisplayName 175 | activity 176 | "SomeTestClasses.TestModule+Marker.getForTypeArg" 177 | 178 | Expect.sourceCodeTags 179 | "SomeTestClasses.TestModule+Marker" 180 | "TestClass.fs" 181 | "getForTypeArg" 182 | activity 183 | 184 | ptestCase "GetForQuote" 185 | <| fun () -> 186 | let activity = SomeTestClasses.TestModule.getForQuote () 187 | 188 | Expect.expectDisplayName activity "SomeTestClasses.TestClass.GetForTypeArg" 189 | 190 | Expect.sourceCodeTags 191 | "SomeTestClasses.TestClass" 192 | "TestClass.fs" 193 | "GetForTypeArg" 194 | activity 195 | 196 | testCase "GetForFunc" 197 | <| fun () -> 198 | let activity = SomeTestClasses.TestModule.getForFunc () 199 | 200 | Expect.expectDisplayName activity "SomeTestClasses.TestModule.getForFunc" 201 | 202 | Expect.sourceCodeTags 203 | "SomeTestClasses.TestModule" 204 | "TestClass.fs" 205 | "getForFunc" 206 | activity 207 | testCaseAsync "GetForFuncAsync" 208 | <| async { 209 | let! activity = SomeTestClasses.TestModule.getForFuncAsync () 210 | 211 | Expect.expectDisplayName activity "SomeTestClasses.TestModule.getForFuncAsync" 212 | 213 | Expect.sourceCodeTags 214 | "SomeTestClasses.TestModule" 215 | "TestClass.fs" 216 | "getForFuncAsync" 217 | activity 218 | } 219 | ] 220 | ] 221 | -------------------------------------------------------------------------------- /tests/FsOpenTelemetry.Tests/TestsActivitySource.fs: -------------------------------------------------------------------------------- 1 | namespace FsOpenTelemetry.Tests 2 | 3 | module rec Tracing = 4 | open System.Diagnostics 5 | 6 | type internal Marker = 7 | interface 8 | end 9 | 10 | let assemblyName = typeof.Assembly.GetName() 11 | 12 | let activitySource = 13 | new ActivitySource(assemblyName.Name, assemblyName.Version.ToString()) 14 | 15 | let activityListener = 16 | new ActivityListener( 17 | ShouldListenTo = (fun s -> true), 18 | SampleUsingParentId = (fun _ -> ActivitySamplingResult.AllData), 19 | Sample = (fun _ -> ActivitySamplingResult.AllData) 20 | ) 21 | 22 | ActivitySource.AddActivityListener(activityListener) 23 | -------------------------------------------------------------------------------- /tests/FsOpenTelemetry.Tests/paket.references: -------------------------------------------------------------------------------- 1 | Expecto 2 | FSharp.Core 3 | Microsoft.NET.Test.Sdk 4 | YoloDev.Expecto.TestSdk 5 | altcover 6 | --------------------------------------------------------------------------------