Skip to content

Commit

Permalink
Merge c7484a4 into 3188ed9
Browse files Browse the repository at this point in the history
  • Loading branch information
axunonb committed Sep 7, 2021
2 parents 3188ed9 + c7484a4 commit 09c092e
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 32 deletions.
23 changes: 23 additions & 0 deletions src/SmartFormat.Tests/Extensions/ChooseFormatterTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using FluentAssertions.Formatting;
using NUnit.Framework;
using SmartFormat.Core.Formatting;
Expand Down Expand Up @@ -101,5 +102,27 @@ public void Choose_throws_when_choices_are_too_few_or_too_many(string format, ob
Assert.Throws<FormattingException>(() => smart.Format(format, arg0));
}

[TestCase(1234, 9999, "1,234.00")]
[TestCase(null, 9999, "9,999.00")]
public void May_Contain_Nested_Formats_As_Choice(int? nullableInt, int valueIfNull, string expected)
{
var data = new { NullableInt = nullableInt, IntValueIfNull = valueIfNull};
var smart = Smart.CreateDefaultSmartFormat();
var result = smart.Format(CultureInfo.InvariantCulture, "{NullableInt:choose(null):{IntValueIfNull:N2}|{:N2}}", data);

Assert.That(result, Is.EqualTo(expected));
}

[TestCase(1234, 9999, "1,234.00")] // first choose formatter
[TestCase(null, 1000, "1k")] // first + nested choose formatter, option 1
[TestCase(null, 2000, "2k")] // first + nested choose formatter, option 2
public void May_Contain_Nested_Choose_Formats(int? nullableInt, int valueIfNull, string expected)
{
var data = new { NullableInt = nullableInt, IntValueIfNull = valueIfNull};
var smart = Smart.CreateDefaultSmartFormat();
var result = smart.Format(CultureInfo.InvariantCulture, "{NullableInt:choose(null):{IntValueIfNull:choose(1000|2000):1k|2k}|{:N2}}", data);

Assert.That(result, Is.EqualTo(expected));
}
}
}
67 changes: 45 additions & 22 deletions src/SmartFormat.Tests/Extensions/NullFormatterTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using SmartFormat.Core.Extensions;
using SmartFormat.Core.Formatting;
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;

Expand Down Expand Up @@ -35,6 +37,14 @@ public void Null_Without_Format_Should_Be_Empty_String_CompatibilityMode()
Assert.That(smart.Format("{JustNull}", new { JustNull = default(object)} ), Is.EqualTo(string.Empty));
}

[TestCase("")] // empty format
[TestCase("something")]
public void NotNull_Should_Be_Empty_String(string format)
{
var smart = GetFormatter();
Assert.That(smart.Format($"{{NotNull:{format}}}", new { NotNull = ""} ), Is.EqualTo(string.Empty));
}

[TestCase("")] // empty format
[TestCase("nothing")]
public void Null_With_Format_Should_Be_Format_String(string format)
Expand All @@ -43,38 +53,51 @@ public void Null_With_Format_Should_Be_Format_String(string format)
Assert.That(smart.Format($"{{JustNull:isnull:{format}}}", new { JustNull = default(object)} ), Is.EqualTo(format));
}

[TestCase("")] // empty format
[TestCase("something")]
public void NotNull_Should_Be_Empty_String(string format)
[TestCase(null, "")]
[TestCase("a string", "")]
[TestCase(new long[] {5, 6, 7}, "")]
public void Empty_FormatOption_Should_Only_Be_Output_If_Argument_Is_Null(object? value, string expected)
{
var smart = GetFormatter();
Assert.That(smart.Format($"{{NotNull:{format}}}", new { NotNull = ""} ), Is.EqualTo(string.Empty));
Assert.That(smart.Format("{TheValue:isnull:}", new {TheValue = value}), Is.EqualTo(expected));
}

[TestCase(null)]
[TestCase("a string")]
[TestCase(new long[] {5, 6, 7})]
public void Format_Should_Only_Be_Output_If_Argument_Is_Null(object? value)
[TestCase(null, "It's null", "It's null")] // Null value and format for null
[TestCase("a string", "It's null|It's a string", "It's a string")] // With format for "not null"
[TestCase("a string", "It's null", "")] // No format for "not null"
[TestCase(new long[] {5, 6, 7}, "It's null|Numbers", "Numbers")] // With format for "not null"
public void FormatOption_Should_Only_Be_Output_If_Argument_Is_Null(object? value, string formats, string expected)
{
var smart = GetFormatter();
Assert.That(smart.Format("{TheValue:isnull:}", new {TheValue = value}), Is.EqualTo(string.Empty));
Assert.That(smart.Format("{TheValue:isnull:nothing}", new {TheValue = value}),
value == null ? Is.EqualTo("nothing") : Is.EqualTo(string.Empty));
Assert.That(smart.Format("{TheValue:isnull:" + formats + "}", new {TheValue = value}), Is.EqualTo(expected));
Assert.That(smart.Format("{0:isnull:" + formats + "}", value), Is.EqualTo(expected));
}

