diff --git a/.github/workflows/SonarCloud.yml b/.github/workflows/SonarCloud.yml index 20d12eb6..547f0cbb 100644 --- a/.github/workflows/SonarCloud.yml +++ b/.github/workflows/SonarCloud.yml @@ -13,14 +13,14 @@ jobs: # (PRs from forks can't access secrets other than secrets.GITHUB_TOKEN for security reasons) if: ${{ !github.event.pull_request.head.repo.fork }} env: - version: '3.1.0' - versionFile: '3.1.0' + version: '3.2.1' + versionFile: '3.2.1' steps: - name: Set up JDK 11 uses: actions/setup-java@v1 with: java-version: 1.11 - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Cache SonarCloud packages @@ -60,7 +60,7 @@ jobs: echo "Packing Version: ${{ env.version }}, File Version: ${{ env.versionFile }}" dotnet pack ./src/SmartFormat.Deploy.sln /verbosity:minimal --configuration release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:PackageOutputPath=${{ github.workspace }}/artifacts /p:Version=${{ env.version }} /p:FileVersion=${{ env.versionFile }} - name: Upload Artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Packages_${{ env.version }} path: ${{ github.workspace }}/artifacts/ diff --git a/appveyor.yml b/appveyor.yml index f692b5ab..61fb606e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,7 +23,7 @@ for: - ps: dotnet restore SmartFormat.sln --verbosity quiet - ps: dotnet add .\SmartFormat.Tests\SmartFormat.Tests.csproj package AltCover - ps: | - $version = "3.2.0" + $version = "3.2.1" $versionFile = $version + "." + ${env:APPVEYOR_BUILD_NUMBER} if ($env:APPVEYOR_PULL_REQUEST_NUMBER) { diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c3a8d78d..3736ab5e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -8,8 +8,8 @@ Copyright 2011-$(CurrentYear) SmartFormat Project https://github.com/axuno/SmartFormat.git true - 3.2.0 - 3.2.0 + 3.2.1 + 3.2.1 3.0.0 latest true diff --git a/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs b/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs index ef04688e..6c1e182b 100644 --- a/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs +++ b/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs @@ -350,4 +350,21 @@ public void DoesNotHandle_Bool_WhenCanAutoDetect_IsTrue() var result = smart.Format(new CultureInfo("ar"), "{0:yes|no}", true); Assert.That(result, Is.EqualTo("yes")); } + + [TestCase("A", "[A]")] + [TestCase(default(string?), "null")] + public void DoesNotHandle_NonDecimalValues_WhenCanAutoDetect_IsTrue(string? category, string expected) + { + var smart = new SmartFormatter() + .AddExtensions(new DefaultSource()) + .AddExtensions(new ReflectionSource()) + // Should not handle because "Category" value cannot convert to decimal + .AddExtensions(new PluralLocalizationFormatter { CanAutoDetect = true }, + // Should detect and handle the format + new ConditionalFormatter { CanAutoDetect = true }, + new DefaultFormatter()); + + var result = smart.Format(new CultureInfo("en"), "{Category:[{}]|null}", new { Category = category }); + Assert.That(result, Is.EqualTo(expected)); + } } diff --git a/src/SmartFormat/Extensions/PluralLocalizationFormatter.cs b/src/SmartFormat/Extensions/PluralLocalizationFormatter.cs index 8816e999..ae637b36 100644 --- a/src/SmartFormat/Extensions/PluralLocalizationFormatter.cs +++ b/src/SmartFormat/Extensions/PluralLocalizationFormatter.cs @@ -123,19 +123,24 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo) // We can format numbers, and IEnumerables. For IEnumerables we look at the number of items // in the collection: this means the user can e.g. use the same parameter for both plural and list, for example // 'Smart.Format("The following {0:plural:person is|people are} impressed: {0:list:{}|, |, and}", new[] { "bob", "alice" });' - if (current is IConvertible convertible && current is not bool) - value = convertible.ToDecimal(null); - else if (current is IEnumerable objects) - value = objects.Count(); - else + + switch (current) { - // Auto detection calls just return a failure to evaluate - if (string.IsNullOrEmpty(formattingInfo.Placeholder?.FormatterName)) - return false; + case IConvertible convertible when current is not bool && TryGetDecimalValue(convertible, null, out value): + break; + case IEnumerable objects: + value = objects.Count(); + break; + default: + { + // Auto detection calls just return a failure to evaluate + if (string.IsNullOrEmpty(formattingInfo.Placeholder?.FormatterName)) + return false; - // throw, if the formatter has been called explicitly - throw new FormatException( - $"Formatter named '{formattingInfo.Placeholder?.FormatterName}' can format numbers and IEnumerables, but the argument was of type '{current?.GetType().ToString() ?? "null"}'"); + // throw, if the formatter has been called explicitly + throw new FormatException( + $"Formatter named '{formattingInfo.Placeholder?.FormatterName}' can format numbers and IEnumerables, but the argument was of type '{current?.GetType().ToString() ?? "null"}'"); + } } // Get the specific plural rule, or the default rule: @@ -154,6 +159,20 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo) return true; } + private static bool TryGetDecimalValue(IConvertible convertible, IFormatProvider? provider, out decimal value) + { + try + { + value = convertible.ToDecimal(provider); + return true; + } + catch + { + value = default; + return false; + } + } + private static PluralRules.PluralRuleDelegate GetPluralRule(IFormattingInfo formattingInfo) { // Determine the culture