├── .github ├── FUNDING.yml └── workflows │ ├── BuildDeployAbiesWebAssembly.yml │ └── CreateRelease.yml ├── .gitignore ├── CHANGELOG.md ├── Generate-ReleaseNotes.cmd ├── Handlebars.Net.Helpers.sln ├── Handlebars.Net.Helpers.sln.DotSettings ├── LICENSE ├── README.md ├── azure-pipeline-ci.yml ├── azure-pipelines-nuget.yml ├── examples ├── AbiesWebAssembly │ ├── AbiesWebAssembly.csproj │ ├── Fluent.cs │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── launchSettings.json │ └── wwwroot │ │ ├── abies.js │ │ ├── css │ │ ├── app.css │ │ └── bootstrap │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ ├── favicon.png │ │ ├── icon-192.png │ │ └── index.html ├── BlazorAppWebAssembly │ ├── App.razor │ ├── BlazorAppWebAssembly.csproj │ ├── Layout │ │ ├── MainLayout.razor │ │ ├── MainLayout.razor.css │ │ ├── NavMenu.razor │ │ └── NavMenu.razor.css │ ├── Pages │ │ ├── Counter.razor │ │ ├── Home.razor │ │ └── Weather.razor │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── _Imports.razor │ └── wwwroot │ │ ├── css │ │ ├── app.css │ │ └── bootstrap │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ ├── favicon.png │ │ ├── icon-192.png │ │ ├── index.html │ │ └── sample-data │ │ └── weather.json └── ConsoleApp │ ├── ConsoleApp.csproj │ └── Program.cs ├── resources ├── hbnet-icon.png ├── hbnet-icon_32x32.png ├── myget-add.png ├── myget-use.png └── nuget-settings.png ├── src ├── Directory.Build.props ├── Handlebars.Net.Helpers.Core │ ├── Attributes │ │ └── HandlebarsWriterAttribute.cs │ ├── Enums │ │ ├── Category.cs │ │ ├── HelperUsage.cs │ │ └── WriterType.cs │ ├── Extensions │ │ └── ParameterInfoExtensions.cs │ ├── GlobalUsings.cs │ ├── Handlebars.Net.Helpers.Core.csproj │ ├── Helpers │ │ ├── BaseHelpers.cs │ │ └── IHelpers.cs │ ├── IO │ │ └── PassthroughTextEncoder.cs │ ├── Json │ │ ├── DataContractJsonSerializerStrategy.cs │ │ ├── IJsonSerializerStrategy.cs │ │ ├── JsonArray.cs │ │ ├── JsonObject.cs │ │ ├── PocoJsonSerializerStrategy.cs │ │ ├── ReflectionUtils.cs │ │ └── SimpleJson.cs │ ├── Options │ │ ├── HandlebarsDynamicLinqHelperOptions.cs │ │ └── HandlebarsHelpersOptions.cs │ ├── Parsers │ │ ├── ArgumentsParser.cs │ │ └── StringValueParser.cs │ └── Utils │ │ ├── ArrayUtils.cs │ │ ├── DateTimeService.cs │ │ ├── DefaultValueCache.cs │ │ ├── EnumUtils.cs │ │ ├── IDateTimeService.cs │ │ └── ObjectUtils.cs ├── Handlebars.Net.Helpers.DynamicLinq │ ├── Handlebars.Net.Helpers.DynamicLinq.csproj │ ├── Helpers │ │ └── DynamicLinqHelpers.cs │ ├── JArrayMerger.cs │ ├── JObjectExtensions.cs │ ├── Models │ │ ├── DynamicJsonClassOptions.cs │ │ ├── FloatBehavior.cs │ │ └── IntegerBehavior.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Handlebars.Net.Helpers.Humanizer │ ├── Handlebars.Net.Helpers.Humanizer.csproj │ ├── Helpers │ │ └── HumanizerHelpers.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Handlebars.Net.Helpers.Json │ ├── Handlebars.Net.Helpers.Json.csproj │ ├── Helpers │ │ └── JsonPathHelpers.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── Utils │ │ └── JsonUtils.cs ├── Handlebars.Net.Helpers.Random │ ├── Handlebars.Net.Helpers.Random.csproj │ ├── Helpers │ │ └── RandomHelpers.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Handlebars.Net.Helpers.XPath │ ├── Handlebars.Net.Helpers.XPath.csproj │ ├── Helpers │ │ └── XPathHelpers.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Handlebars.Net.Helpers.Xeger │ ├── Handlebars.Net.Helpers.Xeger.csproj │ ├── Helpers │ │ └── XegerHelpers.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Handlebars.Net.Helpers.Xslt │ ├── Handlebars.Net.Helpers.Xslt.csproj │ ├── Helpers │ │ └── XsltHelpers.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Handlebars.Net.Helpers.snk ├── Handlebars.Net.Helpers │ ├── Compatibility │ │ └── AppContextHelper.cs │ ├── Extensions │ │ ├── IHandlebarsExtensions.cs │ │ └── MethodBaseExtensions.cs │ ├── Handlebars.Net.Helpers.csproj │ ├── HandlebarsHelpers.cs │ ├── Helpers │ │ ├── BooleanHelpers.cs │ │ ├── ConstantsHelpers.cs │ │ ├── DateTimeHelpers.cs │ │ ├── EnumerableHelpers.cs │ │ ├── EnvironmentHelpers.cs │ │ ├── EvaluateHelper.cs │ │ ├── MathHelpers.cs │ │ ├── ObjectHelpers.cs │ │ ├── RegexHelpers.cs │ │ ├── StringHelpers.cs │ │ └── UrlHelpers.cs │ ├── Models │ │ ├── EvaluateResult.cs │ │ └── WrappedString.cs │ ├── Plugin │ │ └── PluginLoader.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── Utils │ │ ├── ExecuteUtils.cs │ │ ├── HtmlUtils.cs │ │ ├── ReflectionUtils.cs │ │ ├── RegexUtils.cs │ │ └── RuntimeInformationUtils.cs └── sign.txt └── test └── Handlebars.Net.Helpers.Tests ├── DynamicLinq └── JArrayMergerTests.cs ├── Handlebars.Net.Helpers.Tests.csproj ├── Helpers ├── BooleanHelpersTests.cs ├── DateTimeHelpersTests.cs ├── EnumerableHelpersTests.cs ├── EnvironmentHelpersTests.cs ├── ExecuteUtilsTests.cs ├── HumanizerHelpersTests.cs ├── MathHelpersTests.cs ├── ObjectHelpersTests.cs ├── Parsers │ └── StringValueParserTests.cs ├── RandomHelpersTests.cs ├── StringHelpersTests.cs ├── UrlHelpersTests.cs ├── XPathHelpersTests.cs ├── XegerHelpersTests.cs └── XsltHelpersTests.cs ├── IO └── PassthroughTextEncoderTests.cs ├── Models └── WrappedStringTests.cs ├── Templates ├── BooleanHelpersTemplateTests.cs ├── ConstantsHelpersTemplateTests.cs ├── DateTimeHelpersTemplateTests.cs ├── DynamicLinqHelpersTemplateTests.cs ├── EnumerableHelpersTemplateTests.cs ├── EnvironmentHelpersTemplateTests.cs ├── EvaluateHelperTemplateTests.cs ├── HumanizerHelpersTemplateTests.cs ├── JsonPathHelpersTemplateTests.cs ├── MathHelpersTemplateTests.cs ├── ObjectHelpersTemplateTests.cs ├── PathHelpersTemplateTests.cs ├── RandomHelpersTemplateTests.cs ├── RegexHelpersTemplateTests.cs ├── StringHelpersTemplateTests.cs ├── XPathHelpersTemplateTests.cs └── XsltHelpersTemplateTests.cs └── Utils └── ArrayUtilsTests.cs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [StefH] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.paypal.me/stefheyenrath 13 | -------------------------------------------------------------------------------- /.github/workflows/BuildDeployAbiesWebAssembly.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy AbiesWebAssembly 2 | 3 | on: 4 | push: 5 | branches: 6 | - example-Abies 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout Code 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v4 18 | with: 19 | dotnet-version: '9.0.x' 20 | 21 | - name: Restore Dependencies 22 | run: dotnet restore ./examples/AbiesWebAssembly/AbiesWebAssembly.csproj 23 | 24 | - name: Build Project 25 | run: dotnet publish ./examples/AbiesWebAssembly/AbiesWebAssembly.csproj -c Release -o build 26 | 27 | - name: Deploy to GitHub Pages 28 | uses: peaceiris/actions-gh-pages@v4 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | publish_dir: ./build/wwwroot 32 | # destination_dir: Handlebars.Net.Helpers -------------------------------------------------------------------------------- /.github/workflows/CreateRelease.yml: -------------------------------------------------------------------------------- 1 | name: CreateRelease 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*.*.*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | - name: Release 16 | uses: softprops/action-gh-release@v2 17 | with: 18 | generate_release_notes: true 19 | -------------------------------------------------------------------------------- /Generate-ReleaseNotes.cmd: -------------------------------------------------------------------------------- 1 | rem https://github.com/StefH/GitHubReleaseNotes 2 | 3 | SET version=2.5.2 4 | 5 | GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid documentation duplicate --version %version% --token %GH_TOKEN% -------------------------------------------------------------------------------- /Handlebars.Net.Helpers.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | WASM 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Stef Heyenrath 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 | -------------------------------------------------------------------------------- /azure-pipeline-ci.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | vmImage: 'windows-2022' 3 | 4 | variables: 5 | Prerelease: 'ci' 6 | buildId: "1$(Build.BuildId)" 7 | buildProjects: '**/src/**/Handlebars.Net*.csproj' 8 | 9 | steps: 10 | # Print buildId 11 | - script: | 12 | echo "BuildId = $(buildId)" 13 | displayName: 'Print buildId' 14 | 15 | - task: PowerShell@2 16 | displayName: "Use JDK11 by default" 17 | inputs: 18 | targetType: 'inline' 19 | script: | 20 | $jdkPath = $env:JAVA_HOME_11_X64 21 | Write-Host "##vso[task.setvariable variable=JAVA_HOME]$jdkPath" 22 | 23 | - task: SonarCloudPrepare@1 24 | displayName: 'Prepare analysis on SonarCloud' 25 | condition: and(succeeded(), eq(variables['RUN_SONAR'], 'yes'), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 26 | inputs: 27 | SonarCloud: SonarCloud 28 | organization: stefh-github 29 | projectKey: 'Handlebars.Net.Helpers' 30 | projectName: 'Handlebars.Net.Helpers' 31 | extraProperties: | 32 | sonar.cs.opencover.reportsPaths=**/coverage.net6.0.opencover.xml 33 | 34 | # Build source, tests and run tests for net452 and net6.0 (with coverage) 35 | - script: | 36 | dotnet test ./test/Handlebars.Net.Helpers.Tests/Handlebars.Net.Helpers.Tests.csproj --configuration Debug --framework net452 37 | dotnet test ./test/Handlebars.Net.Helpers.Tests/Handlebars.Net.Helpers.Tests.csproj --configuration Debug --framework net6.0 --logger trx /p:CollectCoverage=true /p:CoverletOutputFormat=opencover 38 | displayName: 'Build source, tests and run tests for net452 and net6.0 (with coverage)' 39 | 40 | - task: SonarCloudAnalyze@1 41 | displayName: 'SonarCloud: Run Code Analysis' 42 | condition: and(succeeded(), eq(variables['RUN_SONAR'], 'yes'), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 43 | 44 | - task: SonarCloudPublish@1 45 | displayName: 'SonarCloud: Publish Quality Gate Result' 46 | condition: and(succeeded(), eq(variables['RUN_SONAR'], 'yes'), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 47 | 48 | - task: whitesource.ws-bolt.bolt.wss.WhiteSource Bolt@19 49 | displayName: 'WhiteSource Bolt' 50 | condition: and(succeeded(), eq(variables['RUN_WHITESOURCE'], 'yes'), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 51 | 52 | # Upload coverage to codecov.io 53 | - script: | 54 | %USERPROFILE%\.nuget\packages\codecov\1.10.0\tools\codecov.exe -f "./test/Handlebars.Net.Helpers.Tests/coverage.net6.0.opencover.xml" -t $(CODECOV_TOKEN) 55 | displayName: Upload coverage to codecov.io 56 | 57 | # https://github.com/microsoft/azure-pipelines-tasks/issues/12212 58 | - task: PublishTestResults@2 59 | condition: and(succeeded(), eq(variables['PUBLISH_TESTRESULTS'], 'yes')) 60 | inputs: 61 | testRunner: VSTest 62 | testResultsFiles: '**/*.trx' 63 | 64 | # Based on https://whereslou.com/2018/09/versioning-and-publishing-nuget-packages-automatically-using-azure-devops-pipelines/ 65 | - task: DotNetCoreCLI@2 66 | displayName: Build Release 67 | inputs: 68 | command: 'build' 69 | arguments: /p:Configuration=Release # https://github.com/MicrosoftDocs/vsts-docs/issues/1976 70 | projects: $(buildProjects) 71 | 72 | - task: DotNetCoreCLI@2 73 | displayName: Pack 74 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 75 | inputs: 76 | command: pack 77 | configuration: 'Release' 78 | packagesToPack: $(buildProjects) 79 | nobuild: true 80 | packDirectory: '$(Build.ArtifactStagingDirectory)/packages' 81 | verbosityPack: 'normal' 82 | 83 | - task: PublishBuildArtifacts@1 84 | displayName: Publish Artifacts 85 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 86 | inputs: 87 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 88 | 89 | # https://github.com/NuGet/Home/issues/8148 90 | - task: DotNetCoreCLI@2 91 | displayName: Push to MyGet 92 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 93 | inputs: 94 | command: custom 95 | custom: nuget 96 | arguments: push $(Build.ArtifactStagingDirectory)\packages\*.nupkg -n -s https://www.myget.org/F/handlebars_net_helpers/api/v3/index.json -k $(MyGetKey) -------------------------------------------------------------------------------- /azure-pipelines-nuget.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | vmImage: 'windows-2022' 3 | 4 | variables: 5 | Prerelease: '' 6 | buildId: "1$(Build.BuildId)" 7 | buildProjects: '**/src/**/Handlebars.Net.Helpers*.csproj' 8 | 9 | steps: 10 | # Print buildId 11 | - script: | 12 | echo "BuildId = $(buildId)" 13 | displayName: 'Print buildId' 14 | 15 | # Based on https://whereslou.com/2018/09/versioning-and-publishing-nuget-packages-automatically-using-azure-devops-pipelines/ 16 | - task: DotNetCoreCLI@2 17 | displayName: Build Release 18 | inputs: 19 | command: 'build' 20 | arguments: /p:Configuration=Release # https://github.com/MicrosoftDocs/vsts-docs/issues/1976 21 | projects: $(buildProjects) 22 | 23 | - task: DotNetCoreCLI@2 24 | displayName: Pack 25 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 26 | inputs: 27 | command: pack 28 | configuration: 'Release' 29 | packagesToPack: $(buildProjects) 30 | nobuild: true 31 | packDirectory: '$(Build.ArtifactStagingDirectory)/packages' 32 | verbosityPack: 'normal' 33 | 34 | - task: PublishBuildArtifacts@1 35 | displayName: Publish Artifacts 36 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 37 | inputs: 38 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 39 | 40 | - task: DotNetCoreCLI@2 41 | displayName: Push to NuGet 42 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests 43 | inputs: 44 | command: custom 45 | custom: nuget 46 | arguments: push $(Build.ArtifactStagingDirectory)\packages\*.nupkg -n -s https://api.nuget.org/v3/index.json -k $(NuGetKey) 47 | -------------------------------------------------------------------------------- /examples/AbiesWebAssembly/AbiesWebAssembly.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | true 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/AbiesWebAssembly/Fluent.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Abies.DOM; 3 | using static Abies.Html.Elements; 4 | 5 | namespace AbiesWebAssembly; 6 | 7 | public static class Fluent 8 | { 9 | public static Element button(Attribute[] attributes, Element[] children, [CallerLineNumber] int id = 0) 10 | => element("fluent-button", attributes, children, id); 11 | } -------------------------------------------------------------------------------- /examples/AbiesWebAssembly/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | [assembly:System.Runtime.Versioning.SupportedOSPlatform("browser")] 5 | -------------------------------------------------------------------------------- /examples/AbiesWebAssembly/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AbiesWebAssembly": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://127.0.0.1:58287/", 10 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /examples/AbiesWebAssembly/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | h1:focus { 6 | outline: none; 7 | } 8 | 9 | a, .btn-link { 10 | color: #0071c1; 11 | } 12 | 13 | .btn-primary { 14 | color: #fff; 15 | background-color: #1b6ec2; 16 | border-color: #1861ac; 17 | } 18 | 19 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 20 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 21 | } 22 | 23 | .content { 24 | padding-top: 1.1rem; 25 | } 26 | 27 | .valid.modified:not([type=checkbox]) { 28 | outline: 1px solid #26b050; 29 | } 30 | 31 | .invalid { 32 | outline: 1px solid red; 33 | } 34 | 35 | .validation-message { 36 | color: red; 37 | } 38 | 39 | #blazor-error-ui { 40 | background: lightyellow; 41 | bottom: 0; 42 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 43 | display: none; 44 | left: 0; 45 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 46 | position: fixed; 47 | width: 100%; 48 | z-index: 1000; 49 | } 50 | 51 | #blazor-error-ui .dismiss { 52 | cursor: pointer; 53 | position: absolute; 54 | right: 0.75rem; 55 | top: 0.5rem; 56 | } 57 | 58 | .blazor-error-boundary { 59 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 60 | padding: 1rem 1rem 1rem 3.7rem; 61 | color: white; 62 | } 63 | 64 | .blazor-error-boundary::after { 65 | content: "An error has occurred." 66 | } 67 | 68 | .loading-progress { 69 | position: relative; 70 | display: block; 71 | width: 8rem; 72 | height: 8rem; 73 | margin: 20vh auto 1rem auto; 74 | } 75 | 76 | .loading-progress circle { 77 | fill: none; 78 | stroke: #e0e0e0; 79 | stroke-width: 0.6rem; 80 | transform-origin: 50% 50%; 81 | transform: rotate(-90deg); 82 | } 83 | 84 | .loading-progress circle:last-child { 85 | stroke: #1b6ec2; 86 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 87 | transition: stroke-dasharray 0.05s ease-in-out; 88 | } 89 | 90 | .loading-progress-text { 91 | position: absolute; 92 | text-align: center; 93 | font-weight: bold; 94 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 95 | } 96 | 97 | .loading-progress-text:after { 98 | content: var(--blazor-load-percentage-text, "Loading"); 99 | } 100 | 101 | code { 102 | color: #c02d76; 103 | } 104 | -------------------------------------------------------------------------------- /examples/AbiesWebAssembly/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/examples/AbiesWebAssembly/wwwroot/favicon.png -------------------------------------------------------------------------------- /examples/AbiesWebAssembly/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/examples/AbiesWebAssembly/wwwroot/icon-192.png -------------------------------------------------------------------------------- /examples/AbiesWebAssembly/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HandlebarsNetTestApp 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/BlazorAppWebAssembly.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 |
3 | 6 | 7 |
8 |
9 | About 10 |
11 | 12 |
13 | @Body 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row { 41 | justify-content: space-between; 42 | } 43 | 44 | .top-row ::deep a, .top-row ::deep .btn-link { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | @media (min-width: 641px) { 50 | .page { 51 | flex-direction: row; 52 | } 53 | 54 | .sidebar { 55 | width: 250px; 56 | height: 100vh; 57 | position: sticky; 58 | top: 0; 59 | } 60 | 61 | .top-row { 62 | position: sticky; 63 | top: 0; 64 | z-index: 1; 65 | } 66 | 67 | .top-row.auth ::deep a:first-child { 68 | flex: 1; 69 | text-align: right; 70 | width: 0; 71 | } 72 | 73 | .top-row, article { 74 | padding-left: 2rem !important; 75 | padding-right: 1.5rem !important; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/Layout/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  9 | 10 | 29 | 30 | @code { 31 | private bool collapseNavMenu = true; 32 | 33 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 34 | 35 | private void ToggleNavMenu() 36 | { 37 | collapseNavMenu = !collapseNavMenu; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/Layout/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .bi { 15 | display: inline-block; 16 | position: relative; 17 | width: 1.25rem; 18 | height: 1.25rem; 19 | margin-right: 0.75rem; 20 | top: -1px; 21 | background-size: cover; 22 | } 23 | 24 | .bi-house-door-fill-nav-menu { 25 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); 26 | } 27 | 28 | .bi-plus-square-fill-nav-menu { 29 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); 30 | } 31 | 32 | .bi-list-nested-nav-menu { 33 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); 34 | } 35 | 36 | .nav-item { 37 | font-size: 0.9rem; 38 | padding-bottom: 0.5rem; 39 | } 40 | 41 | .nav-item:first-of-type { 42 | padding-top: 1rem; 43 | } 44 | 45 | .nav-item:last-of-type { 46 | padding-bottom: 1rem; 47 | } 48 | 49 | .nav-item ::deep a { 50 | color: #d7d7d7; 51 | border-radius: 4px; 52 | height: 3rem; 53 | display: flex; 54 | align-items: center; 55 | line-height: 3rem; 56 | } 57 | 58 | .nav-item ::deep a.active { 59 | background-color: rgba(255,255,255,0.37); 60 | color: white; 61 | } 62 | 63 | .nav-item ::deep a:hover { 64 | background-color: rgba(255,255,255,0.1); 65 | color: white; 66 | } 67 | 68 | @media (min-width: 641px) { 69 | .navbar-toggler { 70 | display: none; 71 | } 72 | 73 | .collapse { 74 | /* Never collapse the sidebar for wide screens */ 75 | display: block; 76 | } 77 | 78 | .nav-scrollable { 79 | /* Allow sidebar to scroll for tall menus */ 80 | height: calc(100vh - 3.5rem); 81 | overflow-y: auto; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | 3 | Counter 4 | 5 |

Counter

6 | 7 |

Current count: @currentCount

8 | 9 | 10 | 11 | @code { 12 | private int currentCount = 0; 13 | 14 | private void IncrementCount() 15 | { 16 | currentCount++; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/Program.cs: -------------------------------------------------------------------------------- 1 | using BlazorAppWebAssembly; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 4 | 5 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 6 | builder.RootComponents.Add("#app"); 7 | builder.RootComponents.Add("head::after"); 8 | 9 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 10 | 11 | await builder.Build().RunAsync(); 12 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:32881", 8 | "sslPort": 44390 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 17 | "applicationUrl": "http://localhost:5196", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 27 | "applicationUrl": "https://localhost:7087;http://localhost:5196", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using BlazorAppWebAssembly 10 | @using BlazorAppWebAssembly.Layout 11 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | h1:focus { 6 | outline: none; 7 | } 8 | 9 | a, .btn-link { 10 | color: #0071c1; 11 | } 12 | 13 | .btn-primary { 14 | color: #fff; 15 | background-color: #1b6ec2; 16 | border-color: #1861ac; 17 | } 18 | 19 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 20 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 21 | } 22 | 23 | .content { 24 | padding-top: 1.1rem; 25 | } 26 | 27 | .valid.modified:not([type=checkbox]) { 28 | outline: 1px solid #26b050; 29 | } 30 | 31 | .invalid { 32 | outline: 1px solid red; 33 | } 34 | 35 | .validation-message { 36 | color: red; 37 | } 38 | 39 | #blazor-error-ui { 40 | background: lightyellow; 41 | bottom: 0; 42 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 43 | display: none; 44 | left: 0; 45 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 46 | position: fixed; 47 | width: 100%; 48 | z-index: 1000; 49 | } 50 | 51 | #blazor-error-ui .dismiss { 52 | cursor: pointer; 53 | position: absolute; 54 | right: 0.75rem; 55 | top: 0.5rem; 56 | } 57 | 58 | .blazor-error-boundary { 59 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 60 | padding: 1rem 1rem 1rem 3.7rem; 61 | color: white; 62 | } 63 | 64 | .blazor-error-boundary::after { 65 | content: "An error has occurred." 66 | } 67 | 68 | .loading-progress { 69 | position: relative; 70 | display: block; 71 | width: 8rem; 72 | height: 8rem; 73 | margin: 20vh auto 1rem auto; 74 | } 75 | 76 | .loading-progress circle { 77 | fill: none; 78 | stroke: #e0e0e0; 79 | stroke-width: 0.6rem; 80 | transform-origin: 50% 50%; 81 | transform: rotate(-90deg); 82 | } 83 | 84 | .loading-progress circle:last-child { 85 | stroke: #1b6ec2; 86 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 87 | transition: stroke-dasharray 0.05s ease-in-out; 88 | } 89 | 90 | .loading-progress-text { 91 | position: absolute; 92 | text-align: center; 93 | font-weight: bold; 94 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 95 | } 96 | 97 | .loading-progress-text:after { 98 | content: var(--blazor-load-percentage-text, "Loading"); 99 | } 100 | 101 | code { 102 | color: #c02d76; 103 | } 104 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/examples/BlazorAppWebAssembly/wwwroot/favicon.png -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/examples/BlazorAppWebAssembly/wwwroot/icon-192.png -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BlazorAppWebAssembly 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 | An unhandled error has occurred. 26 | Reload 27 | 🗙 28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/BlazorAppWebAssembly/wwwroot/sample-data/weather.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "date": "2022-01-06", 4 | "temperatureC": 1, 5 | "summary": "Freezing" 6 | }, 7 | { 8 | "date": "2022-01-07", 9 | "temperatureC": 14, 10 | "summary": "Bracing" 11 | }, 12 | { 13 | "date": "2022-01-08", 14 | "temperatureC": -13, 15 | "summary": "Freezing" 16 | }, 17 | { 18 | "date": "2022-01-09", 19 | "temperatureC": -16, 20 | "summary": "Balmy" 21 | }, 22 | { 23 | "date": "2022-01-10", 24 | "temperatureC": -2, 25 | "summary": "Chilly" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /examples/ConsoleApp/ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /resources/hbnet-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/resources/hbnet-icon.png -------------------------------------------------------------------------------- /resources/hbnet-icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/resources/hbnet-icon_32x32.png -------------------------------------------------------------------------------- /resources/myget-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/resources/myget-add.png -------------------------------------------------------------------------------- /resources/myget-use.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/resources/myget-use.png -------------------------------------------------------------------------------- /resources/nuget-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/resources/nuget-settings.png -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MsBuildAllProjects);$(MsBuildThisFileFullPath) 4 | 5 | 6 | 7 | net451;net452;net46;netstandard1.3;netstandard2.0;netstandard2.1;net6.0;net8.0 8 | 2.5.2 9 | 12 10 | enable 11 | Copyright © 2020-2025 Stef Heyenrath 12 | Stef Heyenrath 13 | See CHANGELOG.md 14 | https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/master/resources/hbnet-icon.png 15 | https://github.com/Handlebars-Net/Handlebars.Net.Helpers 16 | MIT 17 | git 18 | https://github.com/Handlebars-Net/Handlebars.Net.Helpers 19 | 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | $(Prerelease)-1$(BUILD_BUILDID) 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Attributes/HandlebarsWriterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HandlebarsDotNet.Helpers.Enums; 3 | 4 | namespace HandlebarsDotNet.Helpers.Attributes; 5 | 6 | [AttributeUsage(AttributeTargets.Method)] 7 | public class HandlebarsWriterAttribute : Attribute 8 | { 9 | public WriterType Type { get; } 10 | 11 | public string? Name { get; set; } 12 | 13 | public HelperUsage Usage { get; set; } 14 | 15 | public bool PassContext { get; set; } 16 | 17 | public HandlebarsWriterAttribute(WriterType type, string? name = null) : this(type, HelperUsage.Both, name) 18 | { 19 | } 20 | 21 | public HandlebarsWriterAttribute(WriterType type, HelperUsage usage, string? name = null) : this(type, usage, false, name) 22 | { 23 | } 24 | 25 | public HandlebarsWriterAttribute(WriterType type, HelperUsage usage, bool passContext, string? name = null) 26 | { 27 | Type = type; 28 | Name = name; 29 | Usage = usage; 30 | PassContext = passContext; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Enums/Category.cs: -------------------------------------------------------------------------------- 1 | namespace HandlebarsDotNet.Helpers.Enums; 2 | 3 | public enum Category 4 | { 5 | Constants, 6 | Enumerable, 7 | Environment, 8 | Math, 9 | Regex, 10 | String, 11 | Url, 12 | DateTime, 13 | XPath, 14 | Xeger, 15 | Random, 16 | JsonPath, 17 | DynamicLinq, 18 | Humanizer, 19 | Boolean, 20 | Object, 21 | Xslt, 22 | Custom // Custom helpers. Not used by the library itself. 23 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Enums/HelperUsage.cs: -------------------------------------------------------------------------------- 1 | namespace HandlebarsDotNet.Helpers.Enums; 2 | 3 | public enum HelperUsage 4 | { 5 | Both, 6 | Normal, 7 | Block 8 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Enums/WriterType.cs: -------------------------------------------------------------------------------- 1 | namespace HandlebarsDotNet.Helpers.Enums; 2 | 3 | public enum WriterType 4 | { 5 | Value, 6 | 7 | String 8 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Extensions/ParameterInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace HandlebarsDotNet.Helpers.Extensions; 4 | 5 | public static class ParameterInfoExtensions 6 | { 7 | public static bool IsParam(this ParameterInfo? parameterInfo) 8 | { 9 | return parameterInfo?.IsDefined(typeof(ParamArrayAttribute)) == true; 10 | } 11 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using global::System; 2 | global using global::System.IO; 3 | #if !(NET35 || NET40) 4 | global using global::System.Threading; 5 | global using global::System.Threading.Tasks; 6 | #endif -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Handlebars.Net.Helpers.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | HandlebarsDotNet.Helpers 5 | HandlebarsDotNet.Helpers.Core 6 | Core functionality for Handlebars.Net.Helpers 7 | Handlebars.Net.Helpers.Core 8 | handlebars;helpers;extensions;core;models;interfaces;common 9 | handlebars;helper;models;interfaces 10 | {7CBD113C-A754-480B-B0D0-AA9DF734F612} 11 | true 12 | ../Handlebars.Net.Helpers.snk 13 | $(DefineConstants);SIMPLE_JSON_INTERNAL 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | $(DefineConstants);SIMPLE_JSON_DATACONTRACT;SIMPLE_JSON_DYNAMIC 27 | 28 | 29 | 30 | $(DefineConstants);SIMPLE_JSON_TYPEINFO 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Helpers/BaseHelpers.cs: -------------------------------------------------------------------------------- 1 | using HandlebarsDotNet.Helpers.Options; 2 | using Stef.Validation; 3 | 4 | namespace HandlebarsDotNet.Helpers.Helpers; 5 | 6 | public abstract class BaseHelpers(IHandlebars context, HandlebarsHelpersOptions options) 7 | { 8 | protected readonly IHandlebars Context = Guard.NotNull(context); 9 | protected readonly HandlebarsHelpersOptions Options = Guard.NotNull(options); 10 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Helpers/IHelpers.cs: -------------------------------------------------------------------------------- 1 | using HandlebarsDotNet.Helpers.Enums; 2 | 3 | namespace HandlebarsDotNet.Helpers.Helpers; 4 | 5 | public interface IHelpers 6 | { 7 | Category Category { get; } 8 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/IO/PassthroughTextEncoder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | 4 | namespace HandlebarsDotNet.Helpers.IO; 5 | 6 | /// 7 | /// A text encoder that implements and directly passes through the input text to the target without any encoding. 8 | /// 9 | public class PassthroughTextEncoder : ITextEncoder 10 | { 11 | /// 12 | /// Writes the given StringBuilder text directly to the target TextWriter. 13 | /// 14 | /// The StringBuilder containing the text. 15 | /// The TextWriter to write the text to. 16 | public void Encode(StringBuilder text, TextWriter target) 17 | { 18 | target.Write(text); 19 | } 20 | 21 | /// 22 | /// Writes the given string text directly to the target TextWriter. 23 | /// 24 | /// The string containing the text. 25 | /// The TextWriter to write the text to. 26 | public void Encode(string text, TextWriter target) 27 | { 28 | target.Write(text); 29 | } 30 | 31 | /// 32 | /// Writes the given text enumerator directly to the target TextWriter. 33 | /// 34 | /// The type of the text enumerator. 35 | /// The enumerator containing the text. 36 | /// The TextWriter to write the text to. 37 | public void Encode(T text, TextWriter target) where T : IEnumerator 38 | { 39 | while (text.MoveNext()) 40 | { 41 | target.Write(text.Current); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Json/DataContractJsonSerializerStrategy.cs: -------------------------------------------------------------------------------- 1 | using System.CodeDom.Compiler; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Runtime.Serialization; 5 | 6 | namespace HandlebarsDotNet.Helpers.Json; 7 | 8 | #if SIMPLE_JSON_DATACONTRACT 9 | [GeneratedCode("simple-json", "1.0.0")] 10 | #if SIMPLE_JSON_INTERNAL 11 | internal 12 | #else 13 | public 14 | #endif 15 | class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy 16 | { 17 | public DataContractJsonSerializerStrategy() 18 | { 19 | GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); 20 | SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); 21 | } 22 | 23 | internal override IDictionary GetterValueFactory(Type type) 24 | { 25 | bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; 26 | if (!hasDataContract) 27 | { 28 | return base.GetterValueFactory(type); 29 | } 30 | 31 | string jsonKey; 32 | IDictionary result = new Dictionary(); 33 | foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) 34 | { 35 | if (propertyInfo.CanRead) 36 | { 37 | MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); 38 | if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) 39 | result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo); 40 | } 41 | } 42 | foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) 43 | { 44 | if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) 45 | result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo); 46 | } 47 | return result; 48 | } 49 | 50 | internal override IDictionary> SetterValueFactory(Type type) 51 | { 52 | bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; 53 | if (!hasDataContract) 54 | return base.SetterValueFactory(type); 55 | string jsonKey; 56 | IDictionary> result = new Dictionary>(); 57 | foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) 58 | { 59 | if (propertyInfo.CanWrite) 60 | { 61 | MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); 62 | if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) 63 | result[jsonKey] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); 64 | } 65 | } 66 | foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) 67 | { 68 | if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) 69 | result[jsonKey] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); 70 | } 71 | // todo implement sorting for DATACONTRACT. 72 | return result; 73 | } 74 | 75 | private static bool CanAdd(MemberInfo info, out string jsonKey) 76 | { 77 | jsonKey = null; 78 | if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null) 79 | return false; 80 | DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute)); 81 | if (dataMemberAttribute == null) 82 | return false; 83 | jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name; 84 | return true; 85 | } 86 | } 87 | 88 | #endif -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Json/IJsonSerializerStrategy.cs: -------------------------------------------------------------------------------- 1 | using System.CodeDom.Compiler; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace HandlebarsDotNet.Helpers.Json; 5 | 6 | [GeneratedCode("simple-json", "1.0.0")] 7 | #if SIMPLE_JSON_INTERNAL 8 | internal 9 | #else 10 | public 11 | #endif 12 | interface IJsonSerializerStrategy 13 | { 14 | [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")] 15 | bool TrySerializeNonPrimitiveObject(object? input, [NotNullWhen(true)] out object? output); 16 | 17 | object? DeserializeObject(object? value, Type type); 18 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Json/JsonArray.cs: -------------------------------------------------------------------------------- 1 | using System.CodeDom.Compiler; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | 5 | namespace HandlebarsDotNet.Helpers.Json; 6 | 7 | /// 8 | /// Represents the json array. 9 | /// 10 | [GeneratedCode("simple-json", "1.0.0")] 11 | [EditorBrowsable(EditorBrowsableState.Never)] 12 | #if SIMPLE_JSON_OBJARRAYINTERNAL 13 | internal 14 | #else 15 | public 16 | #endif 17 | class JsonArray : List 18 | { 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | public JsonArray() { } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The capacity of the json array. 28 | public JsonArray(int capacity) : base(capacity) { } 29 | 30 | /// 31 | /// The json representation of the array. 32 | /// 33 | /// The json representation of the array. 34 | public override string ToString() 35 | { 36 | return SimpleJson.SerializeObject(this) ?? string.Empty; 37 | } 38 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Options/HandlebarsDynamicLinqHelperOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HandlebarsDotNet.Helpers.Options; 2 | 3 | /// 4 | /// Additional options for Handlebars.Net.Helpers.DynamicLinq. 5 | /// 6 | public class HandlebarsDynamicLinqHelperOptions 7 | { 8 | /// 9 | /// When set to true, the DynamicLinq helper will allow the use of the Equals(object obj), Equals(object objA, object objB), ReferenceEquals(object objA, object objB) and ToString() methods on the type. 10 | /// 11 | /// Default value is false. 12 | /// 13 | public bool AllowEqualsAndToStringMethodsOnObject { get; set; } 14 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Options/HandlebarsHelpersOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using HandlebarsDotNet.Helpers.Enums; 3 | using HandlebarsDotNet.Helpers.Helpers; 4 | using HandlebarsDotNet.Helpers.Utils; 5 | 6 | namespace HandlebarsDotNet.Helpers.Options; 7 | 8 | /// 9 | /// The additional options 10 | /// 11 | public class HandlebarsHelpersOptions 12 | { 13 | private const string Dot = "."; 14 | 15 | /// 16 | /// An array of the default allowed HandlebarsHelpers. 17 | /// 18 | public static readonly Category[] DefaultAllowedHandlebarsHelpers = 19 | [ 20 | Category.Boolean, 21 | Category.Constants, 22 | Category.DateTime, 23 | Category.Enumerable, 24 | Category.Humanizer, 25 | Category.JsonPath, 26 | Category.Math, 27 | Category.Object, 28 | Category.Random, 29 | Category.Regex, 30 | Category.String, 31 | Category.Url, 32 | Category.Xeger, 33 | Category.XPath, 34 | Category.Xslt 35 | ]; 36 | 37 | /// 38 | /// Default options. 39 | /// 40 | public static HandlebarsHelpersOptions Default => new(); 41 | 42 | /// 43 | /// Set to false if you don't want to add the prefix from the category to the helper name. (Default is set to true). 44 | /// 45 | public bool UseCategoryPrefix { get; set; } = true; 46 | 47 | /// 48 | /// Define a custom separator when is set to . (Default value is a dot '.'). 49 | /// 50 | public string PrefixSeparator { get; set; } = Dot; 51 | 52 | /// 53 | /// Defines if a dot '.' is used as PrefixSeparator. 54 | /// 55 | public bool PrefixSeparatorIsDot => PrefixSeparator == Dot; 56 | 57 | /// 58 | /// Define a custom prefix which will be added before of the helper name. 59 | /// 60 | public string? Prefix { get; set; } 61 | 62 | /// 63 | /// The categories to register. 64 | /// 65 | /// By default, all categories except and are registered. See the WIKI for details. 66 | /// 67 | public Category[] Categories { get; set; } = DefaultAllowedHandlebarsHelpers; 68 | 69 | /// 70 | /// The additional helpers to register. 71 | /// 72 | public IHelpers[]? Helpers { get; set; } 73 | 74 | /// 75 | /// Used for unit-testing DateTime related functionality. 76 | /// 77 | public IDateTimeService? DateTimeService { get; set; } 78 | 79 | /// 80 | /// A Dictionary with additional Custom Helpers (Key = CategoryPrefix, Value = IHelpers) 81 | /// 82 | public IDictionary? CustomHelpers { get; set; } 83 | 84 | /// 85 | /// The paths to search for additional helpers. If null, the CurrentDirectory and BaseDirectory are used. 86 | /// 87 | public IReadOnlyList? CustomHelperPaths; 88 | 89 | /// 90 | /// Additional options for Handlebars.Net.Helpers.DynamicLinq. 91 | /// 92 | public HandlebarsDynamicLinqHelperOptions? DynamicLinqHelperOptions { get; set; } 93 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Parsers/ArgumentsParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Linq; 4 | using System.Reflection; 5 | using HandlebarsDotNet.Helpers.Extensions; 6 | using HandlebarsDotNet.Helpers.Utils; 7 | 8 | namespace HandlebarsDotNet.Helpers.Parsers; 9 | 10 | public static class ArgumentsParser 11 | { 12 | private static readonly Type[] SupportedTypes = { typeof(int), typeof(long), typeof(double) }; 13 | 14 | public static List Parse(IHandlebars context, ParameterInfo[] parameters, Arguments arguments) 15 | { 16 | var result = new List<(object? Argument, object? DefaultValue)>(); 17 | 18 | for (int i = 0; i < parameters.Length; i++) 19 | { 20 | if (parameters[i].IsParam()) 21 | { 22 | result.Add((arguments.Skip(i).ToArray(), DefaultValueCache.GetDefaultValue(parameters[i].ParameterType))); 23 | } 24 | else if (i < arguments.Length) 25 | { 26 | var defaultValue = parameters[i].HasDefaultValue ? parameters[i].DefaultValue : DefaultValueCache.GetDefaultValue(parameters[i].ParameterType); 27 | result.Add((arguments[i], defaultValue)); 28 | } 29 | } 30 | 31 | return result.Select(x => Parse(context, x.Argument, x.DefaultValue)).ToList(); 32 | } 33 | 34 | public static object? Parse(IHandlebars context, object? argument, object? defaultValue, bool convertObjectArrayToStringList = false) 35 | { 36 | if (argument is UndefinedBindingResult argumentAsUndefinedBindingResult) 37 | { 38 | if (TryParseUndefinedBindingResult(argumentAsUndefinedBindingResult, out var parsedAsObjectList)) 39 | { 40 | if (convertObjectArrayToStringList) 41 | { 42 | return parsedAsObjectList.Cast().ToList(); 43 | } 44 | 45 | return parsedAsObjectList; 46 | } 47 | 48 | // In case it's an UndefinedBindingResult and ThrowOnUnresolvedBindingExpression is set to false, return the default value. 49 | if (!context.Configuration.ThrowOnUnresolvedBindingExpression) 50 | { 51 | return defaultValue; 52 | } 53 | } 54 | 55 | return argument; 56 | } 57 | 58 | public static object ParseAsIntLongOrDouble(IHandlebars context, object? value) 59 | { 60 | // In case the value is null and ThrowOnUnresolvedBindingExpression is set to false, return the default value (0). 61 | if (value is null && !context.Configuration.ThrowOnUnresolvedBindingExpression) 62 | { 63 | return 0; 64 | } 65 | 66 | var parsedValue = StringValueParser.Parse(context, value as string ?? value?.ToString() ?? string.Empty); 67 | 68 | if (SupportedTypes.Contains(parsedValue.GetType())) 69 | { 70 | return parsedValue; 71 | } 72 | 73 | throw new NotSupportedException($"The value '{value}' cannot not be converted to an int, long or double."); 74 | } 75 | 76 | /// 77 | /// In case it's an UndefinedBindingResult, just try to convert the value using Json. 78 | /// This logic adds functionality like parsing a list. 79 | /// 80 | /// The property value 81 | /// The parsed value 82 | /// true in case parsing is ok, else false 83 | private static bool TryParseUndefinedBindingResult(UndefinedBindingResult undefinedBindingResult, [NotNullWhen(true)] out List? parsedValue) 84 | { 85 | return ArrayUtils.TryParseAsObjectList(undefinedBindingResult.Value, out parsedValue); 86 | } 87 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Parsers/StringValueParser.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace HandlebarsDotNet.Helpers.Parsers; 4 | 5 | public static class StringValueParser 6 | { 7 | public static object Parse(IHandlebars context, string valueAsString) 8 | { 9 | if (int.TryParse(valueAsString, NumberStyles.Any, context.Configuration.FormatProvider, out var valueAsInt)) 10 | { 11 | return valueAsInt; 12 | } 13 | 14 | if (long.TryParse(valueAsString, NumberStyles.Any, context.Configuration.FormatProvider, out var valueAsLong)) 15 | { 16 | return valueAsLong; 17 | } 18 | 19 | if (double.TryParse(valueAsString, NumberStyles.Any, context.Configuration.FormatProvider, out var valueAsDouble)) 20 | { 21 | return valueAsDouble; 22 | } 23 | 24 | return valueAsString; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Utils/ArrayUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Linq; 4 | using HandlebarsDotNet.Helpers.Json; 5 | 6 | namespace HandlebarsDotNet.Helpers.Utils; 7 | 8 | public static class ArrayUtils 9 | { 10 | public static string ToArray(IEnumerable array) 11 | { 12 | return SimpleJson.SerializeObject(Fix(array))!; 13 | } 14 | 15 | public static bool TryParseAsObjectList(string value, [NotNullWhen(true)] out List? list) 16 | { 17 | if (SimpleJson.TryDeserializeObject(value, out var obj)) 18 | { 19 | list = (List?)obj!; 20 | return true; 21 | } 22 | 23 | list = null; 24 | return false; 25 | } 26 | 27 | private static IEnumerable Fix(IEnumerable array) 28 | { 29 | return array.Select(a => a is char ? a.ToString() : a); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Utils/DateTimeService.cs: -------------------------------------------------------------------------------- 1 | namespace HandlebarsDotNet.Helpers.Utils; 2 | 3 | public class DateTimeService : IDateTimeService 4 | { 5 | public DateTime Now() 6 | { 7 | return DateTime.Now; 8 | } 9 | 10 | public DateTime UtcNow() 11 | { 12 | return DateTime.UtcNow; 13 | } 14 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Utils/DefaultValueCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Reflection; 3 | 4 | namespace HandlebarsDotNet.Helpers.Utils; 5 | 6 | internal static class DefaultValueCache 7 | { 8 | private static readonly ConcurrentDictionary Cache = new(); 9 | 10 | public static object? GetDefaultValue(Type type) 11 | { 12 | // Try to get the value from the cache 13 | if (!Cache.TryGetValue(type, out var value)) 14 | { 15 | // Value not found in cache, compute and add to cache 16 | value = CreateDefaultValue(type); 17 | Cache.TryAdd(type, value); 18 | } 19 | 20 | return value; 21 | } 22 | 23 | private static object? CreateDefaultValue(Type type) 24 | { 25 | // Uses reflection to create a generic method call 26 | return typeof(DefaultValueCreator<>) 27 | .MakeGenericType(type) 28 | .GetMethod("GetDefault")! 29 | .Invoke(null, null); 30 | } 31 | 32 | private static class DefaultValueCreator 33 | { 34 | public static T? GetDefault() => default(T); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Utils/EnumUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace HandlebarsDotNet.Helpers.Utils; 4 | 5 | public static class EnumUtils 6 | { 7 | public static bool TryParse(object? value, [NotNullWhen(true)] out TEnum? comparisonType) where TEnum : struct, Enum 8 | { 9 | switch (value) 10 | { 11 | case TEnum valueAsEnum: 12 | comparisonType = valueAsEnum; 13 | return true; 14 | 15 | case int valueAsInt when Enum.IsDefined(typeof(TEnum), valueAsInt): 16 | comparisonType = IntToEnum(valueAsInt); 17 | return true; 18 | 19 | case string stringValue when Enum.TryParse(stringValue, out var parsedAsEnum): 20 | comparisonType = parsedAsEnum; 21 | return true; 22 | 23 | case string stringValue when int.TryParse(stringValue, out var parsedAsInt) && Enum.IsDefined(typeof(TEnum), parsedAsInt): 24 | comparisonType = IntToEnum(parsedAsInt); 25 | return true; 26 | 27 | default: 28 | comparisonType = default; 29 | return false; 30 | } 31 | } 32 | 33 | public static TEnum IntToEnum(int value) where TEnum : struct, Enum 34 | => (TEnum)Enum.ToObject(typeof(TEnum), value); 35 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Utils/IDateTimeService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HandlebarsDotNet.Helpers.Utils 4 | { 5 | public interface IDateTimeService 6 | { 7 | DateTime Now(); 8 | 9 | DateTime UtcNow(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Core/Utils/ObjectUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | using HandlebarsDotNet.Helpers.Json; 4 | 5 | namespace HandlebarsDotNet.Helpers.Utils; 6 | 7 | public static class ObjectUtils 8 | { 9 | public static bool TryParseAsDictionary(object? data, [NotNullWhen(true)] out IDictionary? dictionary) 10 | { 11 | try 12 | { 13 | dictionary = SimpleJson.DeserializeObject>(data?.ToString()); 14 | return dictionary != null; 15 | } 16 | catch 17 | { 18 | dictionary = default; 19 | return false; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.DynamicLinq/Handlebars.Net.Helpers.DynamicLinq.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | HandlebarsDotNet.Helpers 5 | HandlebarsDotNet.Helpers.DynamicLinq 6 | Handlebars.Net.Helpers.DynamicLinq 7 | Handlebars.Net.Helpers DynamicLinq 8 | handlebars;helper;Linq;DynamicLinq 9 | {181B9A96-5662-BBAA-5678-BB8E1263125A} 10 | true 11 | ../Handlebars.Net.Helpers.snk 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.DynamicLinq/Models/DynamicJsonClassOptions.cs: -------------------------------------------------------------------------------- 1 | // Copied from https://github.com/StefH/JsonConverter 2 | 3 | namespace HandlebarsDotNet.Helpers.Models; 4 | 5 | internal class DynamicJsonClassOptions 6 | { 7 | public IntegerBehavior IntegerConvertBehavior { get; set; } = IntegerBehavior.UseLong; 8 | 9 | public FloatBehavior FloatConvertBehavior { get; set; } = FloatBehavior.UseDouble; 10 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.DynamicLinq/Models/FloatBehavior.cs: -------------------------------------------------------------------------------- 1 | // Copied from https://github.com/StefH/JsonConverter 2 | 3 | namespace HandlebarsDotNet.Helpers.Models; 4 | 5 | /// 6 | /// Enum to define how to convert a Float in the Json Object. 7 | /// 8 | internal enum FloatBehavior 9 | { 10 | /// 11 | /// Convert all Float types in the Json Object to a double. (default) 12 | /// 13 | UseDouble = 0, 14 | 15 | /// 16 | /// Convert all Float types in the Json Object to a float (unless overflow). 17 | /// 18 | UseFloat = 1, 19 | 20 | /// 21 | /// Convert all Float types in the Json Object to a decimal (unless overflow). 22 | /// 23 | UseDecimal = 2 24 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.DynamicLinq/Models/IntegerBehavior.cs: -------------------------------------------------------------------------------- 1 | // Copied from https://github.com/StefH/JsonConverter 2 | 3 | namespace HandlebarsDotNet.Helpers.Models; 4 | 5 | /// 6 | /// Enum to define how to convert an Integer in the Json Object. 7 | /// 8 | internal enum IntegerBehavior 9 | { 10 | /// 11 | /// Convert all Integer types in the Json Object to an int (unless overflow). 12 | /// (default) 13 | /// 14 | UseInt = 0, 15 | 16 | /// 17 | /// Convert all Integer types in the Json Object to a long. 18 | /// 19 | UseLong = 1 20 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.DynamicLinq/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("HandlebarsDotNet.Helpers.Tests, Publickey=00240000048000009400000006020000002400005253413100040000010001007da523c2b43238a93bfd7eb8229c12178f96e9bbb1a62914e099a6f52c3aed51dd089764716b31f9274823058e300dc1210f015e21019cf76cf7229d6f2de38aa56479077fd5bd011e9e8cbc3420bb05d3d9a4633e0125a41dea59a3aea9fbbb568197a46ddceb3cb9e496d9e95a9bc1f465750d348610d331d9aae8d5f553ca")] 4 | 5 | // Needed for Moq in the UnitTest project 6 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Humanizer/Handlebars.Net.Helpers.Humanizer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | HandlebarsDotNet.Helpers 5 | HandlebarsDotNet.Helpers.Humanizer 6 | Handlebars.Net.Helpers.Humanizer 7 | Handlebars.Net.Helpers Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities. 8 | handlebars;helper;Humanizer 9 | {181B9A96-5662-44AA-8685-E68E12631A55} 10 | true 11 | ../Handlebars.Net.Helpers.snk 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Humanizer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("HandlebarsDotNet.Helpers.Tests, Publickey=00240000048000009400000006020000002400005253413100040000010001007da523c2b43238a93bfd7eb8229c12178f96e9bbb1a62914e099a6f52c3aed51dd089764716b31f9274823058e300dc1210f015e21019cf76cf7229d6f2de38aa56479077fd5bd011e9e8cbc3420bb05d3d9a4633e0125a41dea59a3aea9fbbb568197a46ddceb3cb9e496d9e95a9bc1f465750d348610d331d9aae8d5f553ca")] 4 | 5 | // Needed for Moq in the UnitTest project 6 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Json/Handlebars.Net.Helpers.Json.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | HandlebarsDotNet.Helpers 5 | HandlebarsDotNet.Helpers.Json 6 | Handlebars.Net.Helpers.Json 7 | Handlebars.Net.Helpers Json 8 | handlebars;helper;JsonPath;Json 9 | {181B9A96-5662-44AA-8685-E68E1263125A} 10 | true 11 | ../Handlebars.Net.Helpers.snk 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Json/Helpers/JsonPathHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using HandlebarsDotNet.Helpers.Attributes; 6 | using HandlebarsDotNet.Helpers.Enums; 7 | using HandlebarsDotNet.Helpers.Helpers; 8 | using HandlebarsDotNet.Helpers.Options; 9 | using HandlebarsDotNet.Helpers.Utils; 10 | using Newtonsoft.Json; 11 | using Newtonsoft.Json.Linq; 12 | 13 | // ReSharper disable once CheckNamespace 14 | namespace HandlebarsDotNet.Helpers; 15 | 16 | public class JsonPathHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 17 | { 18 | [HandlebarsWriter(WriterType.String)] 19 | public string SelectToken(object value, string jsonPath) 20 | { 21 | var valueAsJToken = ParseAsJToken(value, nameof(SelectToken)); 22 | 23 | try 24 | { 25 | var result = valueAsJToken.SelectToken(jsonPath); 26 | return result switch 27 | { 28 | { } jTokenResult => jTokenResult.ToString(), 29 | _ => result!.ToString() // In case result is null, this will throw. 30 | }; 31 | } 32 | catch (JsonException ex) 33 | { 34 | throw new HandlebarsException(nameof(SelectToken), ex); 35 | } 36 | } 37 | 38 | [HandlebarsWriter(WriterType.Value)] 39 | public object SelectTokens(object value, string jsonPath) 40 | { 41 | return SelectTokensInternal(value, jsonPath); 42 | } 43 | 44 | [HandlebarsWriter(WriterType.Value, usage: HelperUsage.Block)] 45 | public object SelectTokens(bool _, object value, string jsonPath) 46 | { 47 | return SelectTokensInternal(value, jsonPath); 48 | } 49 | 50 | private object SelectTokensInternal(object value, string jsonPath) 51 | { 52 | var valueAsJToken = ParseAsJToken(value, nameof(SelectTokens)); 53 | 54 | try 55 | { 56 | var jTokens = valueAsJToken?.SelectTokens(jsonPath) ?? new List(); 57 | return jTokens.ToDictionary(jToken => jToken.Path, jToken => jToken); 58 | } 59 | catch (JsonException ex) 60 | { 61 | throw new HandlebarsException(nameof(SelectTokensInternal), ex); 62 | } 63 | } 64 | 65 | private static JToken ParseAsJToken(object? value, string methodName) 66 | { 67 | if (value is null) 68 | { 69 | throw new ArgumentNullException(nameof(value)); 70 | } 71 | 72 | return value switch 73 | { 74 | JToken tokenValue => tokenValue, 75 | string stringValue => JsonUtils.Parse(stringValue), 76 | IEnumerable enumerableValue => JArray.FromObject(enumerableValue), 77 | _ => throw new NotSupportedException($"The value '{value}' with type '{value?.GetType()}' cannot be used in Handlebars JsonPath {methodName}.") 78 | }; 79 | } 80 | 81 | public Category Category => Category.JsonPath; 82 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Json/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("HandlebarsDotNet.Helpers.Tests, Publickey=00240000048000009400000006020000002400005253413100040000010001007da523c2b43238a93bfd7eb8229c12178f96e9bbb1a62914e099a6f52c3aed51dd089764716b31f9274823058e300dc1210f015e21019cf76cf7229d6f2de38aa56479077fd5bd011e9e8cbc3420bb05d3d9a4633e0125a41dea59a3aea9fbbb568197a46ddceb3cb9e496d9e95a9bc1f465750d348610d331d9aae8d5f553ca")] 4 | 5 | // Needed for Moq in the UnitTest project 6 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Json/Utils/JsonUtils.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | 4 | namespace HandlebarsDotNet.Helpers.Utils; 5 | 6 | public static class JsonUtils 7 | { 8 | private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings 9 | { 10 | DateParseHandling = DateParseHandling.None 11 | }; 12 | 13 | /// 14 | /// Load a Newtonsoft.Json.Linq.JObject from a string that contains JSON. 15 | /// Using : DateParseHandling = DateParseHandling.None 16 | /// 17 | /// A System.String that contains JSON. 18 | /// A Newtonsoft.Json.Linq.JToken populated from the string that contains JSON. 19 | public static JToken Parse(string json) 20 | { 21 | return JsonConvert.DeserializeObject(json, JsonSerializerSettings)!; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Random/Handlebars.Net.Helpers.Random.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | HandlebarsDotNet.Helpers 5 | HandlebarsDotNet.Helpers.Random 6 | Handlebars.Net.Helpers.Random 7 | Handlebars.Net.Helpers Random 8 | handlebars;helper;Random 9 | {2A78B30D-D61C-4FEB-4567-2EC9AE84693B} 10 | true 11 | ../Handlebars.Net.Helpers.snk 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Random/Helpers/RandomHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using HandlebarsDotNet.Helpers.Attributes; 5 | using HandlebarsDotNet.Helpers.Enums; 6 | using HandlebarsDotNet.Helpers.Helpers; 7 | using HandlebarsDotNet.Helpers.Options; 8 | using HandlebarsDotNet.Helpers.Parsers; 9 | using RandomDataGenerator.FieldOptions; 10 | using RandomDataGenerator.Randomizers; 11 | 12 | // ReSharper disable once CheckNamespace 13 | namespace HandlebarsDotNet.Helpers; 14 | 15 | public class RandomHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 16 | { 17 | /// 18 | /// For backwards compatibility with WireMock.Net 19 | /// 20 | [HandlebarsWriter(WriterType.Value, "Random")] 21 | public object? Random(IDictionary hash) 22 | { 23 | return Generate(hash); 24 | } 25 | 26 | [HandlebarsWriter(WriterType.Value)] 27 | public object? Generate(IDictionary hash) 28 | { 29 | var fieldOptions = GetFieldOptionsFromHash(hash); 30 | dynamic randomizer = RandomizerFactory.GetRandomizerAsDynamic(fieldOptions); 31 | 32 | // Format DateTime as ISO 8601 33 | if (fieldOptions is IFieldOptionsDateTime) 34 | { 35 | DateTime? date = randomizer.Generate(); 36 | return date?.ToString("s", Context.Configuration.FormatProvider); 37 | } 38 | 39 | // If the IFieldOptionsGuid defines Uppercase, use the 'GenerateAsString' method. 40 | if (fieldOptions is IFieldOptionsGuid fieldOptionsGuid) 41 | { 42 | return fieldOptionsGuid.Uppercase ? randomizer.GenerateAsString() : randomizer.Generate(); 43 | } 44 | 45 | return randomizer.Generate(); 46 | } 47 | 48 | private FieldOptionsAbstract GetFieldOptionsFromHash(IDictionary hash) 49 | { 50 | if (hash.TryGetValue("Type", out var value) && value is string randomTypeAsString) 51 | { 52 | var newProperties = new Dictionary(); 53 | foreach (var item in hash.Where(p => p.Key != "Type")) 54 | { 55 | bool convertObjectArrayToStringList = randomTypeAsString == "StringList"; 56 | var parsedArgumentValue = ArgumentsParser.Parse(Context, item.Value, default(string), convertObjectArrayToStringList); 57 | 58 | newProperties.Add(item.Key, parsedArgumentValue); 59 | } 60 | 61 | return FieldOptionsFactory.GetFieldOptions(randomTypeAsString, newProperties!); 62 | } 63 | 64 | throw new HandlebarsException("The Type argument is missing."); 65 | } 66 | 67 | public Category Category => Category.Random; 68 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Random/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("HandlebarsDotNet.Helpers.Tests, Publickey=00240000048000009400000006020000002400005253413100040000010001007da523c2b43238a93bfd7eb8229c12178f96e9bbb1a62914e099a6f52c3aed51dd089764716b31f9274823058e300dc1210f015e21019cf76cf7229d6f2de38aa56479077fd5bd011e9e8cbc3420bb05d3d9a4633e0125a41dea59a3aea9fbbb568197a46ddceb3cb9e496d9e95a9bc1f465750d348610d331d9aae8d5f553ca")] 4 | 5 | // Needed for Moq in the UnitTest project 6 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.XPath/Handlebars.Net.Helpers.XPath.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | HandlebarsDotNet.Helpers 5 | HandlebarsDotNet.Helpers.XPath 6 | Handlebars.Net.Helpers.XPath 7 | Handlebars.Net.Helpers XPath 8 | handlebars;helper;xpath;xpath2 9 | {42D47A7E-EF66-477D-A524-45EEC130EA67} 10 | true 11 | ../Handlebars.Net.Helpers.snk 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.XPath/Helpers/XPathHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | using System.Xml; 6 | using System.Xml.XPath; 7 | using HandlebarsDotNet.Helpers.Attributes; 8 | using HandlebarsDotNet.Helpers.Enums; 9 | using HandlebarsDotNet.Helpers.Helpers; 10 | using HandlebarsDotNet.Helpers.Options; 11 | #if !NETSTANDARD1_3 12 | using Wmhelp.XPath2; 13 | #endif 14 | 15 | // ReSharper disable once CheckNamespace 16 | namespace HandlebarsDotNet.Helpers; 17 | 18 | public class XPathHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 19 | { 20 | private static readonly TimeSpan RegexTimeout = TimeSpan.FromSeconds(1); 21 | 22 | // Remove the "" from a XML document. 23 | // (https://github.com/WireMock-Net/WireMock.Net/issues/618) 24 | private static readonly Regex RemoveXmlVersionRegex = new(@"(<\?xml.*?\?>)", RegexOptions.Compiled, RegexTimeout); 25 | 26 | [HandlebarsWriter(WriterType.Value)] 27 | public XPathNavigator SelectNode(string document, string xpath) 28 | { 29 | return SelectSingleNode(document, xpath); 30 | } 31 | 32 | [HandlebarsWriter(WriterType.Value)] 33 | public XPathNavigator SelectSingleNode(string document, string xpath) 34 | { 35 | var nav = CreateNavigator(document); 36 | try 37 | { 38 | #if NETSTANDARD1_3 39 | return nav.SelectSingleNode(xpath); 40 | #else 41 | return nav.XPath2SelectSingleNode(xpath); 42 | #endif 43 | } 44 | catch (Exception ex) 45 | { 46 | throw new HandlebarsException(nameof(SelectSingleNode), ex); 47 | } 48 | } 49 | 50 | [HandlebarsWriter(WriterType.String)] 51 | public string SelectNodeAsXml(string document, string xpath) 52 | { 53 | return SelectSingleNode(document, xpath).OuterXml; 54 | } 55 | 56 | /// 57 | /// Added for backwards compatibility. 58 | /// This method just returns a concatenated string from all the string values. 59 | /// 60 | [HandlebarsWriter(WriterType.String)] 61 | public string SelectNodesAsString(string document, string xpath) 62 | { 63 | var listXPathNavigator = SelectNodes(document, xpath); 64 | 65 | var resultXml = new StringBuilder(); 66 | foreach (var node in listXPathNavigator) 67 | { 68 | resultXml.Append(node.Value); 69 | } 70 | 71 | return resultXml.ToString(); 72 | } 73 | 74 | [HandlebarsWriter(WriterType.String)] 75 | public string SelectNodesAsXml(string document, string xpath) 76 | { 77 | var listXPathNavigator = SelectNodes(document, xpath); 78 | 79 | var resultXml = new StringBuilder(); 80 | foreach (var node in listXPathNavigator) 81 | { 82 | resultXml.Append(node.OuterXml); 83 | } 84 | 85 | return resultXml.ToString(); 86 | } 87 | 88 | [HandlebarsWriter(WriterType.Value)] 89 | public IReadOnlyList SelectNodes(string document, string xpath) 90 | { 91 | var nav = CreateNavigator(document); 92 | try 93 | { 94 | #if NETSTANDARD1_3 95 | var result = nav.Select(xpath); 96 | #else 97 | var result = nav.XPath2SelectNodes(xpath); 98 | #endif 99 | var list = new List(); 100 | foreach (XPathNavigator node in result) 101 | { 102 | list.Add(node); 103 | } 104 | 105 | return list.AsReadOnly(); 106 | } 107 | catch (Exception ex) 108 | { 109 | throw new HandlebarsException(nameof(SelectNodes), ex); 110 | } 111 | } 112 | 113 | [HandlebarsWriter(WriterType.Value)] 114 | public object? Evaluate(string document, string xpath) 115 | { 116 | var nav = CreateNavigator(document); 117 | 118 | try 119 | { 120 | #if NETSTANDARD1_3 121 | return nav.Evaluate(xpath); 122 | #else 123 | return nav.XPath2Evaluate(xpath); 124 | #endif 125 | } 126 | catch (Exception ex) 127 | { 128 | throw new HandlebarsException(nameof(Evaluate), ex); 129 | } 130 | } 131 | 132 | [HandlebarsWriter(WriterType.String)] 133 | public string? EvaluateToString(string document, string xpath) 134 | { 135 | return Evaluate(document, xpath)?.ToString(); 136 | } 137 | 138 | private static XPathNavigator CreateNavigator(string document) 139 | { 140 | return new XmlDocument 141 | { 142 | InnerXml = RemoveXmlVersionRegex.Replace(document, string.Empty) 143 | }.CreateNavigator()!; 144 | } 145 | 146 | public Category Category => Category.XPath; 147 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.XPath/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("HandlebarsDotNet.Helpers.Tests, Publickey=00240000048000009400000006020000002400005253413100040000010001007da523c2b43238a93bfd7eb8229c12178f96e9bbb1a62914e099a6f52c3aed51dd089764716b31f9274823058e300dc1210f015e21019cf76cf7229d6f2de38aa56479077fd5bd011e9e8cbc3420bb05d3d9a4633e0125a41dea59a3aea9fbbb568197a46ddceb3cb9e496d9e95a9bc1f465750d348610d331d9aae8d5f553ca")] 4 | 5 | // Needed for Moq in the UnitTest project 6 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Xeger/Handlebars.Net.Helpers.Xeger.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | HandlebarsDotNet.Helpers 5 | HandlebarsDotNet.Helpers.Xeger 6 | Handlebars.Net.Helpers.Xeger 7 | Handlebars.Net.Helpers Xeger (Fare) 8 | handlebars;helper;Regex;Xeger;Fare 9 | {2278B30D-D61C-4FEB-8AEB-2EC9AE84693B} 10 | true 11 | ../Handlebars.Net.Helpers.snk 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Xeger/Helpers/XegerHelpers.cs: -------------------------------------------------------------------------------- 1 | using Fare; 2 | using HandlebarsDotNet.Helpers.Attributes; 3 | using HandlebarsDotNet.Helpers.Enums; 4 | using HandlebarsDotNet.Helpers.Helpers; 5 | using HandlebarsDotNet.Helpers.Options; 6 | 7 | // ReSharper disable once CheckNamespace 8 | namespace HandlebarsDotNet.Helpers; 9 | 10 | public class XegerHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 11 | { 12 | [HandlebarsWriter(WriterType.String, "Xeger.Generate")] 13 | public string Generate(string pattern) 14 | { 15 | return new Xeger(pattern).Generate(); 16 | } 17 | 18 | public Category Category => Category.Xeger; 19 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Xeger/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("HandlebarsDotNet.Helpers.Tests, Publickey=00240000048000009400000006020000002400005253413100040000010001007da523c2b43238a93bfd7eb8229c12178f96e9bbb1a62914e099a6f52c3aed51dd089764716b31f9274823058e300dc1210f015e21019cf76cf7229d6f2de38aa56479077fd5bd011e9e8cbc3420bb05d3d9a4633e0125a41dea59a3aea9fbbb568197a46ddceb3cb9e496d9e95a9bc1f465750d348610d331d9aae8d5f553ca")] 4 | 5 | // Needed for Moq in the UnitTest project 6 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Xslt/Handlebars.Net.Helpers.Xslt.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | HandlebarsDotNet.Helpers 5 | HandlebarsDotNet.Helpers.Xslt 6 | Handlebars.Net.Helpers.Xslt 7 | Handlebars.Net.Helpers Xslt 8 | handlebars;helper;xml;xslt;transform 9 | {E9A5CF6C-544C-4D1C-8AAD-EF4144FF710A} 10 | true 11 | ../Handlebars.Net.Helpers.snk 12 | net46;netstandard2.0;netstandard2.1;net6.0;net8.0 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Xslt/Helpers/XsltHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.RegularExpressions; 4 | using System.Xml; 5 | using System.Xml.Xsl; 6 | using HandlebarsDotNet.Helpers.Attributes; 7 | using HandlebarsDotNet.Helpers.Enums; 8 | using HandlebarsDotNet.Helpers.Helpers; 9 | using HandlebarsDotNet.Helpers.Options; 10 | using Stef.Validation; 11 | 12 | // ReSharper disable once CheckNamespace 13 | namespace HandlebarsDotNet.Helpers; 14 | 15 | public class XsltHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 16 | { 17 | private static readonly TimeSpan RegexTimeout = TimeSpan.FromSeconds(1); 18 | 19 | // Remove the "" from a XML document. 20 | // (https://github.com/WireMock-Net/WireMock.Net/issues/618) 21 | private static readonly Regex RemoveXmlVersionRegex = new(@"(<\?xml.*?\?>)", RegexOptions.Compiled, RegexTimeout); 22 | 23 | [HandlebarsWriter(WriterType.Value)] 24 | public XmlDocument Transform(string inputXml, string xsltString) 25 | { 26 | Guard.NotNullOrEmpty(inputXml); 27 | Guard.NotNullOrEmpty(xsltString); 28 | 29 | var inputDoc = CreateXmlDocument(inputXml); 30 | var xslt = CreateXslCompiledTransform(xsltString); 31 | 32 | var outputDoc = new XmlDocument(); 33 | 34 | // Create an XmlWriter that writes directly into the XmlDocument 35 | using var xmlWriter = outputDoc.CreateNavigator()!.AppendChild(); 36 | 37 | // Apply the transformation 38 | xslt.Transform(inputDoc, xmlWriter); 39 | 40 | return outputDoc; 41 | } 42 | 43 | [HandlebarsWriter(WriterType.String)] 44 | public string TransformToString(string document, string xslt, bool? indent = true, bool? removeXmlVersion = true) 45 | { 46 | var outputDoc = Transform(document, xslt); 47 | 48 | string result; 49 | if (indent == false) 50 | { 51 | result = outputDoc.OuterXml; 52 | return removeXmlVersion == false ? result : RemoveXmlVersion(result); 53 | } 54 | 55 | // Define the settings to use for indentation 56 | var settings = new XmlWriterSettings 57 | { 58 | Indent = true 59 | }; 60 | 61 | // Now, serialize the XmlDocument with indentation 62 | var stringWriter = new StringWriter(); 63 | using (var indentingWriter = XmlWriter.Create(stringWriter, settings)) 64 | { 65 | outputDoc.WriteTo(indentingWriter); 66 | } 67 | 68 | result = stringWriter.ToString(); 69 | return removeXmlVersion == false ? result : RemoveXmlVersion(result); 70 | } 71 | 72 | private static XmlDocument CreateXmlDocument(string document) 73 | { 74 | return new XmlDocument 75 | { 76 | InnerXml = RemoveXmlVersion(document) 77 | }; 78 | } 79 | 80 | private static XslCompiledTransform CreateXslCompiledTransform(string xsltString) 81 | { 82 | var xslt = new XslCompiledTransform(); 83 | using var xsltReader = XmlReader.Create(new StringReader(xsltString)); 84 | xslt.Load(xsltReader); 85 | 86 | return xslt; 87 | } 88 | 89 | private static string RemoveXmlVersion(string xml) 90 | { 91 | return RemoveXmlVersionRegex.Replace(xml, string.Empty).Trim(); 92 | } 93 | 94 | public Category Category => Category.Xslt; 95 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.Xslt/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("HandlebarsDotNet.Helpers.Tests, Publickey=00240000048000009400000006020000002400005253413100040000010001007da523c2b43238a93bfd7eb8229c12178f96e9bbb1a62914e099a6f52c3aed51dd089764716b31f9274823058e300dc1210f015e21019cf76cf7229d6f2de38aa56479077fd5bd011e9e8cbc3420bb05d3d9a4633e0125a41dea59a3aea9fbbb568197a46ddceb3cb9e496d9e95a9bc1f465750d348610d331d9aae8d5f553ca")] 4 | 5 | // Needed for Moq in the UnitTest project 6 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Handlebars-Net/Handlebars.Net.Helpers/3785ba7eb61dd343c6ace3b26cf94e3c8f85d04f/src/Handlebars.Net.Helpers.snk -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Compatibility/AppContextHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace HandlebarsDotNet.Helpers.Compatibility; 5 | 6 | internal static class AppContextHelper 7 | { 8 | public static string GetBaseDirectory() 9 | { 10 | #if NETSTANDARD1_3_OR_GREATER || NET6_0_OR_GREATER 11 | return AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); 12 | #else 13 | return AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); 14 | #endif 15 | } 16 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Extensions/IHandlebarsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using HandlebarsDotNet.Helpers.Helpers; 3 | using HandlebarsDotNet.Helpers.Models; 4 | using Stef.Validation; 5 | 6 | namespace HandlebarsDotNet.Helpers.Extensions; 7 | 8 | // ReSharper disable once InconsistentNaming 9 | public static class IHandlebarsExtensions 10 | { 11 | public static bool TryEvaluate(this IHandlebars handlebarsContext, string template, object? data, out object? result) 12 | { 13 | Guard.NotNull(handlebarsContext); 14 | Guard.NotNull(template); 15 | 16 | var start = template.TakeWhile(c => c == '{').Count(); 17 | var end = template.Reverse().TakeWhile(c => c == '}').Count(); 18 | var updated = template.Substring(start, template.Length - start - end); 19 | 20 | // Always set AsyncLocalResultFromEvaluate to NotEvaluated 21 | HandlebarsHelpers.AsyncLocalResultFromEvaluate.Value = EvaluateResult.NotEvaluated; 22 | 23 | result = null; 24 | 25 | try 26 | { 27 | var compiled = handlebarsContext.Compile(new string('{', start) + EvaluateHelper.HelperName + " " + updated + new string('}', end)); 28 | 29 | compiled(data); 30 | 31 | if (HandlebarsHelpers.AsyncLocalResultFromEvaluate.Value.IsEvaluated) 32 | { 33 | result = HandlebarsHelpers.AsyncLocalResultFromEvaluate.Value.Result; 34 | return true; 35 | } 36 | 37 | return false; 38 | } 39 | catch 40 | { 41 | result = null; 42 | return false; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Extensions/MethodBaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using HandlebarsDotNet; 3 | using HandlebarsDotNet.Helpers.Extensions; 4 | 5 | // ReSharper disable once CheckNamespace 6 | namespace System.Reflection; 7 | 8 | /// 9 | /// Based on https://stackoverflow.com/questions/13071805/dynamic-invoke-of-a-method-using-named-parameters 10 | /// 11 | internal static class MethodBaseExtensions 12 | { 13 | public static bool LastParameterIsParam(this MethodBase methodInfo) 14 | { 15 | return methodInfo.GetParameters().LastOrDefault()?.IsParam() == true; 16 | } 17 | 18 | public static bool FirstParameterIsHelperOptions(this MethodBase methodInfo) 19 | { 20 | var parameters = methodInfo.GetParameters(); 21 | return parameters.Length > 0 && parameters[0].ParameterType == typeof(IHelperOptions); 22 | } 23 | 24 | /* Both methods below are not used, but I keep them for future reference 25 | public static object InvokeWithNamedParameters(this MethodBase self, object obj, IDictionary namedParameters) 26 | { 27 | return self.Invoke(obj, MapParameters(self, namedParameters))!; 28 | } 29 | 30 | private static object[] MapParameters(MethodBase method, IDictionary namedParameters) 31 | { 32 | var paramNames = method.GetParameters().Select(p => p.Name).ToArray(); 33 | var parameters = new object[paramNames.Length]; 34 | for (int i = 0; i < parameters.Length; ++i) 35 | { 36 | parameters[i] = Type.Missing; 37 | } 38 | 39 | foreach (var item in namedParameters) 40 | { 41 | int paramIndex = Array.FindIndex(paramNames, n => n?.Equals(item.Key, StringComparison.OrdinalIgnoreCase) == true); 42 | if (paramIndex >= 0) 43 | { 44 | parameters[paramIndex] = item.Value; 45 | } 46 | } 47 | 48 | return parameters; 49 | } 50 | */ 51 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Handlebars.Net.Helpers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Handlebars.Net.Helpers 5 | HandlebarsDotNet.Helpers 6 | enable 7 | Several Handlebars.Net helpers in the categories: 'Boolean', 'Constants', 'DateTime', 'Path', 'Enumerable', 'Environment', 'Math', 'Regex', 'String' and 'Urls'. 8 | Handlebars.Net.Helpers 9 | handlebars;mustache;helper;helpers;extensions;constants;collection;enumerable;system;environment;math;regex;datetime;string;dictionary 10 | {fd1cf989-3f17-4f38-a5f4-edecc647d237} 11 | true 12 | ../Handlebars.Net.Helpers.snk 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | all 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/BooleanHelpers.cs: -------------------------------------------------------------------------------- 1 | using HandlebarsDotNet.Helpers.Attributes; 2 | using HandlebarsDotNet.Helpers.Enums; 3 | using HandlebarsDotNet.Helpers.Options; 4 | 5 | namespace HandlebarsDotNet.Helpers.Helpers; 6 | 7 | internal class BooleanHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 8 | { 9 | [HandlebarsWriter(WriterType.Value)] 10 | public bool Equal(bool value, bool test) 11 | { 12 | return value == test; 13 | } 14 | 15 | [HandlebarsWriter(WriterType.Value)] 16 | public bool NotEqual(bool value, bool test) 17 | { 18 | return value != test; 19 | } 20 | 21 | [HandlebarsWriter(WriterType.Value)] 22 | public bool Not(bool value) 23 | { 24 | return !value; 25 | } 26 | 27 | [HandlebarsWriter(WriterType.Value)] 28 | public bool And(bool value, bool test) 29 | { 30 | return value && test; 31 | } 32 | 33 | [HandlebarsWriter(WriterType.Value)] 34 | public bool LogicalAnd(bool value, bool test) 35 | { 36 | return value & test; 37 | } 38 | 39 | [HandlebarsWriter(WriterType.Value)] 40 | public bool Or(bool value, bool test) 41 | { 42 | return value || test; 43 | } 44 | 45 | [HandlebarsWriter(WriterType.Value)] 46 | public bool LogicalOr(bool value, bool test) 47 | { 48 | return value | test; 49 | } 50 | 51 | [HandlebarsWriter(WriterType.Value)] 52 | public bool LogicalXor(bool value, bool test) 53 | { 54 | return value ^ test; 55 | } 56 | 57 | public Category Category => Category.Boolean; 58 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/ConstantsHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HandlebarsDotNet.Helpers.Attributes; 3 | using HandlebarsDotNet.Helpers.Enums; 4 | using HandlebarsDotNet.Helpers.Options; 5 | 6 | namespace HandlebarsDotNet.Helpers.Helpers; 7 | 8 | internal class ConstantsHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 9 | { 10 | [HandlebarsWriter(WriterType.Value, "Constants.Math.E")] 11 | public double E() 12 | { 13 | return Math.E; 14 | } 15 | 16 | [HandlebarsWriter(WriterType.Value, "Constants.Math.PI")] 17 | public double PI() 18 | { 19 | return Math.PI; 20 | } 21 | 22 | public Category Category => Category.Constants; 23 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/DateTimeHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HandlebarsDotNet.Helpers.Attributes; 3 | using HandlebarsDotNet.Helpers.Enums; 4 | using HandlebarsDotNet.Helpers.Options; 5 | using HandlebarsDotNet.Helpers.Utils; 6 | using Stef.Validation; 7 | 8 | namespace HandlebarsDotNet.Helpers.Helpers; 9 | 10 | internal class DateTimeHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 11 | { 12 | private readonly IDateTimeService _dateTimeService = options.DateTimeService ?? new DateTimeService(); 13 | private readonly StringHelpers _stringHelpers = new(context, options); 14 | 15 | [HandlebarsWriter(WriterType.Value)] 16 | public object Now(string? format = null) 17 | { 18 | var now = _dateTimeService.Now(); 19 | return format is null ? now : now.ToString(format, Context.Configuration.FormatProvider); 20 | } 21 | 22 | [HandlebarsWriter(WriterType.Value)] 23 | public object UtcNow(string? format = null) 24 | { 25 | var utc = _dateTimeService.UtcNow(); 26 | return format is null ? utc : utc.ToString(format, Context.Configuration.FormatProvider); 27 | } 28 | 29 | [HandlebarsWriter(WriterType.String, Name = "DateTime.Format")] 30 | public string Format(object? value, string format) 31 | { 32 | return value switch 33 | { 34 | DateTime valueAsDateTime => _stringHelpers.Format(valueAsDateTime, format), 35 | string valueAsString when DateTime.TryParse(valueAsString, out var parsedAsDateTime) => _stringHelpers.Format(parsedAsDateTime, format), 36 | _ => string.Empty 37 | }; 38 | } 39 | 40 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.Parse")] 41 | public DateTime Parse(string value) 42 | { 43 | return DateTime.Parse(value, Context.Configuration.FormatProvider); 44 | } 45 | 46 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.ParseExact")] 47 | public DateTime ParseExact(string value, string format) 48 | { 49 | return DateTime.ParseExact(value, format, Context.Configuration.FormatProvider); 50 | } 51 | 52 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.AddYears")] 53 | public DateTime AddYears(object value, int increment, string? format = null) 54 | { 55 | return GetDateTimeNonNullable(value, format).AddYears(increment); 56 | } 57 | 58 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.AddMonths")] 59 | public DateTime AddMonths(object value, int increment, string? format = null) 60 | { 61 | return GetDateTimeNonNullable(value, format).AddMonths(increment); 62 | } 63 | 64 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.AddDays")] 65 | public DateTime AddDays(object value, int increment, string? format = null) 66 | { 67 | return GetDateTimeNonNullable(value, format).AddDays(increment); 68 | } 69 | 70 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.AddHours")] 71 | public DateTime AddHours(object value, int increment, string? format = null) 72 | { 73 | return GetDateTimeNonNullable(value, format).AddHours(increment); 74 | } 75 | 76 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.AddMinutes")] 77 | public DateTime AddMinutes(object value, int increment, string? format = null) 78 | { 79 | return GetDateTimeNonNullable(value, format).AddMinutes(increment); 80 | } 81 | 82 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.AddSeconds")] 83 | public DateTime AddSeconds(object value, int increment, string? format = null) 84 | { 85 | return GetDateTimeNonNullable(value, format).AddSeconds(increment); 86 | } 87 | 88 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.AddMilliseconds")] 89 | public DateTime AddMilliseconds(object value, int increment, string? format = null) 90 | { 91 | return GetDateTimeNonNullable(value, format).AddMilliseconds(increment); 92 | } 93 | 94 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.AddTicks")] 95 | public DateTime AddTicks(object value, int increment, string? format = null) 96 | { 97 | return GetDateTimeNonNullable(value, format).AddTicks(increment); 98 | } 99 | 100 | [HandlebarsWriter(WriterType.Value, Name = "DateTime.Add")] 101 | public DateTime Add(object value, int increment, string datePart, string? format = null) 102 | { 103 | var dateTime = GetDateTimeNonNullable(value, format); 104 | 105 | return datePart switch 106 | { 107 | "years" => dateTime.AddYears(increment), 108 | "months" => dateTime.AddMonths(increment), 109 | "days" => dateTime.AddDays(increment), 110 | "hours" => dateTime.AddHours(increment), 111 | "minutes" => dateTime.AddMinutes(increment), 112 | "seconds" => dateTime.AddSeconds(increment), 113 | "milliseconds" => dateTime.AddMilliseconds(increment), 114 | "ticks" => dateTime.AddTicks(increment), 115 | _ => throw new ArgumentException("Invalid date part. It must be one of: [years, months, days, hours, minutes, seconds, milliseconds or ticks].") 116 | }; 117 | } 118 | 119 | private DateTime? GetDatetime(object? value, string? format) 120 | { 121 | return value switch 122 | { 123 | null => null, 124 | DateTime dateTimeValue => dateTimeValue, 125 | string stringValue => string.IsNullOrEmpty(format) ? Parse(stringValue) : ParseExact(stringValue, format), 126 | _ => GetDatetime(value.ToString(), format) 127 | }; 128 | } 129 | 130 | private DateTime GetDateTimeNonNullable(object value, string? format) 131 | { 132 | return Guard.NotNull(GetDatetime(Guard.NotNull(value), format)).GetValueOrDefault(); 133 | } 134 | 135 | public Category Category => Category.DateTime; 136 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/EnumerableHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using HandlebarsDotNet.Helpers.Attributes; 5 | using HandlebarsDotNet.Helpers.Enums; 6 | using HandlebarsDotNet.Helpers.Options; 7 | using HandlebarsDotNet.Helpers.Utils; 8 | using Stef.Validation; 9 | 10 | namespace HandlebarsDotNet.Helpers.Helpers; 11 | 12 | /// 13 | /// Some ideas based on https://github.com/helpers/handlebars-helpers#array 14 | /// 15 | internal class EnumerableHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 16 | { 17 | [HandlebarsWriter(WriterType.Value)] 18 | public double Average(IEnumerable values) 19 | { 20 | return ExecuteUtils.Execute(values, x => x.Average()); 21 | } 22 | 23 | [HandlebarsWriter(WriterType.Value)] 24 | public int Count(IEnumerable? value) 25 | { 26 | return value?.Count() ?? 0; 27 | } 28 | 29 | [HandlebarsWriter(WriterType.Value)] 30 | public IEnumerable Distinct(IEnumerable values) 31 | { 32 | if (values is null) 33 | { 34 | throw new ArgumentNullException(nameof(values)); 35 | } 36 | 37 | return values.Distinct().ToList(); 38 | } 39 | 40 | [HandlebarsWriter(WriterType.Value)] 41 | public bool IsFirst(IEnumerable values, object? value) 42 | { 43 | if (values is null) 44 | { 45 | throw new ArgumentNullException(nameof(values)); 46 | } 47 | 48 | return values.FirstOrDefault() == value; 49 | } 50 | 51 | [HandlebarsWriter(WriterType.Value)] 52 | public bool IsLast(IEnumerable values, object? value) 53 | { 54 | if (values is null) 55 | { 56 | throw new ArgumentNullException(nameof(values)); 57 | } 58 | 59 | return values.LastOrDefault() == value; 60 | } 61 | 62 | [HandlebarsWriter(WriterType.Value)] 63 | public bool IsEmpty(IEnumerable? value) 64 | { 65 | return value is null || !value.Any(); 66 | } 67 | 68 | [HandlebarsWriter(WriterType.Value)] 69 | public double Max(IEnumerable values) 70 | { 71 | return ExecuteUtils.Execute(values, x => x.Max()); 72 | } 73 | 74 | [HandlebarsWriter(WriterType.Value)] 75 | public double Min(IEnumerable values) 76 | { 77 | return ExecuteUtils.Execute(values, x => x.Min()); 78 | } 79 | 80 | [HandlebarsWriter(WriterType.Value)] 81 | public IEnumerable Page(IEnumerable values, int pageNumber, int numberOfObjectsPerPage = 0) 82 | { 83 | return values.Skip(numberOfObjectsPerPage * pageNumber).Take(numberOfObjectsPerPage); 84 | } 85 | 86 | [HandlebarsWriter(WriterType.Value)] 87 | public IEnumerable Reverse(IEnumerable values) 88 | { 89 | return values.Reverse().ToList(); 90 | } 91 | 92 | [HandlebarsWriter(WriterType.Value)] 93 | public IEnumerable Select(IEnumerable values, string propertyName, bool skipNullValues = false) 94 | { 95 | Guard.NotNullOrEmpty(propertyName); 96 | 97 | foreach (var value in Guard.NotNull(values)) 98 | { 99 | var result = ReflectionUtils.GetPropertyOrFieldValue(value, propertyName); 100 | if (!skipNullValues || (skipNullValues && result != null)) 101 | { 102 | yield return result; 103 | } 104 | } 105 | } 106 | 107 | [HandlebarsWriter(WriterType.Value)] 108 | public IEnumerable Skip(IEnumerable values, int count) 109 | { 110 | if (values is null) 111 | { 112 | throw new ArgumentNullException(nameof(values)); 113 | } 114 | 115 | return values.Skip(count).ToList(); 116 | } 117 | 118 | [HandlebarsWriter(WriterType.Value)] 119 | public double Sum(IEnumerable values) 120 | { 121 | return ExecuteUtils.Execute(values, x => x.Sum()); 122 | } 123 | 124 | [HandlebarsWriter(WriterType.Value)] 125 | public IEnumerable Take(IEnumerable values, int count) 126 | { 127 | if (values is null) 128 | { 129 | throw new ArgumentNullException(nameof(values)); 130 | } 131 | 132 | return values.Take(count).ToList(); 133 | } 134 | 135 | public Category Category => Category.Enumerable; 136 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/EnvironmentHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using HandlebarsDotNet.Helpers.Attributes; 4 | using HandlebarsDotNet.Helpers.Enums; 5 | using HandlebarsDotNet.Helpers.Options; 6 | 7 | namespace HandlebarsDotNet.Helpers.Helpers; 8 | 9 | internal class EnvironmentHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 10 | { 11 | #if NETSTANDARD1_3 12 | [HandlebarsWriter(WriterType.String)] 13 | public string? GetEnvironmentVariable(string variable) 14 | { 15 | return Environment.GetEnvironmentVariable(variable); 16 | } 17 | 18 | [HandlebarsWriter(WriterType.String)] 19 | public IDictionary GetEnvironmentVariables() 20 | { 21 | return Environment.GetEnvironmentVariables(); 22 | } 23 | #else 24 | private const string Process = nameof(EnvironmentVariableTarget.Process); 25 | 26 | [HandlebarsWriter(WriterType.String)] 27 | public string? GetEnvironmentVariable(string variable, string target = Process) 28 | { 29 | return Environment.GetEnvironmentVariable(variable, Parse(target)); 30 | } 31 | 32 | public IDictionary GetEnvironmentVariables(string target = Process) 33 | { 34 | return Environment.GetEnvironmentVariables(Parse(target)); 35 | } 36 | 37 | private static EnvironmentVariableTarget Parse(string target) 38 | { 39 | return Enum.TryParse(target, out var @enum) ? @enum : EnvironmentVariableTarget.Process; 40 | } 41 | #endif 42 | 43 | public Category Category => Category.Environment; 44 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/EvaluateHelper.cs: -------------------------------------------------------------------------------- 1 | using HandlebarsDotNet.PathStructure; 2 | using Stef.Validation; 3 | using HandlebarsDotNet.Helpers.Models; 4 | #if NETSTANDARD1_3_OR_GREATER || NET46_OR_GREATER || NET6_0_OR_GREATER 5 | using System.Threading; 6 | #else 7 | using HandlebarsDotNet.Polyfills; 8 | #endif 9 | 10 | namespace HandlebarsDotNet.Helpers.Helpers; 11 | 12 | /// 13 | /// https://github.com/Handlebars-Net/Handlebars.Net/issues/487 14 | /// 15 | internal class EvaluateHelper : IHelperDescriptor 16 | { 17 | internal const string HelperName = "__evaluate"; 18 | 19 | public PathInfo Name => HelperName; 20 | 21 | private readonly AsyncLocal _asyncLocal; 22 | 23 | public EvaluateHelper(AsyncLocal asyncLocal) 24 | { 25 | _asyncLocal = Guard.NotNull(asyncLocal); 26 | } 27 | 28 | public object Invoke(in HelperOptions options, in Context context, in Arguments arguments) 29 | { 30 | return SetValue(arguments); 31 | } 32 | 33 | public void Invoke(in EncodedTextWriter output, in HelperOptions options, in Context context, in Arguments arguments) 34 | { 35 | SetValue(arguments); 36 | } 37 | 38 | private EvaluateResult SetValue(Arguments arguments) 39 | { 40 | return _asyncLocal.Value = new EvaluateResult { IsEvaluated = true, Result = arguments[0] }; 41 | } 42 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/MathHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HandlebarsDotNet.Helpers.Attributes; 3 | using HandlebarsDotNet.Helpers.Enums; 4 | using HandlebarsDotNet.Helpers.Options; 5 | using HandlebarsDotNet.Helpers.Utils; 6 | 7 | namespace HandlebarsDotNet.Helpers.Helpers; 8 | 9 | internal class MathHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 10 | { 11 | private const double Tolerance = 1e-20; // 20 Digits of precision 12 | 13 | [HandlebarsWriter(WriterType.Value)] 14 | public object Add(object value1, object value2) 15 | { 16 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 + x2, (x1, x2) => x1 + x2, (x1, x2) => x1 + x2); 17 | } 18 | 19 | [HandlebarsWriter(WriterType.Value)] 20 | public object Abs(object value) 21 | { 22 | return ExecuteUtils.Execute(Context, value, Math.Abs, Math.Abs, Math.Abs); 23 | } 24 | 25 | [HandlebarsWriter(WriterType.Value)] 26 | public double Avg(object value1, object value2) 27 | { 28 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => (x1 + x2) / 2.0); 29 | } 30 | 31 | [HandlebarsWriter(WriterType.Value)] 32 | public double Ceiling(object value) 33 | { 34 | return ExecuteUtils.Execute(value, x => Math.Ceiling(1.0 * x)); 35 | } 36 | 37 | [HandlebarsWriter(WriterType.Value)] 38 | public double Divide(object value1, object value2) 39 | { 40 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 / x2); 41 | } 42 | 43 | [HandlebarsWriter(WriterType.Value)] 44 | public bool Equal(object value1, object value2) 45 | { 46 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 == x2, (x1, x2) => x1 == x2, (x1, x2) => Math.Abs(x1 - x2) < Tolerance); 47 | } 48 | 49 | [HandlebarsWriter(WriterType.Value)] 50 | public double Floor(object value1, object value2) 51 | { 52 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => Math.Floor(1.0 * x1 / 1.0 * x2)); 53 | } 54 | 55 | [HandlebarsWriter(WriterType.Value)] 56 | public bool GreaterThan(object value1, object value2) 57 | { 58 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 > x2, (x1, x2) => x1 > x2, (x1, x2) => x1 > x2); 59 | } 60 | 61 | [HandlebarsWriter(WriterType.Value)] 62 | public bool GreaterThanEqual(object value1, object value2) 63 | { 64 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 >= x2, (x1, x2) => x1 >= x2, (x1, x2) => x1 >= x2); 65 | } 66 | 67 | [HandlebarsWriter(WriterType.Value)] 68 | public bool LessThan(object value1, object value2) 69 | { 70 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 < x2, (x1, x2) => x1 < x2, (x1, x2) => x1 < x2); 71 | } 72 | 73 | [HandlebarsWriter(WriterType.Value)] 74 | public bool LessThanEqual(object value1, object value2) 75 | { 76 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 <= x2, (x1, x2) => x1 <= x2, (x1, x2) => x1 <= x2); 77 | } 78 | 79 | [HandlebarsWriter(WriterType.Value)] 80 | public object Max(object value1, object value2) 81 | { 82 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => Math.Max(x1, x2), (x1, x2) => Math.Max(x1, x2), (x1, x2) => Math.Max(x1, x2)); 83 | } 84 | 85 | [HandlebarsWriter(WriterType.Value)] 86 | public object Min(object value1, object value2) 87 | { 88 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => Math.Min(x1, x2), (x1, x2) => Math.Min(x1, x2), (x1, x2) => Math.Min(x1, x2)); 89 | } 90 | 91 | [HandlebarsWriter(WriterType.Value)] 92 | public object Minus(object value1, object value2) 93 | { 94 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 - x2, (x1, x2) => x1 - x2, (x1, x2) => x1 - x2); 95 | } 96 | 97 | [HandlebarsWriter(WriterType.Value)] 98 | public object Modulo(object value1, object value2) 99 | { 100 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 % x2, (x1, x2) => x1 % x2, (x1, x2) => x1 % x2); 101 | } 102 | 103 | [HandlebarsWriter(WriterType.Value)] 104 | public object Multiply(object value1, object value2) 105 | { 106 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 * x2, (x1, x2) => x1 * x2, (x1, x2) => x1 * x2); 107 | } 108 | 109 | [HandlebarsWriter(WriterType.Value)] 110 | public bool NotEqual(object value1, object value2) 111 | { 112 | return ExecuteUtils.Execute(Context, value1, value2, (x1, x2) => x1 != x2, (x1, x2) => x1 != x2, (x1, x2) => Math.Abs(x1 - x2) > Tolerance); 113 | } 114 | 115 | [HandlebarsWriter(WriterType.Value)] 116 | public object Plus(object value1, object value2) 117 | { 118 | return Add(value1, value2); 119 | } 120 | 121 | [HandlebarsWriter(WriterType.Value)] 122 | public object Power(object value1, object value2) 123 | { 124 | return ExecuteUtils.Execute(Context, value1, value2, Math.Pow); 125 | } 126 | 127 | [HandlebarsWriter(WriterType.Value)] 128 | public double Round(object value) 129 | { 130 | return ExecuteUtils.Execute(value, Math.Round); 131 | } 132 | 133 | [HandlebarsWriter(WriterType.Value)] 134 | public object Sign(object value) 135 | { 136 | return ExecuteUtils.Execute(Context, value, Math.Sign, l => Math.Sign(l), d => Math.Sign(d)); 137 | } 138 | 139 | [HandlebarsWriter(WriterType.Value)] 140 | public object Subtract(object value1, object value2) 141 | { 142 | return Minus(value1, value2); 143 | } 144 | 145 | [HandlebarsWriter(WriterType.Value)] 146 | public object Sqrt(object value) 147 | { 148 | return ExecuteUtils.Execute(value, Math.Sqrt); 149 | } 150 | 151 | [HandlebarsWriter(WriterType.Value)] 152 | public object Times(object value1, object value2) 153 | { 154 | return Multiply(value1, value2); 155 | } 156 | 157 | public Category Category => Category.Math; 158 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/ObjectHelpers.cs: -------------------------------------------------------------------------------- 1 | using HandlebarsDotNet.Helpers.Attributes; 2 | using HandlebarsDotNet.Helpers.Enums; 3 | using HandlebarsDotNet.Helpers.Options; 4 | using System; 5 | 6 | namespace HandlebarsDotNet.Helpers.Helpers; 7 | 8 | internal class ObjectHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 9 | { 10 | [HandlebarsWriter(WriterType.Value)] 11 | public object? FormatAsObject(object? value) 12 | { 13 | return value; 14 | } 15 | 16 | [HandlebarsWriter(WriterType.String)] 17 | public string ToString(object? value) 18 | { 19 | return value?.ToString() ?? string.Empty; 20 | } 21 | 22 | [HandlebarsWriter(WriterType.Value, Name = "Object.IsNull")] 23 | public bool IsNull(object? value) 24 | { 25 | return value is null; 26 | } 27 | 28 | [HandlebarsWriter(WriterType.Value, Name = "Object.IsNotNull")] 29 | public bool IsNotNull(object? value) 30 | { 31 | return value is not null; 32 | } 33 | 34 | [HandlebarsWriter(WriterType.Value, Name = "Object.Equal")] 35 | public bool Equal(object? value1, object? value2) 36 | { 37 | if (value1 is null && value2 is null) 38 | { 39 | return true; 40 | } 41 | 42 | if (value1 is null ^ value2 is null) 43 | { 44 | return false; 45 | } 46 | 47 | return value1!.Equals(value2); 48 | } 49 | 50 | [HandlebarsWriter(WriterType.Value, Name = "Object.NotEqual")] 51 | public bool NotEqual(object? value1, object? value2) 52 | { 53 | if (value1 is null && value2 is null) 54 | { 55 | return false; 56 | } 57 | 58 | if (value1 is null ^ value2 is null) 59 | { 60 | return true; 61 | } 62 | 63 | return !value1!.Equals(value2); 64 | } 65 | 66 | [HandlebarsWriter(WriterType.Value, Name = "Object.GreaterThan")] 67 | public bool GreaterThan(object value1, object value2) 68 | { 69 | return CompareTo(value1, value2) > 0; 70 | } 71 | 72 | [HandlebarsWriter(WriterType.Value, Name = "Object.GreaterThanEqual")] 73 | public bool GreaterThanEqual(object value1, object value2) 74 | { 75 | return Equal(value1, value2) || CompareTo(value1, value2) > 0; 76 | } 77 | 78 | [HandlebarsWriter(WriterType.Value, Name = "Object.LowerThan")] 79 | public bool LowerThan(object value1, object value2) 80 | { 81 | return CompareTo(value1, value2) < 0; 82 | } 83 | 84 | [HandlebarsWriter(WriterType.Value, Name = "Object.LowerThanEqual")] 85 | public bool LowerThanEqual(object value1, object value2) 86 | { 87 | return Equal(value1, value2) || CompareTo(value1, value2) < 0; 88 | } 89 | 90 | [HandlebarsWriter(WriterType.Value, Name = "Object.CompareTo")] 91 | public int CompareTo(object? value1, object? value2) 92 | { 93 | if (value1 is null || value2 is null) 94 | { 95 | return 0; 96 | } 97 | 98 | if (value1 is not IComparable comparable1 || value2 is not IComparable comparable2) 99 | { 100 | throw new ArgumentException("Both values should implement IComparable."); 101 | } 102 | 103 | return comparable1.CompareTo(comparable2); 104 | } 105 | 106 | public Category Category => Category.Object; 107 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/RegexHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | using HandlebarsDotNet.Helpers.Attributes; 5 | using HandlebarsDotNet.Helpers.Enums; 6 | using HandlebarsDotNet.Helpers.Options; 7 | using HandlebarsDotNet.Helpers.Utils; 8 | 9 | namespace HandlebarsDotNet.Helpers.Helpers; 10 | 11 | internal class RegexHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 12 | { 13 | private static readonly TimeSpan RegexTimeout = TimeSpan.FromSeconds(1); 14 | 15 | [HandlebarsWriter(WriterType.Value)] 16 | public bool IsMatch(string value, string regexPattern, string? options = null) 17 | { 18 | return Match(value, regexPattern, options) is not null; 19 | } 20 | 21 | [HandlebarsWriter(WriterType.Value)] 22 | public object? Match(string value, string regexPattern, string? options = null) 23 | { 24 | return MatchInternal(false, value, regexPattern, options); 25 | } 26 | 27 | [HandlebarsWriter(WriterType.Value, usage: HelperUsage.Block)] 28 | public object? Match(bool isBlockHelper, string value, string regexPattern, string? options = null) 29 | { 30 | return MatchInternal(isBlockHelper, value, regexPattern, options); 31 | } 32 | 33 | private static object? MatchInternal(bool isBlockHelper, string value, string regexPattern, string? options = null) 34 | { 35 | Regex regex; 36 | if (!string.IsNullOrWhiteSpace(options)) 37 | { 38 | RegexOptions regexOptions = RegexOptions.None; 39 | foreach (char ch in options.Distinct()) 40 | { 41 | switch (ch) 42 | { 43 | case 'i': 44 | regexOptions |= RegexOptions.IgnoreCase; 45 | break; 46 | 47 | case 'm': 48 | regexOptions |= RegexOptions.Multiline; 49 | break; 50 | 51 | case 'n': 52 | regexOptions |= RegexOptions.ExplicitCapture; 53 | break; 54 | 55 | case 'c': 56 | regexOptions |= RegexOptions.Compiled; 57 | break; 58 | 59 | case 's': 60 | regexOptions |= RegexOptions.Singleline; 61 | break; 62 | 63 | case 'x': 64 | regexOptions |= RegexOptions.IgnorePatternWhitespace; 65 | break; 66 | 67 | case 'r': 68 | regexOptions |= RegexOptions.RightToLeft; 69 | break; 70 | 71 | case 'e': 72 | regexOptions |= RegexOptions.ECMAScript; 73 | break; 74 | 75 | case 'C': 76 | regexOptions |= RegexOptions.CultureInvariant; 77 | break; 78 | } 79 | } 80 | 81 | regex = new Regex(regexPattern, regexOptions, RegexTimeout); 82 | } 83 | else 84 | { 85 | regex = new Regex(regexPattern, RegexOptions.None, RegexTimeout); 86 | } 87 | 88 | var namedGroups = RegexUtils.GetNamedGroups(regex, value); 89 | if (isBlockHelper && namedGroups.Any()) 90 | { 91 | return namedGroups; 92 | } 93 | 94 | var match = regex.Match(value); 95 | if (match.Success) 96 | { 97 | return match.Value; 98 | } 99 | 100 | return null; 101 | } 102 | 103 | public Category Category => Category.Regex; 104 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Helpers/UrlHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using HandlebarsDotNet.Helpers.Attributes; 3 | using HandlebarsDotNet.Helpers.Enums; 4 | using HandlebarsDotNet.Helpers.Options; 5 | 6 | namespace HandlebarsDotNet.Helpers.Helpers; 7 | 8 | internal class UrlHelpers(IHandlebars context, HandlebarsHelpersOptions options) : BaseHelpers(context, options), IHelpers 9 | { 10 | [HandlebarsWriter(WriterType.String)] 11 | public string DecodeUri(string value) 12 | { 13 | return WebUtility.UrlDecode(value); 14 | } 15 | 16 | [HandlebarsWriter(WriterType.String)] 17 | public string? EncodeUri(string value) 18 | { 19 | return WebUtility.UrlEncode(value); 20 | } 21 | 22 | public Category Category => Category.Url; 23 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Models/EvaluateResult.cs: -------------------------------------------------------------------------------- 1 | namespace HandlebarsDotNet.Helpers.Models; 2 | 3 | internal struct EvaluateResult 4 | { 5 | public object? Result { get; set; } 6 | 7 | public bool IsEvaluated { get; set; } 8 | 9 | public static EvaluateResult NotEvaluated => new() 10 | { 11 | IsEvaluated = false, 12 | Result = null 13 | }; 14 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Models/WrappedString.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Text.RegularExpressions; 3 | using HandlebarsDotNet.Helpers.Utils; 4 | using Stef.Validation; 5 | 6 | namespace HandlebarsDotNet.Helpers.Models; 7 | 8 | public class WrappedString 9 | { 10 | private const string Start = "ϟЊҜߍ"; 11 | private const string End = "ߍҜЊϟ"; 12 | private const string Pattern = $"{Start}(.*){End}"; // Regex pattern 13 | private const string Replacement = "$1"; // $1 refers to the first capture group 14 | 15 | public string Value { get; } 16 | 17 | public string WrappedValue { get; } 18 | 19 | public WrappedString(string value) 20 | { 21 | Value = Guard.NotNull(value); 22 | WrappedValue = $"{Start}{Value}{End}"; 23 | } 24 | 25 | public override string ToString() 26 | { 27 | return WrappedValue; 28 | } 29 | 30 | public static bool TryDecode(string text, [NotNullWhen(true)] out string? decoded) 31 | { 32 | Guard.NotNull(text); 33 | 34 | if (TryRegexReplace(text, out decoded)) 35 | { 36 | return true; 37 | } 38 | 39 | try 40 | { 41 | // Because Handlebars uses Html encoding on the Start and End tokens, try to HtmlDecode the provided text. 42 | var htmlDecoded = HtmlUtils.HtmlDecode(text); 43 | 44 | // Check if the html decoded value contains the Start and End token and try to use regex to extract the value between the Start and End tokens. 45 | if (TryRegexReplace(htmlDecoded, out decoded)) 46 | { 47 | return true; 48 | } 49 | 50 | decoded = text; 51 | return false; 52 | } 53 | catch 54 | { 55 | // Ignore exceptions from HtmlUtils.HtmlDecode 56 | } 57 | 58 | decoded = text; 59 | return false; 60 | } 61 | 62 | private static bool TryRegexReplace(string input, [NotNullWhen(true)] out string? output) 63 | { 64 | // Check if the string value contains the Start and End token. 65 | if (input.Contains(Start) && input.Contains(End)) 66 | { 67 | // Use regex to extract the value between the Start and End tokens. 68 | output = Regex.Replace(input, Pattern, Replacement); 69 | return true; 70 | } 71 | 72 | output = null; 73 | return false; 74 | } 75 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Plugin/PluginLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using HandlebarsDotNet.Helpers.Enums; 7 | using HandlebarsDotNet.Helpers.Helpers; 8 | 9 | namespace HandlebarsDotNet.Helpers.Plugin; 10 | 11 | internal static class PluginLoader 12 | { 13 | public static IDictionary Load(IEnumerable paths, IDictionary items, params object[] args) 14 | { 15 | var helpers = new Dictionary(); 16 | 17 | var pluginTypes = new List(); 18 | try 19 | { 20 | foreach (var file in paths.Distinct().SelectMany(path => Directory.GetFiles(path, "Handlebars*dll")).Distinct()) 21 | { 22 | try 23 | { 24 | var assembly = Assembly.Load(new AssemblyName 25 | { 26 | Name = Path.GetFileNameWithoutExtension(file) 27 | }); 28 | 29 | pluginTypes.AddRange(GetImplementationTypeByInterface(assembly)); 30 | } 31 | catch 32 | { 33 | // Just try next .dll 34 | } 35 | } 36 | } 37 | catch 38 | { 39 | // File system access possibly denied, don't search for any more files 40 | return helpers; 41 | } 42 | 43 | 44 | foreach (var item in items) 45 | { 46 | var matchingType = pluginTypes.FirstOrDefault(pt => pt.Name == item.Value); 47 | if (matchingType is not null) 48 | { 49 | helpers.Add(item.Key, (IHelpers)Activator.CreateInstance(matchingType, args)!); 50 | } 51 | } 52 | 53 | return helpers; 54 | } 55 | 56 | private static IEnumerable GetImplementationTypeByInterface(Assembly assembly) 57 | { 58 | return assembly.GetTypes().Where(t => typeof(IHelpers).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface); 59 | } 60 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("HandlebarsDotNet.Helpers.Tests, Publickey=00240000048000009400000006020000002400005253413100040000010001007da523c2b43238a93bfd7eb8229c12178f96e9bbb1a62914e099a6f52c3aed51dd089764716b31f9274823058e300dc1210f015e21019cf76cf7229d6f2de38aa56479077fd5bd011e9e8cbc3420bb05d3d9a4633e0125a41dea59a3aea9fbbb568197a46ddceb3cb9e496d9e95a9bc1f465750d348610d331d9aae8d5f553ca")] 4 | 5 | // Needed for Moq in the UnitTest project 6 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Utils/ReflectionUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Reflection; 3 | 4 | namespace HandlebarsDotNet.Helpers.Utils 5 | { 6 | internal static class ReflectionUtils 7 | { 8 | // https://stackoverflow.com/questions/2535287/getting-nested-object-property-value-using-reflection 9 | public static object? GetPropertyOrFieldValue(object? obj, string propertyOrFieldName) 10 | { 11 | return propertyOrFieldName.Contains(".") ? 12 | GetPropertyOrFieldValue(GetPropertyOrFieldValueInternal(obj, propertyOrFieldName.Split('.').First()), string.Join(".", propertyOrFieldName.Split('.').Skip(1))) : 13 | GetPropertyOrFieldValueInternal(obj, propertyOrFieldName); 14 | } 15 | 16 | private static object? GetPropertyOrFieldValueInternal(object? obj, string propertyOrFieldName) 17 | { 18 | var propertyInfo = obj?.GetType().GetTypeInfo().GetDeclaredProperty(propertyOrFieldName); 19 | if (propertyInfo != null) 20 | { 21 | return propertyInfo.GetValue(obj); 22 | } 23 | 24 | var fieldInfo = obj?.GetType().GetTypeInfo().GetDeclaredField(propertyOrFieldName); 25 | if (fieldInfo != null) 26 | { 27 | return fieldInfo.GetValue(obj); 28 | } 29 | 30 | return null; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Utils/RegexUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace HandlebarsDotNet.Helpers.Utils 5 | { 6 | internal static class RegexUtils 7 | { 8 | public static Dictionary GetNamedGroups(Regex regex, string input) 9 | { 10 | var namedGroupsDictionary = new Dictionary(); 11 | 12 | GroupCollection groups = regex.Match(input).Groups; 13 | foreach (string groupName in regex.GetGroupNames()) 14 | { 15 | if (groups[groupName].Captures.Count > 0) 16 | { 17 | namedGroupsDictionary.Add(groupName, groups[groupName].Value); 18 | } 19 | } 20 | 21 | return namedGroupsDictionary; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Handlebars.Net.Helpers/Utils/RuntimeInformationUtils.cs: -------------------------------------------------------------------------------- 1 | namespace HandlebarsDotNet.Helpers.Utils; 2 | 3 | internal static class RuntimeInformationUtils 4 | { 5 | public static readonly bool IsBlazorWASM; 6 | 7 | static RuntimeInformationUtils() 8 | { 9 | #if NET451 || NET452 || NET46 || NETSTANDARD1_3 10 | IsBlazorWASM = false; 11 | #else 12 | IsBlazorWASM = 13 | // Used for Blazor WebAssembly .NET Core 3.x / .NET Standard 2.x 14 | System.Type.GetType("Mono.Runtime") != null || 15 | 16 | // Use for Blazor WebAssembly .NET 17 | // See also https://github.com/mono/mono/pull/19568/files 18 | System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Create("BROWSER")); 19 | #endif 20 | } 21 | } -------------------------------------------------------------------------------- /src/sign.txt: -------------------------------------------------------------------------------- 1 | Start VS2019 developer prompt 2 | 3 | sn -p Handlebars.Net.Helpers public-key 4 | sn -tp public-key > public-key.txt -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Handlebars.Net.Helpers.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net451;net452;net46;netcoreapp3.1;net6.0;net8.0 5 | false 6 | HandlebarsDotNet.Helpers.Tests 7 | HandlebarsDotNet.Helpers.Tests 8 | latest 9 | enable 10 | {aeef0925-0931-41b5-a3c5-c6a215560ebc} 11 | true 12 | ../../src/Handlebars.Net.Helpers.snk 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/BooleanHelpersTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Helpers; 4 | using HandlebarsDotNet.Helpers.Options; 5 | using Moq; 6 | using Xunit; 7 | 8 | namespace HandlebarsDotNet.Helpers.Tests.Helpers; 9 | 10 | public class BooleanHelpersTests 11 | { 12 | private readonly BooleanHelpers _sut; 13 | 14 | public BooleanHelpersTests() 15 | { 16 | var contextMock = new Mock(); 17 | contextMock.SetupGet(c => c.Configuration).Returns(new HandlebarsConfiguration { FormatProvider = CultureInfo.InvariantCulture }); 18 | 19 | _sut = new BooleanHelpers(contextMock.Object, new HandlebarsHelpersOptions()); 20 | } 21 | 22 | 23 | [Theory] 24 | [InlineData(true, false, false)] 25 | [InlineData(false, true, false)] 26 | [InlineData(true, true, true)] 27 | [InlineData(false, false, true)] 28 | public void Equal(bool value, bool test, bool expected) 29 | { 30 | // Act 31 | var result = _sut.Equal(value, test); 32 | 33 | // Assert 34 | result.Should().Be(expected); 35 | } 36 | 37 | [Theory] 38 | [InlineData(true, false, true)] 39 | [InlineData(false, true, true)] 40 | [InlineData(true, true, false)] 41 | [InlineData(false, false, false)] 42 | public void NotEqual(bool value, bool test, bool expected) 43 | { 44 | // Act 45 | var result = _sut.NotEqual(value, test); 46 | 47 | // Assert 48 | result.Should().Be(expected); 49 | } 50 | 51 | [Theory] 52 | [InlineData(true, false)] 53 | [InlineData(false, true)] 54 | public void Not(bool value, bool expected) 55 | { 56 | // Act 57 | var result = _sut.Not(value); 58 | 59 | // Assert 60 | result.Should().Be(expected); 61 | } 62 | 63 | [Theory] 64 | [InlineData(true, true, true)] 65 | [InlineData(false, true, false)] 66 | [InlineData(true, false, false)] 67 | [InlineData(false, false, false)] 68 | public void And(bool value, bool test, bool expected) 69 | { 70 | // Act 71 | var result = _sut.And(value, test); 72 | 73 | // Assert 74 | result.Should().Be(expected); 75 | } 76 | 77 | [Theory] 78 | [InlineData(true, true, true)] 79 | [InlineData(false, true, false)] 80 | [InlineData(true, false, false)] 81 | [InlineData(false, false, false)] 82 | public void LogicalAnd(bool value, bool test, bool expected) 83 | { 84 | // Act 85 | var result = _sut.LogicalAnd(value, test); 86 | 87 | // Assert 88 | result.Should().Be(expected); 89 | } 90 | 91 | [Theory] 92 | [InlineData(true, true, true)] 93 | [InlineData(false, true, true)] 94 | [InlineData(true, false, true)] 95 | [InlineData(false, false, false)] 96 | public void Or(bool value, bool test, bool expected) 97 | { 98 | // Act 99 | var result = _sut.Or(value, test); 100 | 101 | // Assert 102 | result.Should().Be(expected); 103 | } 104 | 105 | [Theory] 106 | [InlineData(true, true, true)] 107 | [InlineData(false, true, true)] 108 | [InlineData(true, false, true)] 109 | [InlineData(false, false, false)] 110 | public void LogicalOr(bool value, bool test, bool expected) 111 | { 112 | // Act 113 | var result = _sut.LogicalOr(value, test); 114 | 115 | // Assert 116 | result.Should().Be(expected); 117 | } 118 | 119 | [Theory] 120 | [InlineData(true, true, false)] 121 | [InlineData(false, true, true)] 122 | [InlineData(true, false, true)] 123 | [InlineData(false, false, false)] 124 | public void LogicalXor(bool value, bool test, bool expected) 125 | { 126 | // Act 127 | var result = _sut.LogicalXor(value, test); 128 | 129 | // Assert 130 | result.Should().Be(expected); 131 | } 132 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/EnvironmentHelpersTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Helpers; 4 | using HandlebarsDotNet.Helpers.Options; 5 | using Moq; 6 | using Xunit; 7 | 8 | namespace HandlebarsDotNet.Helpers.Tests.Helpers; 9 | 10 | public class EnvironmentHelpersTests 11 | { 12 | private readonly EnvironmentHelpers _sut; 13 | 14 | public EnvironmentHelpersTests() 15 | { 16 | var handlebarsContext = new Mock(); 17 | _sut = new EnvironmentHelpers(handlebarsContext.Object, HandlebarsHelpersOptions.Default); 18 | } 19 | 20 | [Fact] 21 | public void GetEnvironmentVariable_NonExisting() 22 | { 23 | // Arrange 24 | var value = Guid.NewGuid().ToString(); 25 | 26 | // Act 27 | var result = _sut.GetEnvironmentVariable(value); 28 | 29 | // Assert 30 | result.Should().BeNull(); 31 | } 32 | 33 | [Fact] 34 | public void GetEnvironmentVariable_Existing() 35 | { 36 | // Arrange 37 | var variable = Guid.NewGuid().ToString(); 38 | var value = Guid.NewGuid().ToString(); 39 | try 40 | { 41 | Environment.SetEnvironmentVariable(variable, value); 42 | 43 | // Act 44 | var result = _sut.GetEnvironmentVariable(variable); 45 | 46 | // Assert 47 | result.Should().Be(value); 48 | 49 | } 50 | finally 51 | { 52 | Environment.SetEnvironmentVariable(variable, null); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/ExecuteUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using HandlebarsDotNet.Helpers.Utils; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace HandlebarsDotNet.Helpers.Tests.Helpers 9 | { 10 | public class ExecuteUtilsTests 11 | { 12 | [Fact] 13 | public void Execute_function_with_invalid_data_should_throw_exception() 14 | { 15 | // Arrange 16 | var value = "invalid data"; 17 | 18 | // Act and Assert 19 | value 20 | .Invoking(v => ExecuteUtils.Execute(v, Math.Sqrt)) 21 | .Should().Throw(); 22 | } 23 | 24 | [Fact] 25 | public void Execute_array_function_with_invalid_data_should_throw_exception() 26 | { 27 | // Arrange 28 | var array = new object[] { "invalid data" }; 29 | var function = new Func, double>(x => x.FirstOrDefault()); 30 | 31 | // Act and Assert 32 | array 33 | .Invoking(v => ExecuteUtils.Execute(v, function)) 34 | .Should().Throw(); 35 | } 36 | 37 | [Fact] 38 | public void Execute_two_argument_function_with_invalid_data_should_throw_exception() 39 | { 40 | // Arrange 41 | var v1 = 10.0; 42 | var v2 = "invalid data"; 43 | var function = new Func((x, y) => x + y); 44 | 45 | // Act and Assert 46 | v1 47 | .Invoking(v => ExecuteUtils.Execute(null!, v, v2, function)) 48 | .Should().Throw(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/MathHelpersTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Threading; 3 | using FluentAssertions; 4 | using HandlebarsDotNet.Helpers.Helpers; 5 | using HandlebarsDotNet.Helpers.Options; 6 | using Moq; 7 | using Xunit; 8 | 9 | namespace HandlebarsDotNet.Helpers.Tests.Helpers; 10 | 11 | public class MathHelpersTests 12 | { 13 | private readonly Mock _contextMock; 14 | 15 | private readonly MathHelpers _sut; 16 | 17 | public MathHelpersTests() 18 | { 19 | _contextMock = new Mock(); 20 | _contextMock.SetupGet(c => c.Configuration).Returns(new HandlebarsConfiguration { FormatProvider = CultureInfo.InvariantCulture }); 21 | 22 | _sut = new MathHelpers(_contextMock.Object, new HandlebarsHelpersOptions()); 23 | 24 | Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; 25 | } 26 | 27 | [Theory] 28 | [InlineData(-1, 1)] 29 | [InlineData(1, 1)] 30 | [InlineData(-2147483649L, 2147483649L)] 31 | [InlineData(1.2, 1.2)] 32 | [InlineData(-1.2, 1.2)] 33 | [InlineData("-1", 1)] 34 | [InlineData("-1.2", 1.2)] 35 | public void Abs(object value, object expected) 36 | { 37 | // Act 38 | var result = _sut.Abs(value); 39 | 40 | // Assert 41 | result.Should().Be(expected); 42 | } 43 | 44 | [Fact] 45 | public void Abs_Complex() 46 | { 47 | // Act 48 | var result1 = _sut.Abs(new ComplexInt()); 49 | var result2 = _sut.Abs(new ComplexDouble()); 50 | 51 | // Assert 52 | result1.Should().Be(42); 53 | result2.Should().Be(42.1); 54 | } 55 | 56 | [Theory] 57 | [InlineData(-1, 1, 0)] 58 | [InlineData(1, 2, 3)] 59 | [InlineData(-1.2, 1.2, 0)] 60 | [InlineData(1.2, 1.3, 2.5)] 61 | [InlineData("1000", "1.2", 1001.2)] 62 | public void Add(object value1, object value2, object expected) 63 | { 64 | // Act 65 | var result = _sut.Add(value1, value2); 66 | 67 | // Assert 68 | result.Should().Be(expected); 69 | } 70 | 71 | [Theory] 72 | [InlineData(1, 43)] 73 | [InlineData("1", 43)] 74 | [InlineData(1.2, 43.2)] 75 | [InlineData("1.2", 43.2)] 76 | public void Add_Complex_And_Any(object value, object expected) 77 | { 78 | // Act 79 | var result1 = _sut.Add(new ComplexInt(), value); 80 | var result2 = _sut.Add(value, new ComplexInt()); 81 | 82 | // Assert 83 | result1.Should().Be(expected); 84 | result2.Should().Be(expected); 85 | } 86 | 87 | [Fact] 88 | public void Add_Complex_And_Complex() 89 | { 90 | // Act 91 | var result1 = _sut.Add(new ComplexInt(), new ComplexInt()); 92 | var result2 = _sut.Add(new ComplexInt(), new ComplexDouble()); 93 | 94 | var result3 = _sut.Add(new ComplexDouble(), new ComplexDouble()); 95 | var result4 = _sut.Add(new ComplexDouble(), new ComplexInt()); 96 | 97 | // Assert 98 | result1.Should().Be(84); 99 | result2.Should().Be(84.1); 100 | result3.Should().Be(84.2); 101 | result4.Should().Be(84.1); 102 | } 103 | 104 | [Theory] 105 | [InlineData(-1, 1, -1)] 106 | [InlineData(-1.2, 1.2, -1)] 107 | [InlineData(1.2, 0.5, 2.4)] 108 | [InlineData("45", "22.5", 2)] 109 | public void Divide(object value1, object value2, double expected) 110 | { 111 | // Act 112 | var result = _sut.Divide(value1, value2); 113 | 114 | // Assert 115 | result.Should().Be(expected); 116 | } 117 | 118 | [Theory] 119 | [InlineData(-1, 0, false)] 120 | [InlineData(-2147483649L, 0, false)] 121 | [InlineData(2147483649L, 0, true)] 122 | [InlineData(1.2, 0.2, true)] 123 | [InlineData(-1.2, 0.2, false)] 124 | [InlineData("-1", 0, false)] 125 | [InlineData("1.2", 0, true)] 126 | public void GreaterThan(object value1, object value2, bool expected) 127 | { 128 | // Act 129 | var result = _sut.GreaterThan(value1, value2); 130 | 131 | // Assert 132 | result.Should().Be(expected); 133 | } 134 | 135 | [Theory] 136 | [InlineData(-1.1, 1.1, 0)] 137 | [InlineData("20", "30.0", 25)] 138 | public void Avg(object value1, object value2, double expected) 139 | { 140 | // Act 141 | var result = _sut.Avg(value1, value2); 142 | 143 | // Assert 144 | result.Should().Be(expected); 145 | } 146 | 147 | [Theory] 148 | [InlineData(-1, 1, 1)] 149 | [InlineData(-1.2, 1, 1)] 150 | [InlineData(1, -1.2, 1)] 151 | public void Max(object value1, object value2, object expected) 152 | { 153 | // Act 154 | var result = _sut.Max(value1, value2); 155 | 156 | // Assert 157 | result.Should().Be(expected); 158 | } 159 | 160 | [Theory] 161 | [InlineData(-1, 1, -1)] 162 | [InlineData(-1.2, 1, -1.2)] 163 | [InlineData(1, -1.2, -1.2)] 164 | public void Min(object value1, object value2, object expected) 165 | { 166 | // Act 167 | var result = _sut.Min(value1, value2); 168 | 169 | // Assert 170 | result.Should().Be(expected); 171 | } 172 | 173 | [Theory] 174 | [InlineData(-1, 1, -1)] 175 | [InlineData(-1.2, 2, -2.4)] 176 | [InlineData(1.2, 0.5, 0.6)] 177 | [InlineData("2", "22.5", 45)] 178 | public void Multiply(object value1, object value2, double expected) 179 | { 180 | // Act 181 | var result = _sut.Multiply(value1, value2); 182 | 183 | // Assert 184 | result.Should().Be(expected); 185 | } 186 | 187 | [Theory] 188 | [InlineData(0, 0)] 189 | [InlineData(0.0d, 0)] 190 | [InlineData(-2, -1)] 191 | [InlineData(-2.2, -1)] 192 | [InlineData(42, 1)] 193 | [InlineData(42.5, 1)] 194 | public void Sign(object value, object expected) 195 | { 196 | // Act 197 | var result = _sut.Sign(value); 198 | 199 | // Assert 200 | result.Should().Be(expected); 201 | } 202 | } 203 | 204 | class ComplexInt 205 | { 206 | public override string ToString() 207 | { 208 | return "42"; 209 | } 210 | } 211 | 212 | class ComplexDouble 213 | { 214 | public override string ToString() 215 | { 216 | return "42.1"; 217 | } 218 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/Parsers/StringValueParserTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Parsers; 4 | using Moq; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Helpers.Parsers; 8 | 9 | public class StringValueParserTests 10 | { 11 | private readonly Mock _handlebarsContext; 12 | 13 | public StringValueParserTests() 14 | { 15 | var configuration = new HandlebarsConfiguration 16 | { 17 | FormatProvider = CultureInfo.InvariantCulture 18 | }; 19 | 20 | _handlebarsContext = new Mock(); 21 | _handlebarsContext.SetupGet(c => c.Configuration).Returns(configuration); 22 | } 23 | 24 | [Theory] 25 | [InlineData("123", 123)] 26 | [InlineData("-1", -1)] 27 | [InlineData("2147483647", int.MaxValue)] 28 | [InlineData("-2147483648", int.MinValue)] 29 | public void Parse_ShouldReturnInt_WhenInputIsInt(string input, int expected) 30 | { 31 | var result = StringValueParser.Parse(_handlebarsContext.Object, input); 32 | result.Should().BeOfType().And.Be(expected); 33 | } 34 | 35 | [Theory] 36 | [InlineData("9223372036854775807", long.MaxValue)] 37 | [InlineData("-9223372036854775808", long.MinValue)] 38 | public void Parse_ShouldReturnLong_WhenInputIsLong(string input, long expected) 39 | { 40 | var result = StringValueParser.Parse(_handlebarsContext.Object, input); 41 | result.Should().BeOfType().And.Be(expected); 42 | } 43 | 44 | [Theory] 45 | [InlineData("123.456", 123.456)] 46 | [InlineData("0.0001", 0.0001)] 47 | [InlineData("-1.42", -1.42)] 48 | public void Parse_ShouldReturnDouble_WhenInputIsDouble(string input, double expected) 49 | { 50 | var result = StringValueParser.Parse(_handlebarsContext.Object, input); 51 | result.Should().BeOfType().And.Be(expected); 52 | } 53 | 54 | [Theory] 55 | [InlineData("")] 56 | [InlineData("test")] 57 | [InlineData("123test")] 58 | public void Parse_ShouldReturnString_WhenInputIsNonNumericString(string input) 59 | { 60 | var result = StringValueParser.Parse(_handlebarsContext.Object, input); 61 | result.Should().BeOfType().And.Be(input); 62 | } 63 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/RandomHelpersTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Options; 4 | using Moq; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Helpers; 8 | 9 | public class RandomHelpersTests 10 | { 11 | private readonly RandomHelpers _sut; 12 | 13 | public RandomHelpersTests() 14 | { 15 | var contextMock = new Mock(); 16 | contextMock.SetupGet(c => c.Configuration).Returns(new HandlebarsConfiguration()); 17 | 18 | _sut = new RandomHelpers(contextMock.Object, HandlebarsHelpersOptions.Default); 19 | } 20 | 21 | [Fact] 22 | public void Random() 23 | { 24 | // Arrange 25 | var hash = new Dictionary 26 | { 27 | { "Type", "Integer" }, 28 | { "Min", 1000 }, 29 | { "Max", 9999 } 30 | }; 31 | 32 | // Act 33 | var result = _sut.Random(hash); 34 | 35 | // Assert 36 | (result as int?).Should().BeInRange(1000, 9999); 37 | } 38 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/UrlHelpersTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using HandlebarsDotNet.Helpers.Helpers; 3 | using HandlebarsDotNet.Helpers.Options; 4 | using Moq; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Helpers; 8 | 9 | public class UrlHelpersTests 10 | { 11 | private readonly UrlHelpers _sut; 12 | 13 | public UrlHelpersTests() 14 | { 15 | var contextMock = new Mock(); 16 | 17 | _sut = new UrlHelpers(contextMock.Object, HandlebarsHelpersOptions.Default); 18 | } 19 | 20 | [Theory] 21 | [InlineData(null, null)] 22 | [InlineData("", "")] 23 | [InlineData("arinet%2FHandlebarDocs%2Fblob%2Fmaster%2FcustomHelpers%2Furl.md%23decodeuri", "arinet/HandlebarDocs/blob/master/customHelpers/url.md#decodeuri")] 24 | [InlineData("%2Fsearch%2Finventory%2Fbrand%2FPolaris+Industries%2Fsort%2Fbest-match", "/search/inventory/brand/Polaris Industries/sort/best-match")] 25 | public void DecodeUri(string value, string expected) 26 | { 27 | // Act 28 | var result = _sut.DecodeUri(value); 29 | 30 | // Assert 31 | result.Should().Be(expected); 32 | } 33 | 34 | [Theory] 35 | [InlineData(null, null)] 36 | [InlineData("", "")] 37 | [InlineData("arinet/HandlebarDocs/blob/master/customHelpers/url.md#decodeuri", "arinet%2FHandlebarDocs%2Fblob%2Fmaster%2FcustomHelpers%2Furl.md%23decodeuri")] 38 | [InlineData("/search/inventory/brand/Polaris Industries/sort/best-match", "%2Fsearch%2Finventory%2Fbrand%2FPolaris+Industries%2Fsort%2Fbest-match")] 39 | public void EncodeUri(string value, string expected) 40 | { 41 | // Act 42 | var result = _sut.EncodeUri(value); 43 | 44 | // Assert 45 | result.Should().Be(expected); 46 | } 47 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/XPathHelpersTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using HandlebarsDotNet.Helpers.Options; 3 | using Moq; 4 | using Xunit; 5 | 6 | namespace HandlebarsDotNet.Helpers.Tests.Helpers; 7 | 8 | public class XPathHelpersTests 9 | { 10 | private readonly Mock _contextMock; 11 | 12 | private readonly XPathHelpers _sut; 13 | 14 | public XPathHelpersTests() 15 | { 16 | _contextMock = new Mock(); 17 | _contextMock.SetupGet(c => c.Configuration).Returns(new HandlebarsConfiguration()); 18 | 19 | _sut = new XPathHelpers(_contextMock.Object, HandlebarsHelpersOptions.Default); 20 | } 21 | 22 | [Fact] 23 | public void Evaluate_ToBool() 24 | { 25 | // Assign 26 | var xml = @" 27 | 28 | abc 29 | "; 30 | 31 | // Act 32 | var result = _sut.Evaluate(xml, "boolean(/todo-list[count(todo-item) = 1])") as bool?; 33 | 34 | // Assert 35 | result.Should().BeTrue(); 36 | } 37 | 38 | [Fact] 39 | public void Evaluate_ToString() 40 | { 41 | // Act 42 | var result = _sut.Evaluate("", "//a/@id")?.ToString(); 43 | 44 | // Assert 45 | result.Should().Be("1"); 46 | } 47 | 48 | [Fact] 49 | public void EvaluateToString_ReturnString() 50 | { 51 | // Act 52 | var result = _sut.EvaluateToString("", "//a/@id"); 53 | 54 | // Assert 55 | result.Should().Be("1"); 56 | } 57 | 58 | [Fact] 59 | public void EvaluateToString_ReturnStringArray() 60 | { 61 | // Act 62 | var result = _sut.EvaluateToString("", "//a/@id"); 63 | 64 | // Assert 65 | result.Should().Be("1, 2"); 66 | } 67 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/XegerHelpersTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using HandlebarsDotNet.Helpers.Options; 3 | using Moq; 4 | using Xunit; 5 | 6 | namespace HandlebarsDotNet.Helpers.Tests.Helpers; 7 | 8 | public class XegerHelpersTests 9 | { 10 | private readonly Mock _contextMock; 11 | 12 | private readonly XegerHelpers _sut; 13 | 14 | public XegerHelpersTests() 15 | { 16 | _contextMock = new Mock(); 17 | _contextMock.SetupGet(c => c.Configuration).Returns(new HandlebarsConfiguration()); 18 | 19 | _sut = new XegerHelpers(_contextMock.Object, HandlebarsHelpersOptions.Default); 20 | } 21 | 22 | [Fact] 23 | public void Xeger_Generate_Test1() 24 | { 25 | // Act 26 | var result = _sut.Generate("[1-9]{1}\\d{3}"); 27 | 28 | // Assert 29 | int.Parse(result).Should().BeInRange(1000, 9999); 30 | } 31 | 32 | [Fact] 33 | public void Xeger_Generate_Test2() 34 | { 35 | // Act 36 | var result = _sut.Generate("{[\"]A[\"]:[\"]A[0-9]{3}[1-9][\"]}"); 37 | 38 | // Assert 39 | result.Should().StartWith("{").And.EndWith("}").And.Contain(":"); 40 | } 41 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Helpers/XsltHelpersTests.cs: -------------------------------------------------------------------------------- 1 | #if !(NET451 || NET452) 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Options; 4 | using Moq; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Helpers; 8 | 9 | public class XsltHelpersTests 10 | { 11 | private static string xml = @" 12 | 13 | 14 | "; 15 | 16 | private static string xslt = @" 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | "; 32 | 33 | private readonly XsltHelpers _sut; 34 | 35 | public XsltHelpersTests() 36 | { 37 | Mock contextMock = new(); 38 | contextMock.SetupGet(c => c.Configuration).Returns(new HandlebarsConfiguration()); 39 | 40 | _sut = new XsltHelpers(contextMock.Object, HandlebarsHelpersOptions.Default); 41 | } 42 | 43 | [Fact] 44 | public void TransformToString() 45 | { 46 | // Act 47 | var result = _sut.TransformToString(xml, xslt); 48 | 49 | // Assert 50 | const string expected = @" 51 | 52 | 53 | "; 54 | result.Should().BeEquivalentTo(expected); 55 | } 56 | 57 | [Fact] 58 | public void TransformToString_IndentIsFalse() 59 | { 60 | // Act 61 | var result = _sut.TransformToString(xml, xslt, false); 62 | 63 | // Assert 64 | const string expected = @""; 65 | result.Should().BeEquivalentTo(expected); 66 | } 67 | } 68 | #endif -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/IO/PassthroughTextEncoderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.IO; 3 | using System.Text; 4 | using FluentAssertions; 5 | using HandlebarsDotNet.Helpers.IO; 6 | using Xunit; 7 | 8 | namespace HandlebarsDotNet.Helpers.Tests.IO; 9 | 10 | public class PassthroughTextEncoderTests 11 | { 12 | private readonly PassthroughTextEncoder _sut = new(); 13 | 14 | [Theory] 15 | [InlineData("Hello & World", "test Hello & World abc")] 16 | [InlineData("1 < 2 > 0", "test 1 < 2 > 0 abc")] 17 | [InlineData("Use \"quotes\"", "test Use \"quotes\" abc")] 18 | [InlineData("'single quotes'", "test 'single quotes' abc")] 19 | [InlineData("あ", "test あ abc")] 20 | [InlineData("A", "test A abc")] 21 | [InlineData(null, "test abc")] 22 | [InlineData("", "test abc")] 23 | public void UsePassthroughTextEncoder(string value, string expected) 24 | { 25 | var context = Handlebars.Create(); 26 | context.Configuration.FormatProvider = CultureInfo.InvariantCulture; 27 | context.Configuration.TextEncoder = _sut; 28 | 29 | var model = new 30 | { 31 | x = value 32 | }; 33 | var action = context.Compile("test {{x}} abc"); 34 | 35 | // Act 36 | var result = action(model); 37 | 38 | // Assert 39 | result.Should().Be(expected); 40 | } 41 | 42 | [Fact] 43 | public void Encode_Enumerator_WritesToTarget() 44 | { 45 | // Arrange 46 | using var text = "Hello, World!".GetEnumerator(); 47 | using var target = new StringWriter(); 48 | 49 | // Act 50 | _sut.Encode(text, target); 51 | 52 | // Assert 53 | target.ToString().Should().Be("Hello, World!"); 54 | } 55 | 56 | [Fact] 57 | public void Encode_String_WritesToTarget() 58 | { 59 | // Arrange 60 | const string text = "Hello, World!"; 61 | using var target = new StringWriter(); 62 | 63 | // Act 64 | _sut.Encode(text, target); 65 | 66 | // Assert 67 | target.ToString().Should().Be("Hello, World!"); 68 | } 69 | 70 | [Fact] 71 | public void Encode_StringBuilder_WritesToTarget() 72 | { 73 | // Arrange 74 | var text = new StringBuilder("Hello, World!"); 75 | using var target = new StringWriter(); 76 | 77 | // Act 78 | _sut.Encode(text, target); 79 | 80 | // Assert 81 | target.ToString().Should().Be("Hello, World!"); 82 | } 83 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Models/WrappedStringTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Models; 4 | using HandlebarsDotNet.Helpers.Utils; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Models; 8 | 9 | [ExcludeFromCodeCoverage] 10 | public class WrappedStringTests 11 | { 12 | [Theory] 13 | [InlineData("Hello, World!", "Hello, World!", "ϟЊҜߍHello, World!ߍҜЊϟ")] 14 | [InlineData("1234567890", "1234567890", "ϟЊҜߍ1234567890ߍҜЊϟ")] 15 | [InlineData("", "", "ϟЊҜߍߍҜЊϟ")] 16 | public void Encode(string input, string expectedValue, string expectedEncodedValue) 17 | { 18 | // Act 19 | var result = new WrappedString(input); 20 | 21 | // Assert 22 | result.Value.Should().Be(expectedValue); 23 | result.WrappedValue.Should().Be(expectedEncodedValue); 24 | result.ToString().Should().Be(expectedEncodedValue); 25 | } 26 | 27 | [Theory] 28 | [InlineData("ϟЊҜߍHello, World!ߍҜЊϟ", true, "Hello, World!")] 29 | [InlineData("ϟЊҜߍ1234567890ߍҜЊϟ", true, "1234567890")] 30 | [InlineData("Hello, World!", false, "Hello, World!")] 31 | [InlineData("", false, "")] 32 | public void TryDecode(string input, bool expectedResult, string expectedOutput) 33 | { 34 | // Act 35 | var result1 = WrappedString.TryDecode(input, out var decoded1); 36 | 37 | // Assert 38 | result1.Should().Be(expectedResult); 39 | decoded1.Should().Be(expectedOutput); 40 | 41 | // Act 42 | var result2 = WrappedString.TryDecode(HtmlUtils.HtmlEncode(input), out var decoded2); 43 | 44 | // Assert 45 | result2.Should().Be(expectedResult); 46 | decoded2.Should().Be(expectedOutput); 47 | } 48 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/BooleanHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Enums; 4 | using Xunit; 5 | 6 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 7 | 8 | public class BooleanHelpersTemplateTests 9 | { 10 | private readonly IHandlebars _handlebarsContext; 11 | 12 | public BooleanHelpersTemplateTests() 13 | { 14 | _handlebarsContext = Handlebars.Create(); 15 | _handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture; 16 | 17 | HandlebarsHelpers.Register(_handlebarsContext, Category.Boolean); 18 | } 19 | 20 | [Fact] 21 | public void Boolean_Not() 22 | { 23 | // Arrange 24 | var template = """[ {{#each data}}{{#if (Boolean.Not @first)}}, "{{this}}"{{else}}"{{this}}"{{/if}}{{/each}} ]"""; 25 | var compiled = _handlebarsContext.Compile(template); 26 | var data = new 27 | { 28 | data = new[] { "text1", "text2", "text3" } 29 | }; 30 | 31 | // Act 32 | var result = compiled(data); 33 | 34 | // Assert 35 | result.Should().Be("""[ "text1", "text2", "text3" ]"""); 36 | } 37 | 38 | [Theory] 39 | [InlineData(true, "False")] 40 | [InlineData(false, "True")] 41 | public void Not(bool value, string expected) 42 | { 43 | // Arrange 44 | var template = "{{[Boolean.Not] value}}"; 45 | var compiled = _handlebarsContext.Compile(template); 46 | var data = new 47 | { 48 | value = value 49 | }; 50 | 51 | // Act 52 | var result = compiled(data); 53 | 54 | // Assert 55 | result.Should().Be(expected); 56 | } 57 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/ConstantsHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Enums; 4 | using Xunit; 5 | 6 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 7 | 8 | public class ConstantsHelpersTemplateTests 9 | { 10 | private readonly IHandlebars _handlebarsContext; 11 | 12 | public ConstantsHelpersTemplateTests() 13 | { 14 | _handlebarsContext = Handlebars.Create(); 15 | _handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture; 16 | 17 | HandlebarsHelpers.Register(_handlebarsContext, Category.Constants); 18 | } 19 | 20 | [Theory] 21 | [InlineData("{{[Constants.Math.PI]}}", "3.14")] 22 | [InlineData("{{[Constants.Math.E]}}", "2.71")] 23 | public void Constants(string template, string expected) 24 | { 25 | // Arrange 26 | var action = _handlebarsContext.Compile(template); 27 | 28 | // Act 29 | var result = action(""); 30 | 31 | // Assert 32 | result.Should().StartWith(expected); 33 | } 34 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/EnumerableHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | using FluentAssertions; 4 | using HandlebarsDotNet.Helpers.Enums; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 8 | 9 | public class EnumerableHelpersTemplateTests 10 | { 11 | private readonly IHandlebars _handlebarsContext; 12 | 13 | public EnumerableHelpersTemplateTests() 14 | { 15 | _handlebarsContext = Handlebars.Create(); 16 | _handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture; 17 | 18 | HandlebarsHelpers.Register(_handlebarsContext, Category.Enumerable); 19 | } 20 | 21 | [Theory] 22 | [InlineData(null, "0")] 23 | [InlineData(new object[] { }, "0")] 24 | [InlineData(new object[] { "a", "b", "c" }, "3")] 25 | public void Count(IEnumerable? values, string expected) 26 | { 27 | // Arrange 28 | var template = "{{[Enumerable.Count] values}}"; 29 | var compiled = _handlebarsContext.Compile(template); 30 | var data = new 31 | { 32 | values = values 33 | }; 34 | 35 | // Act 36 | var result = compiled(data); 37 | 38 | // Assert 39 | result.Should().Be(expected); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/EnvironmentHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using HandlebarsDotNet.Helpers.Enums; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 8 | 9 | public class EnvironmentHelpersTemplateTests 10 | { 11 | private readonly IHandlebars _handlebarsContext; 12 | 13 | public EnvironmentHelpersTemplateTests() 14 | { 15 | _handlebarsContext = Handlebars.Create(); 16 | 17 | HandlebarsHelpers.Register(_handlebarsContext, o => 18 | { 19 | o.UseCategoryPrefix = false; 20 | o.Categories = o.Categories.Concat([Category.Environment]).ToArray(); 21 | }); 22 | } 23 | 24 | [Fact] 25 | public void GetEnvironmentVariable_NonExisting() 26 | { 27 | // Arrange 28 | var variable = Guid.NewGuid().ToString(); 29 | var template = $"{{{{GetEnvironmentVariable \"{variable}\"}}}}"; 30 | var compiled = _handlebarsContext.Compile(template); 31 | var data = new { }; 32 | 33 | // Act 34 | var result = compiled(data); 35 | 36 | // Assert 37 | result.Should().Be(""); 38 | } 39 | 40 | [Fact] 41 | public void GetEnvironmentVariable_Existing() 42 | { 43 | // Arrange 44 | var variable = Guid.NewGuid().ToString(); 45 | var value = Guid.NewGuid().ToString(); 46 | try 47 | { 48 | Environment.SetEnvironmentVariable(variable, value); 49 | 50 | var template = $"{{{{GetEnvironmentVariable \"{variable}\"}}}}"; 51 | var compiled = _handlebarsContext.Compile(template); 52 | var data = new { }; 53 | 54 | // Act 55 | var result = compiled(data); 56 | 57 | // Assert 58 | result.Should().Be(value); 59 | 60 | } 61 | finally 62 | { 63 | Environment.SetEnvironmentVariable(variable, null); 64 | } 65 | } 66 | 67 | [Fact] 68 | public void GetEnvironmentVariable_CategoryNotDefined_ShouldThrowException() 69 | { 70 | // Arrange 71 | var handlebarsContext = Handlebars.Create(); 72 | HandlebarsHelpers.Register(handlebarsContext, o => 73 | { 74 | o.UseCategoryPrefix = false; 75 | }); 76 | 77 | var variable = Guid.NewGuid().ToString(); 78 | var template = $"{{{{GetEnvironmentVariable \"{variable}\"}}}}"; 79 | var compiled = handlebarsContext.Compile(template); 80 | var data = new { }; 81 | 82 | // Act 83 | Action act = () => compiled(data); 84 | 85 | // Assert 86 | act.Should().Throw().WithMessage("Template references a helper that cannot be resolved. Helper 'GetEnvironmentVariable'"); 87 | } 88 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/EvaluateHelperTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FluentAssertions; 6 | using HandlebarsDotNet.Helpers.Extensions; 7 | using HandlebarsDotNet.Helpers.Models; 8 | using Xunit; 9 | 10 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 11 | 12 | public class EvaluateHelperTemplateTests 13 | { 14 | private readonly IHandlebars _handlebarsContext; 15 | 16 | public EvaluateHelperTemplateTests() 17 | { 18 | _handlebarsContext = Handlebars.Create(); 19 | HandlebarsHelpers.Register(_handlebarsContext); 20 | } 21 | 22 | [Theory] 23 | [InlineData("")] 24 | [InlineData("{")] 25 | [InlineData("{{")] 26 | [InlineData("}")] 27 | [InlineData("}}")] 28 | [InlineData("{x}")] 29 | [InlineData("{{x}")] 30 | [InlineData("{x}}")] 31 | public void TryEvaluateInvalid(string template) 32 | { 33 | // Arrange 34 | var data = new { x = 1 }; 35 | 36 | // Act 37 | var isValid = _handlebarsContext.TryEvaluate(template, data, out var result); 38 | 39 | // Assert 40 | isValid.Should().BeFalse(); 41 | result.Should().BeNull(); 42 | } 43 | 44 | [Theory] 45 | [InlineData(@"{{x}}", 'c')] 46 | [InlineData(@"{{x}}", "s")] 47 | [InlineData(@"{{x}}", 1)] 48 | [InlineData(@"{{x}}", int.MaxValue)] 49 | [InlineData(@"{{x}}", long.MaxValue)] 50 | [InlineData(@"{{x}}", float.MaxValue)] 51 | [InlineData(@"{{x}}", double.MaxValue)] 52 | public void TryEvaluateValid(string template, object x) 53 | { 54 | // Arrange 55 | var data = new { x }; 56 | 57 | // Act 58 | var isValid = _handlebarsContext.TryEvaluate(template, data, out var result); 59 | 60 | // Assert 61 | isValid.Should().BeTrue(); 62 | result.Should().Be(x); 63 | } 64 | 65 | [Fact] 66 | public void TryEvaluateDecimal() 67 | { 68 | // Arrange 69 | var x = decimal.MaxValue; 70 | var data = new { x }; 71 | 72 | // Act 73 | var isValid = _handlebarsContext.TryEvaluate(@"{{x}}", data, out var result); 74 | 75 | // Assert 76 | isValid.Should().BeTrue(); 77 | result.Should().Be(x); 78 | } 79 | 80 | [Fact] 81 | public void TryMultiple() 82 | { 83 | // Arrange 84 | var data = new { x = 42 }; 85 | var templates = new[] 86 | { 87 | "{{x}}", // valid 88 | "test {{x}}", // invalid 89 | "{{", // invalid 90 | "", // invalid 91 | "{{x}}" // valid 92 | }; 93 | 94 | // Act 95 | var results = new List(); 96 | foreach (var template in templates) 97 | { 98 | var isEvaluated = _handlebarsContext.TryEvaluate(template, data, out var result); 99 | results.Add(new EvaluateResult { IsEvaluated = isEvaluated, Result = result }); 100 | } 101 | 102 | var joined = string.Join(", ", results.Select(r => $"{r.IsEvaluated}:{r.Result ?? "NULL"}")); 103 | 104 | // Assert 105 | joined.Should().Be("True:42, False:NULL, False:NULL, False:NULL, True:42"); 106 | } 107 | 108 | [Fact] 109 | public void TryMultipleParallel() 110 | { 111 | // Arrange 112 | var data = new { x = 42 }; 113 | var templates = new[] 114 | { 115 | "{{x}}", // valid 116 | "test {{x}}", // invalid 117 | "{{", // invalid 118 | "", // invalid 119 | "{{x}}" // valid 120 | }; 121 | 122 | // Act 123 | var results = new ConcurrentBag(); 124 | Parallel.ForEach(templates, template => 125 | { 126 | var isEvaluated = _handlebarsContext.TryEvaluate(template, data, out var result); 127 | results.Add(new EvaluateResult { IsEvaluated = isEvaluated, Result = result }); 128 | }); 129 | 130 | var numIsEvaluated = results.Count(r => r.IsEvaluated); 131 | 132 | // Assert 133 | numIsEvaluated.Should().Be(2); 134 | } 135 | 136 | #if NET6_0_OR_GREATER 137 | [Fact] 138 | public async Task TryMultipleParallelAsync() 139 | { 140 | // Arrange 141 | var data = new { x = 42 }; 142 | var templates = new[] 143 | { 144 | "{{x}}", // valid 145 | "test {{x}}", // invalid 146 | "{{", // invalid 147 | "", // invalid 148 | "{{x}}" // valid 149 | }; 150 | 151 | // Act 152 | var results = new ConcurrentBag(); 153 | await Parallel.ForEachAsync(templates, (template, ct) => 154 | { 155 | var isEvaluated = _handlebarsContext.TryEvaluate(template, data, out var result); 156 | results.Add(new EvaluateResult { IsEvaluated = isEvaluated, Result = result }); 157 | 158 | return ValueTask.CompletedTask; 159 | }); 160 | 161 | var numIsEvaluated = results.Count(r => r.IsEvaluated); 162 | 163 | // Assert 164 | numIsEvaluated.Should().Be(2); 165 | } 166 | #endif 167 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/HumanizerHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using FluentAssertions; 4 | using HandlebarsDotNet.Helpers.Utils; 5 | using Moq; 6 | using Xunit; 7 | 8 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 9 | 10 | public class HumanizerHelpersTemplateTests 11 | { 12 | private readonly DateTime DateTimeNow = new DateTime(2020, 4, 15, 23, 59, 58); 13 | 14 | private readonly Mock _dateTimeServiceMock; 15 | 16 | private readonly IHandlebars _handlebarsContext; 17 | 18 | public HumanizerHelpersTemplateTests() 19 | { 20 | _dateTimeServiceMock = new Mock(); 21 | _dateTimeServiceMock.Setup(d => d.Now()).Returns(DateTimeNow); 22 | _dateTimeServiceMock.Setup(d => d.UtcNow()).Returns(DateTimeNow.ToUniversalTime); 23 | 24 | _handlebarsContext = Handlebars.Create(); 25 | _handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture; 26 | 27 | HandlebarsHelpers.Register(_handlebarsContext, o => 28 | { 29 | o.DateTimeService = _dateTimeServiceMock.Object; 30 | }); 31 | } 32 | 33 | [Fact] 34 | public void HumanizeDateTime() 35 | { 36 | var template = $"{{{{[Humanizer.Humanize] \"{DateTime.UtcNow.AddDays(-1):O}\" }}}}"; 37 | 38 | // Arrange 39 | var action = _handlebarsContext.Compile(template); 40 | 41 | // Act 42 | var result = action(""); 43 | 44 | // Assert 45 | result.Should().Be("yesterday"); 46 | } 47 | 48 | [Theory] 49 | [InlineData("{{[Humanizer.Humanize] \"HTML\"}}", "HTML")] 50 | [InlineData("{{[Humanizer.Humanize] \"PascalCaseInputStringIsTurnedIntoSentence\"}}", "Pascal case input string is turned into sentence")] 51 | public void HumanizeString(string template, string expected) 52 | { 53 | // Arrange 54 | var action = _handlebarsContext.Compile(template); 55 | 56 | // Act 57 | var result = action(""); 58 | 59 | // Assert 60 | result.Should().Be(expected); 61 | } 62 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/JsonPathHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using HandlebarsDotNet.Helpers.Enums; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 8 | 9 | public class JsonPathHelpersTemplateTests 10 | { 11 | private readonly IHandlebars _handlebarsContext; 12 | 13 | public JsonPathHelpersTemplateTests() 14 | { 15 | _handlebarsContext = Handlebars.Create(); 16 | 17 | HandlebarsHelpers.Register(_handlebarsContext, Category.JsonPath); 18 | } 19 | 20 | [Fact] 21 | public void SelectToken() 22 | { 23 | // Arrange 24 | var request = new 25 | { 26 | body = "{ \"Price\": 99 }" 27 | }; 28 | 29 | var action = _handlebarsContext.Compile("{{JsonPath.SelectToken body \"..Price\"}}"); 30 | 31 | // Act 32 | var result = action(request); 33 | 34 | // Assert 35 | int.Parse(result).Should().Be(99); 36 | } 37 | 38 | [Fact] 39 | public void SelectToken_WithComplexTemplate() 40 | { 41 | // Arrange 42 | const string requestAsJson = """ 43 | { 44 | "bodyAsJson": { 45 | "pricingContext": { 46 | "market": "US" 47 | } 48 | } 49 | } 50 | """; 51 | var request = JsonConvert.DeserializeObject(requestAsJson); 52 | 53 | // Use single quotes for the JsonPath else it will be parsed correctly by Handlebars.Net 54 | var action = _handlebarsContext.Compile("{\r\n \"market\": \"{{JsonPath.SelectToken bodyAsJson '$.pricingContext.market'}}\",\r\n \"languages\": \"en\"\r\n}"); 55 | 56 | // Act 57 | var result = action(request); 58 | 59 | // Assert 60 | var expected = """ 61 | { 62 | "market": "US", 63 | "languages": "en" 64 | } 65 | """; 66 | result.Should().Be(expected); 67 | } 68 | 69 | [Fact(Skip = "https://github.com/Handlebars-Net/Handlebars.Net/issues/584")] 70 | public void SelectToken_WithComplexTemplateFails() 71 | { 72 | // Arrange 73 | const string requestAsJson = """ 74 | { 75 | "bodyAsJson": { 76 | "pricingContext": { 77 | "market": "US" 78 | } 79 | } 80 | } 81 | """; 82 | var request = JsonConvert.DeserializeObject(requestAsJson); 83 | 84 | // Use single quotes for the JsonPath else it will be parsed correctly by Handlebars.Net 85 | var action = _handlebarsContext.Compile("{\r\n \"market\": \"{{JsonPath.SelectToken bodyAsJson \\\"$.pricingContext.market\\\"}}\",\r\n \"languages\": \"en\"\r\n}"); 86 | 87 | // Act 88 | var result = action(request); 89 | 90 | // Assert 91 | var expected = """ 92 | { 93 | "market": "US", 94 | "languages": "en" 95 | } 96 | """; 97 | result.Should().Be(expected); 98 | } 99 | 100 | [Fact] 101 | public void SelectTokens_With_JObject() 102 | { 103 | // Arrange 104 | var request = new 105 | { 106 | body = JObject.Parse(@"{ 107 | 'Stores': [ 108 | 'Lambton Quay', 109 | 'Willis Street' 110 | ], 111 | 'Manufacturers': [ 112 | { 113 | 'Name': 'Acme Co', 114 | 'Products': [ 115 | { 116 | 'Name': 'Anvil', 117 | 'Price': 50 118 | } 119 | ] 120 | }, 121 | { 122 | 'Name': 'Contoso', 123 | 'Products': [ 124 | { 125 | 'Name': 'Elbow Grease', 126 | 'Price': 99.95 127 | }, 128 | { 129 | 'Name': 'Headlight Fluid', 130 | 'Price': 4 131 | } 132 | ] 133 | } 134 | ] 135 | }") 136 | }; 137 | 138 | var action = _handlebarsContext.Compile("{{#JsonPath.SelectTokens body \"$..Products[?(@.Price >= 50)].Name\"}}{{#each this}}%{{@index}}:{{this}}%{{/each}}{{/JsonPath.SelectTokens}}"); 139 | 140 | // Act 141 | var result = action(request); 142 | 143 | // Assert 144 | result.Should().Be("%0:Anvil%%1:Elbow Grease%"); 145 | } 146 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/MathHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using FluentAssertions; 4 | using HandlebarsDotNet.Helpers.Enums; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 8 | 9 | public class MathHelpersTemplateTests 10 | { 11 | private readonly IHandlebars _handlebarsContext; 12 | 13 | public MathHelpersTemplateTests() 14 | { 15 | _handlebarsContext = Handlebars.Create(); 16 | _handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture; 17 | 18 | HandlebarsHelpers.Register(_handlebarsContext, Category.Math); 19 | } 20 | 21 | [Theory] 22 | [InlineData("{{[Math.Add] 1 2}}", "3")] 23 | [InlineData("{{[Math.Add] 2.2 3.1}}", "5.3")] 24 | public void Add(string template, string expected) 25 | { 26 | // Arrange 27 | var action = _handlebarsContext.Compile(template); 28 | 29 | // Act 30 | var result = action(""); 31 | 32 | // Assert 33 | result.Should().StartWith(expected); 34 | } 35 | 36 | [Fact] 37 | public void Add_ThrowOnUnresolvedBindingExpression_False() 38 | { 39 | // Arrange 40 | var handlebarsContext = Handlebars.Create(); 41 | handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture; 42 | handlebarsContext.Configuration.ThrowOnUnresolvedBindingExpression = false; 43 | 44 | HandlebarsHelpers.Register(handlebarsContext, Category.Math); 45 | var action = handlebarsContext.Compile("{{[Math.Add] viewData.page 42}}"); 46 | var viewData = new 47 | { 48 | abc = "xyz" 49 | }; 50 | 51 | // Act 52 | var result = action(viewData); 53 | 54 | // Assert 1 55 | result.Should().Be("42"); 56 | } 57 | 58 | [Theory] 59 | [InlineData("{{[Math.LessThan] 2 1}}", "False")] 60 | [InlineData("{{[Math.LessThan] 1 2}}", "True")] 61 | [InlineData("{{[Math.LessThan] 2.2 3.1}}", "True")] 62 | public void LessThan(string template, string expected) 63 | { 64 | // Arrange 65 | var action = _handlebarsContext.Compile(template); 66 | 67 | // Act 68 | var result = action(""); 69 | 70 | // Assert 71 | result.Should().Be(expected); 72 | } 73 | 74 | [Theory] 75 | [InlineData("{{Math.Subtract 100 1}}", "99")] 76 | [InlineData("{{Math.Subtract 101.1 1}}", "100.1")] 77 | [InlineData("{{Math.Subtract 101 0.9}}", "100.1")] 78 | public void Subtract(string template, string expected) 79 | { 80 | // Arrange 81 | var action = _handlebarsContext.Compile(template); 82 | 83 | // Act 84 | var result = action(""); 85 | 86 | // Assert 87 | result.Should().Be(expected); 88 | } 89 | 90 | [Fact] 91 | public void Subtract_With_InvalidArguments_Should_Throw() 92 | { 93 | // Arrange 94 | var compiled = _handlebarsContext.Compile("{{Math.Subtract 100 ''}}"); 95 | 96 | // Act 97 | Action action = () => compiled(""); 98 | 99 | // Assert 100 | action.Should().Throw(); 101 | } 102 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/ObjectHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using CultureAwareTesting.xUnit; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 8 | 9 | public class ObjectHelpersTemplateTests 10 | { 11 | private readonly IHandlebars _handlebarsContext; 12 | 13 | public ObjectHelpersTemplateTests() 14 | { 15 | _handlebarsContext = Handlebars.Create(); 16 | _handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture; 17 | 18 | HandlebarsHelpers.Register(_handlebarsContext, o => 19 | { 20 | }); 21 | } 22 | 23 | [CulturedFact("en-us")] 24 | public void ToString_WithInt() 25 | { 26 | // Arrange 27 | var model = new 28 | { 29 | x = 123.456 30 | }; 31 | 32 | var template = "{{[Object].ToString x }}"; 33 | 34 | var action = _handlebarsContext.Compile(template); 35 | 36 | // Act 37 | var result = action(model); 38 | 39 | // Assert 40 | result.Should().Be("123.456"); 41 | } 42 | 43 | [CulturedTheory("en-us")] 44 | [InlineData("{{[Object].Equal value1 value2 }}", false)] 45 | [InlineData("{{[Object].NotEqual value1 value2 }}", true)] 46 | [InlineData("{{[Object].GreaterThan value1 value2 }}", true)] 47 | [InlineData("{{[Object].GreaterThanEqual value1 value2 }}", true)] 48 | [InlineData("{{[Object].LowerThan value1 value2 }}", false)] 49 | [InlineData("{{[Object].LowerThanEqual value1 value2 }}", false)] 50 | [InlineData("{{[Object].CompareTo value1 value2}}", 1)] 51 | public void Compare(string template, object expected) 52 | { 53 | // Arrange 54 | var model = new 55 | { 56 | value1 = DateTime.Parse("2000-01-01"), 57 | value2 = DateTime.Parse("1999-01-01"), 58 | }; 59 | 60 | var action = _handlebarsContext.Compile(template); 61 | 62 | // Act 63 | var result = action(model); 64 | 65 | // Assert 66 | result.Should().Be(expected.ToString()); 67 | } 68 | 69 | [Fact] 70 | public void IsNull() 71 | { 72 | // Arrange 73 | var model = new 74 | { 75 | value = default(object) 76 | }; 77 | 78 | var template = "{{[Object].IsNull value}}"; 79 | 80 | var action = _handlebarsContext.Compile(template); 81 | 82 | // Act 83 | var result = action(model); 84 | 85 | // Assert 86 | result.Should().Be("True"); 87 | } 88 | 89 | [Fact] 90 | public void IsNotNull() 91 | { 92 | // Arrange 93 | var model = new 94 | { 95 | value = default(object) 96 | }; 97 | 98 | var template = "{{[Object].IsNotNull value}}"; 99 | 100 | var action = _handlebarsContext.Compile(template); 101 | 102 | // Act 103 | var result = action(model); 104 | 105 | // Assert 106 | result.Should().Be("False"); 107 | } 108 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/PathHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | using FluentAssertions; 4 | using Newtonsoft.Json.Linq; 5 | using Xunit; 6 | 7 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 8 | 9 | public class PathHelpersTemplateTests 10 | { 11 | private readonly IHandlebars _handlebarsContext; 12 | 13 | public PathHelpersTemplateTests() 14 | { 15 | _handlebarsContext = Handlebars.Create(); 16 | _handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture; 17 | 18 | HandlebarsHelpers.Register(_handlebarsContext); 19 | } 20 | 21 | [Theory] 22 | [InlineData("{{[Lookup] data '_1' }}", "one")] 23 | [InlineData("{{[Lookup] data '_2' }}", "two")] 24 | [InlineData("{{[Lookup] data '_3' }}", "")] 25 | [InlineData("{{[Lookup] data '_3' 'not found'}}", "not found")] 26 | public void Lookup_Object(string template, string expected) 27 | { 28 | // Arrange 29 | var dictionary = new 30 | { 31 | _1 = "one", 32 | _2 = "two" 33 | }; 34 | var model = new 35 | { 36 | data = dictionary 37 | }; 38 | 39 | var action = _handlebarsContext.Compile(template); 40 | 41 | // Act 42 | var result = action(model); 43 | 44 | // Assert 45 | result.Should().Be(expected); 46 | } 47 | 48 | [Theory] 49 | [InlineData("{{[Lookup] data '1' }}", "one")] 50 | [InlineData("{{[Lookup] data '2' }}", "two")] 51 | [InlineData("{{[Lookup] data '3' }}", "")] 52 | [InlineData("{{[Lookup] data '3' 'not found'}}", "not found")] 53 | public void Lookup_Dictionary(string template, string expected) 54 | { 55 | // Arrange 56 | var dictionary = new Dictionary 57 | { 58 | { "1", "one" }, 59 | { "2", "two" } 60 | }; 61 | var model = new 62 | { 63 | data = dictionary 64 | }; 65 | 66 | var action = _handlebarsContext.Compile(template); 67 | 68 | // Act 69 | var result = action(model); 70 | 71 | // Assert 72 | result.Should().Be(expected); 73 | } 74 | 75 | [Theory] 76 | [InlineData("{{[Lookup] data '1' }}", "one")] 77 | [InlineData("{{[Lookup] data '2' }}", "two")] 78 | [InlineData("{{[Lookup] data '3' }}", "")] 79 | [InlineData("{{[Lookup] data '3' 'not found'}}", "not found")] 80 | public void Lookup_JObject(string template, string expected) 81 | { 82 | // Arrange 83 | var jObject = new JObject 84 | { 85 | { "1", "one" }, 86 | { "2", "two" } 87 | }; 88 | var model = new 89 | { 90 | data = jObject 91 | }; 92 | 93 | var action = _handlebarsContext.Compile(template); 94 | 95 | // Act 96 | var result = action(model); 97 | 98 | // Assert 99 | result.Should().Be(expected); 100 | } 101 | 102 | #if NET6_0_OR_GREATER 103 | [Theory] 104 | [InlineData("{{[Lookup] data '1' }}", "one")] 105 | [InlineData("{{[Lookup] data '2' }}", "two")] 106 | [InlineData("{{[Lookup] data '3' }}", "")] 107 | [InlineData("{{[Lookup] data '3' 'not found'}}", "not found")] 108 | public void Lookup_JsonObject(string template, string expected) 109 | { 110 | // Arrange 111 | var jsonObject = new System.Text.Json.Nodes.JsonObject 112 | { 113 | { "1", "one" }, 114 | { "2", "two" } 115 | }; 116 | var model = new 117 | { 118 | data = jsonObject 119 | }; 120 | 121 | var action = _handlebarsContext.Compile(template); 122 | 123 | // Act 124 | var result = action(model); 125 | 126 | // Assert 127 | result.Should().Be(expected); 128 | } 129 | #endif 130 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/RandomHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using HandlebarsDotNet.Helpers.Enums; 3 | using Xunit; 4 | 5 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 6 | 7 | public class RandomHelpersTemplateTests 8 | { 9 | private readonly IHandlebars _handlebarsContext; 10 | 11 | public RandomHelpersTemplateTests() 12 | { 13 | _handlebarsContext = Handlebars.Create(); 14 | 15 | HandlebarsHelpers.Register(_handlebarsContext, Category.Random); 16 | } 17 | 18 | [Fact] 19 | public void Random_Integer() 20 | { 21 | // Arrange 22 | var action = _handlebarsContext.Compile("{{Random.Generate Type=\"Integer\" Min=1000 Max=9999}}"); 23 | 24 | // Act 25 | var result = action(""); 26 | 27 | // Assert 28 | int.Parse(result).Should().BeInRange(1000, 9999); 29 | } 30 | 31 | [Fact] 32 | public void Random_Long1() 33 | { 34 | // Arrange 35 | var action = _handlebarsContext.Compile("{{Random.Generate Type=\"Long\" Min=1000000000 Max=9999999999}}"); 36 | 37 | // Act 38 | var result = action(""); 39 | 40 | // Assert 41 | long.Parse(result).Should().BeInRange(1000000000, 9999999999); 42 | } 43 | 44 | [Fact] 45 | public void Random_Long2() 46 | { 47 | // Arrange 48 | var action = _handlebarsContext.Compile("{{#Random Type=\"Long\" Min=1000000000 Max=9999999999}}{{this}}{{/Random}}"); 49 | 50 | // Act 51 | var result = action(""); 52 | 53 | // Assert 54 | long.Parse(result).Should().BeInRange(1000000000, 9999999999); 55 | } 56 | 57 | [Fact] 58 | public void Random_StringList() 59 | { 60 | // Arrange 61 | var action = _handlebarsContext.Compile("{{Random.Generate Type=\"StringList\" Values=[\"a\", \"b\", \"c\"]}}"); 62 | 63 | // Act 64 | var result = action(""); 65 | 66 | // Assert 67 | result.Should().NotBeEmpty("a"); 68 | } 69 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/RegexHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Enums; 4 | using Xunit; 5 | 6 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 7 | 8 | public class RegexHelpersTemplateTests 9 | { 10 | private readonly IHandlebars _handlebarsContext; 11 | 12 | public RegexHelpersTemplateTests() 13 | { 14 | _handlebarsContext = Handlebars.Create(); 15 | 16 | HandlebarsHelpers.Register(_handlebarsContext, Category.Regex); 17 | } 18 | 19 | [Theory] 20 | [InlineData("{{#Regex.IsMatch \"Hello\" \"Hello\"}}yes{{else}}no{{/Regex.IsMatch}}", "yes")] 21 | [InlineData("{{#Regex.IsMatch \"Hello\" \"x\"}}yes{{else}}no{{/Regex.IsMatch}}", "no")] 22 | [InlineData("{{#Regex.IsMatch \"Hello\" \"Hello\"}}TRUE{{else}}FALSE{{/Regex.IsMatch}}", "TRUE")] 23 | [InlineData("{{#Regex.IsMatch \"Hello\" \"x\"}}TRUE{{else}}FALSE{{/Regex.IsMatch}}", "FALSE")] 24 | [InlineData("{{#Regex.IsMatch \"Hello\" \"Hello\"}}{{.}}{{else}}no{{/Regex.IsMatch}}", "True")] 25 | public void BlockHelper_IsMatch(string template, string expected) 26 | { 27 | // Arrange 28 | var action = _handlebarsContext.Compile(template); 29 | 30 | // Act 31 | var result = action(""); 32 | 33 | // Assert 34 | result.Should().Be(expected); 35 | } 36 | 37 | [Theory] 38 | [InlineData("{{#if (Regex.IsMatch \"Hello\" \"Hello\")}}this is true{{/if}}", "this is true")] 39 | [InlineData("{{#if (Regex.IsMatch \"Hello\" \"x\")}}this is true{{/if}}", "")] 40 | public void NormalHelper_IsMatch_With_If(string template, string expected) 41 | { 42 | // Arrange 43 | var action = _handlebarsContext.Compile(template); 44 | 45 | // Act 46 | var result = action(""); 47 | 48 | // Assert 49 | result.Should().Be(expected); 50 | } 51 | 52 | [Fact] 53 | public void NormalHelper_IsMatch_With_If_And_ComplexObject() 54 | { 55 | // Arrange 56 | var template = "{{#if (#Regex.IsMatch Attributes 'visible')}}{{Value}}{{/if}}"; 57 | var context = new Dictionary() 58 | { 59 | ["Attributes"] = "visible", 60 | ["Value"] = "SomeString" 61 | }; 62 | var action = _handlebarsContext.Compile(template); 63 | 64 | // Act 65 | var result = action(context); 66 | 67 | // Assert 68 | result.Should().Be("SomeString"); 69 | } 70 | 71 | [Theory] 72 | [InlineData("{{#Regex.IsMatch thing 'foo' 'i'}}Yes{{/Regex.IsMatch}}", "Yes")] 73 | [InlineData("{{#Regex.IsMatch thing 'foo'}}Yes{{/Regex.IsMatch}}", "")] 74 | [InlineData("{{#Regex.IsMatch thing 'bar' 'i'}}Yes{{/Regex.IsMatch}}", "")] 75 | public void NormalHelper_IsMatch_IgnoreCase(string template, string expected) 76 | { 77 | // Arrange 78 | var action = _handlebarsContext.Compile(template); 79 | var value = new 80 | { 81 | thing = "Foo" 82 | }; 83 | 84 | // Act 85 | var result = action(value); 86 | 87 | // Assert 88 | result.Should().Be(expected); 89 | } 90 | } -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Templates/XsltHelpersTemplateTests.cs: -------------------------------------------------------------------------------- 1 | #if !(NET451 || NET452) 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Enums; 4 | using Xunit; 5 | 6 | namespace HandlebarsDotNet.Helpers.Tests.Templates; 7 | 8 | public class XsltHelpersTemplateTests 9 | { 10 | private readonly IHandlebars _handlebarsContext; 11 | 12 | public XsltHelpersTemplateTests() 13 | { 14 | _handlebarsContext = Handlebars.Create(); 15 | 16 | HandlebarsHelpers.Register(_handlebarsContext, Category.Xslt); 17 | } 18 | 19 | [Fact] 20 | public void TransformToString() 21 | { 22 | // Assign 23 | const string xml = @" 24 | 25 | 26 | "; 27 | 28 | const string xslt = @" 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | "; 44 | 45 | // Arrange 46 | var request = new 47 | { 48 | body = xml 49 | }; 50 | 51 | const string expression = "{{Xslt.TransformToString body \"" + xslt + "\"}}"; 52 | var action = _handlebarsContext.Compile(expression); 53 | 54 | // Act 55 | var result = action(request); 56 | 57 | // Assert 58 | const string expected = @" 59 | 60 | 61 | "; 62 | result.Should().BeEquivalentTo(expected); 63 | } 64 | } 65 | #endif -------------------------------------------------------------------------------- /test/Handlebars.Net.Helpers.Tests/Utils/ArrayUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentAssertions; 3 | using HandlebarsDotNet.Helpers.Utils; 4 | using Xunit; 5 | 6 | namespace HandlebarsDotNet.Helpers.Tests.Utils 7 | { 8 | public class ArrayUtilsTests 9 | { 10 | [Theory] 11 | [InlineData(new object?[] { 1, "c", "str", null }, @"[1,""c"",""str"",null]")] 12 | [InlineData(new object?[] { 1, 'c', "str", null }, @"[1,""c"",""str"",null]")] 13 | public void ToArray(IEnumerable values, string expected) 14 | { 15 | // Act 16 | var result = ArrayUtils.ToArray(values); 17 | 18 | // Assert 19 | result.Should().Be(expected); 20 | } 21 | 22 | [Theory] 23 | [InlineData("", false, null)] 24 | [InlineData("[", false, null)] 25 | [InlineData("[]", true, new object?[] { })] 26 | [InlineData(@"[1,""c"",""str"",null]", true, new object?[] { 1, "c", "str", null })] 27 | public void TryParse(string value, bool valid, object?[] expected) 28 | { 29 | // Act 30 | bool result = ArrayUtils.TryParseAsObjectList(value, out List? parsed); 31 | 32 | // Assert 33 | result.Should().Be(valid); 34 | parsed.Should().BeEquivalentTo(expected); 35 | } 36 | } 37 | } --------------------------------------------------------------------------------