[TestCase(null, "Argument is null")]
[TestCase("Argument has this value", "Argument has this value")]
public void Combine_NullFormatter_With_Other_Formatter(object value, string expected)
[TestCase(null, 0, "Was null")] // first choose formatter
[TestCase(123, 1000, "1k")] // first + nested choose formatter, option 1
[TestCase(123, 2000, "2,000.00")] // first + nested choose formatter, option 2
public void May_Contain_Nested_Formats(int? nullableInt, int valueIfNotNull, string expected)
{
var smart = new SmartFormatter();
smart.AddExtensions(new DefaultSource());
smart.AddExtensions(new NullFormatter(), new DefaultFormatter());

// NullFormatter will output only, if value is null
// DefaultFormatter will render null as string.Empty
var result = smart.Format($"{{0:isnull:{expected}}}{{0}}", value!);

var smart = GetFormatter();
smart.AddExtensions(new ChooseFormatter());
var data = new { NullableInt = nullableInt, IntValueIfNotNull = valueIfNotNull};
var result = smart.Format(CultureInfo.InvariantCulture, "{NullableInt:isnull:Was null|{IntValueIfNotNull:choose(1000|2000):1k|{:N2}}}", data);

Assert.That(result, Is.EqualTo(expected));
}

[Test]
public void NullFormatter_Must_Not_Contain_Choose_Options()
{
var smart = GetFormatter();
Assert.That(() => smart.Format("{0:isnull(op|ti|ons):Is null}", 123), Throws.InstanceOf<FormattingException>());
}

[Test]
public void NullFormatter_Format_Count_Must_Be_1_or_2()
{
var smart = GetFormatter();
Assert.That(() => smart.Format("{0:isnull:1|2|3}", 123), Throws.InstanceOf<FormattingException>(), "No format included");
}
}
}
64 changes: 54 additions & 10 deletions src/SmartFormat/Extensions/NullFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
using System.Collections.Generic;
using System.Text;
using SmartFormat.Core.Extensions;
using SmartFormat.Core.Parsing;

namespace SmartFormat.Extensions
{
/// <summary>
/// The class formats <see langword="null"/> values.
/// </summary>
/// <example>
/// Smart.Format("{0:isnull:It's null}", arg)
/// Smart.Format("{0:isnull:It's null|Not null}", arg)
/// Smart.Format("{0:isnull:It's null|{}}", arg)
/// </example>
public class NullFormatter : IFormatter
{
/// <summary>
Expand All @@ -27,21 +33,59 @@ public class NullFormatter : IFormatter
///<inheritdoc/>
public bool CanAutoDetect { get; set; } = false;

/// <inheritdoc />
/// <summary>
/// Gets or sets the character used to split the option text literals.
/// </summary>
public char SplitChar { get; set; } = '|';

///<inheritdoc />
public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
{
var format = formattingInfo.Format;
var current = formattingInfo.CurrentValue;
var currentValue = formattingInfo.CurrentValue;
var chooseOptions = formattingInfo.FormatterOptions.AsSpan().Trim();
var formats = formattingInfo.Format?.Split(SplitChar);

// Check whether arguments can be handled by this formatter
if (chooseOptions.Length != 0)
{
// 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}' does not allow choose options");
}

if (formats is null || formats.Count is < 1 or > 2)
{
// Auto detection calls just return a failure to evaluate
if (string.IsNullOrEmpty(formattingInfo.Placeholder?.FormatterName))
return false;

switch (current)
// throw, if the formatter has been called explicitly
throw new FormatException(
$"Formatter named '{formattingInfo.Placeholder?.FormatterName}' must have 1 or 2 format options");
}

// Use the format for null
if (currentValue is null)
{
case null when format is not null:
formattingInfo.Write(format.GetLiteralText());
return true;
default:
formattingInfo.Write(string.Empty);
return true;
formattingInfo.FormatAsChild(formats[0], currentValue);
return true;
}

// Use the format for a value other than null
if (formats.Count > 1)
{
formattingInfo.FormatAsChild(formats[1], currentValue);
return true;
}

// There is no format for a value other than null
formattingInfo.Write(ReadOnlySpan<char>.Empty);

return true;
}
}
}

0 comments on commit 09c092e

Please sign in to comment.