Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Char to split options and formats can be changed #243

Merged
merged 4 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ SmartFormat is not a fully-fledged HTML parser. If this is required, use [AngleS
a) Get the culture from the `FormattingInfo.FormatterOptions`.<br/>
b) Get the culture from the `IFormatProvider` argument (which may be a `CultureInfo`) to `SmartFormatter.Format(IFormatProvider, string, object?[])`<br/>
c) The `CultureInfo.CurrentUICulture`<br/>
d) `CultureInfo.InvariantCulture` maps to `CultureInfo.GetCultureInfo("en")` ([#243](https://github.com/axuno/SmartFormat/pull/243))

### 20. Refactored `TimeFormatter` ([#220](https://github.com/axuno/SmartFormat/pull/220), [#221](https://github.com/axuno/SmartFormat/pull/221), [#234](https://github.com/axuno/SmartFormat/pull/234))

Expand Down Expand Up @@ -502,9 +503,24 @@ var formatter = new SmartFormatter()
```

### 24. Miscellaneous
* Since [#228](https://github.com/axuno/SmartFormat/pull/228) there are no more `Cysharp.Text` classes used in the `SmartFormat` namespace
* Created class `ZStringBuilder` as a wrapper around `Utf16ValueStringBuilder`.
* Replaced occurrences of `Utf16ValueStringBuilder` with `ZStringBuilder`.

a) Cyhsarp.Text

Since [#228](https://github.com/axuno/SmartFormat/pull/228) there are no more `Cysharp.Text` classes used in the `SmartFormat` namespace
* Created class `ZStringBuilder` as a wrapper around `Utf16ValueStringBuilder`.
* Replaced occurrences of `Utf16ValueStringBuilder` with `ZStringBuilder`.

b) Split character for options and formats

Since [#243](https://github.com/axuno/SmartFormat/pull/243) the character to split options and formats can be changed. This allows having the default split character `|` as part of the output string.
Affects `ChooseFormatter`, `ConditionalFormatter`, `IsMatchFormatter`, `ListFormatter`, `PluralLocalizationFormatter`, `SubStringFormatter`. Example:
```Csharp
var smart = Smart.CreateDefaultSmartFormat();
// Change SplitChar from | to TAB, so we can use | for the output string
smart.GetFormatterExtension<ConditionalFormatter>()!.SplitChar = '\t';
_ = smart.Format({0:cond:|No|\t|Yes|}", 1);
// Result: "|Yes|"
```

v2.7.2
===
Expand Down
13 changes: 12 additions & 1 deletion src/SmartFormat.Tests/Extensions/ChooseFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using NUnit.Framework;
using SmartFormat.Core.Formatting;
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;
using SmartFormat.Tests.TestUtils;

namespace SmartFormat.Tests.Extensions
Expand Down Expand Up @@ -33,6 +34,16 @@ public void Choose_should_work_with_numbers_strings_and_booleans(string format,
Assert.AreEqual(expectedResult, smart.Format(format, arg0));
}

[Test]
public void Choose_With_Changed_SplitChar()
{
var smart = Smart.CreateDefaultSmartFormat();
// Set SplitChar from | to TAB, so we can use | for the output string
smart.GetFormatterExtension<ChooseFormatter>()!.SplitChar = '\t';
var result = smart.Format("{0:choose(1\t2\t3):|one|\t|two|\t|three|}", 2);
Assert.That(result, Is.EqualTo("|two|"));
}

[TestCase("{0:choose(true|True):one|two|default}", true, "two")]
[TestCase("{0:choose(true|TRUE):one|two|default}", true, "default")]
[TestCase("{0:choose(string|String):one|two|default}", "String", "two")]
Expand Down Expand Up @@ -123,4 +134,4 @@ public void May_Contain_Nested_Choose_Formats(int? nullableInt, int valueIfNull,
Assert.That(result, Is.EqualTo(expected));
}
}
}
}
12 changes: 12 additions & 0 deletions src/SmartFormat.Tests/Extensions/ConditionalFormatterTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using NUnit.Framework;
using SmartFormat.Core.Formatting;
using SmartFormat.Extensions;
using SmartFormat.Tests.TestUtils;
using SmartFormat.Utilities;

Expand Down Expand Up @@ -38,6 +39,17 @@ public void Test_Bool(string format, string expected)
smart.Test(format, args, expected);
}

[TestCase("{0:cond:|Yes|\t|No|}", 0, "|Yes|")]
[TestCase("{0:cond:|Yes|\t|No|}", 1, "|No|")]
public void Test_With_Changed_SplitChar(string format, int arg, string expected)
{
var smart = Smart.CreateDefaultSmartFormat();
// Set SplitChar from | to TAB, so we can use | for the output string
smart.GetFormatterExtension<ConditionalFormatter>()!.SplitChar = '\t';
var result = smart.Format(format, arg);
Assert.That(result, Is.EqualTo(expected));
}

[Test]
public void Explicit_Formatter_With_Not_Enough_Parameters_Should_Throw()
{
Expand Down
15 changes: 13 additions & 2 deletions src/SmartFormat.Tests/Extensions/IsMatchFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,19 @@ public void Less_Than_2_Format_Options_Should_Throw()
// less than 2 format options should throw exception
Assert.Throws<FormattingException>(() =>
_formatter.Format("{theKey:ismatch(^.+123.+$):Dummy content}", _variable));
Assert.DoesNotThrow(() =>
_formatter.Format("{theKey:ismatch(^.+123.+$):Dummy content|2nd option}", _variable));
}

// The "{}" in the format will (as always) output the matching variable
[TestCase("{theKey:ismatch(^.+123.+$):|Has match for '{}'|\t|No match|}", "|Has match for 'Some123Content'|")]
[TestCase("{theKey:ismatch(^.+999.+$):|Has match for '{}'|\t|No match|}", "|No match|")]
public void Test_With_Changed_SplitChar(string format, string expected)
{
var variable = new Dictionary<string, object> { {"theKey", "Some123Content"}};
var smart = Smart.CreateDefaultSmartFormat();
// Set SplitChar from | to TAB, so we can use | for the output string
smart.GetFormatterExtension<IsMatchFormatter>()!.SplitChar = '\t';
var result = smart.Format(format, variable);
Assert.That(result, Is.EqualTo(expected));
}

[Test]
Expand Down
11 changes: 11 additions & 0 deletions src/SmartFormat.Tests/Extensions/ListFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ public void Simple_List()
Assert.AreEqual("one, two, and three", result);
}

[Test]
public void Simple_List_Changed_SplitChar()
{
var smart = Smart.CreateDefaultSmartFormat();
// Set SplitChar from | to TAB, so we can use | for the output string
smart.GetFormatterExtension<ListFormatter>()!.SplitChar = '\t';
var items = new[] { "one", "two", "three" };
var result = smart.Format("{0:list:{}\t|\t|}", new object[] { items }); // important: not only "items" as the parameter
Assert.AreEqual("one|two|three", result);
}

[Test]
public void Empty_List()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,5 +278,23 @@ public void Nested_PlaceHolders_Pluralization(int numOfPeople, string format, bo

Assert.That(result, numOfPeople == 1 ? Is.EqualTo("There is a person.") : Is.EqualTo("There are 2 people."));
}

[TestCase(1, "There {People.Count:plural:|is a person|.\t|are {} people|.}", "There |is a person|.")]
[TestCase(2, "There {People.Count:plural:|is a person|.\t|are {} people|.}", "There |are 2 people|.")]
public void Pluralization_With_Changed_SplitChar(int numOfPeople, string format, string expected)
{
var data = numOfPeople == 1
? new {People = new List<object> {new {Name = "Name 1", Age = 20}}}
: new {People = new List<object> {new {Name = "Name 1", Age = 20}, new {Name = "Name 2", Age = 30}}};

var smart = new SmartFormatter()
.AddExtensions(new ReflectionSource())
// Set SplitChar from | to TAB, so we can use | for the output string
.AddExtensions(new PluralLocalizationFormatter { SplitChar = '\t'},
new DefaultFormatter());

var result = smart.Format(format, data);
Assert.That(result, Is.EqualTo(expected));
}
}
}
16 changes: 13 additions & 3 deletions src/SmartFormat.Tests/Extensions/SubStringFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,18 @@ public void DataItemIsNull()
var smart = GetFormatter();
var ssf = smart.GetFormatterExtension<SubStringFormatter>();
ssf!.NullDisplayString = "???";
ssf!.ParameterDelimiter = '*';
Assert.AreEqual(ssf.NullDisplayString, smart.Format("{Name:substr(0*3)}", new Dictionary<string, string?> { { "Name", null } }));
Assert.AreEqual(ssf.NullDisplayString, smart.Format("{Name:substr(0,3)}", new Dictionary<string, string?> { { "Name", null } }));
}

[Test]
public void Test_With_Changed_SplitChar()
{
var smart = GetFormatter();
var currentSplitChar = smart.GetFormatterExtension<SubStringFormatter>()!.SplitChar;
// Change SplitChar from default ',' to '|'
smart.GetFormatterExtension<SubStringFormatter>()!.SplitChar = '|';
Assert.AreEqual("Joh", smart.Format("{Name:substr(-4|-1)}", _people.First()));
Assert.That(currentSplitChar, Is.EqualTo(',')); // make sure there was a change
}

[Test]
Expand All @@ -165,4 +175,4 @@ public void ImplicitFormatterEvaluation_With_Wrong_Args_Should_Fail()
FormattingInfoExtensions.Create("{0::(0,2)}", new List<object?>(new[] {new object()}))), Is.EqualTo(false));
}
}
}
}
4 changes: 2 additions & 2 deletions src/SmartFormat/Core/Formatting/IFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface IFormatter
/// (when no formatter name is specified in the input format string).
/// For example, "{0:N2}" will implicitly call extensions marked as <see cref="CanAutoDetect"/>.
/// Implicit formatter invocations should not throw exceptions.
/// With <see cref="CanAutoDetect"/>=<see langword="false"/>, the formatter can only be
/// With <see cref="CanAutoDetect"/> == <see langword="false"/>, the formatter can only be
/// called by its name in the input format string.
/// </summary>
/// <remarks>
Expand All @@ -36,4 +36,4 @@ public interface IFormatter
/// </summary>
bool TryEvaluateFormat(IFormattingInfo formattingInfo);
}
}
}
3 changes: 1 addition & 2 deletions src/SmartFormat/Core/Parsing/Format.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,13 +296,12 @@ public IList<Format> Split(char search)

return _splitCache;
}

/// <summary>
/// Splits the <see cref="Format"/> items by the given search character.
/// </summary>
/// <param name="search">e search character used to split.</param>
/// <param name="maxCount">The maximum number of <see cref="IList"/> of type <see cref="Format"/>.</param>
/// <returns></returns>
/// <returns>An <see cref="IList{T}"/> of <see cref="Format"/>s.</returns>
public IList<Format> Split(char search, int maxCount)
{
var splits = FindAll(search, maxCount);
Expand Down
2 changes: 1 addition & 1 deletion src/SmartFormat/Extensions/ChooseFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ private static Format DetermineChosenFormat(IFormattingInfo formattingInfo, ILis
return chosenFormat;
}
}
}
}
9 changes: 7 additions & 2 deletions src/SmartFormat/Extensions/ConditionalFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ private static readonly Regex _complexConditionPattern

///<inheritdoc/>
public bool CanAutoDetect { get; set; } = true;

/// <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)
Expand All @@ -45,7 +50,7 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
if (format?.BaseString.Length > 0 && format.BaseString[format.StartIndex] == ':') format = format.Substring(1);

// See if the format string contains un-nested "|":
var parameters = format is not null ? format.Split('|') : new List<Format>(0);
var parameters = format is not null ? format.Split(SplitChar) : new List<Format>(0);

// Check whether arguments can be handled by this formatter
if (format is null || parameters.Count == 1)
Expand Down Expand Up @@ -234,4 +239,4 @@ private static bool TryEvaluateCondition(Format parameter, decimal value, out bo
return true;
}
}
}
}
9 changes: 7 additions & 2 deletions src/SmartFormat/Extensions/IsMatchFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@ public class IsMatchFormatter : IFormatter
///<inheritdoc/>
public bool CanAutoDetect { get; set; } = false;

/// <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 expression = formattingInfo.FormatterOptions;
var formats = formattingInfo.Format?.Split('|');
var formats = formattingInfo.Format?.Split(SplitChar);

// Check whether arguments can be handled by this formatter
if (formats is null || formats.Count != 2)
Expand Down Expand Up @@ -69,4 +74,4 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
/// </summary>
public RegexOptions RegexOptions { get; set; }
}
}
}
11 changes: 8 additions & 3 deletions src/SmartFormat/Extensions/ListFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public class ListFormatter : IFormatter, ISource, IInitializer
///<inheritdoc/>
public bool CanAutoDetect { get; set; } = true;

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

/// <summary>
/// This allows an integer to be used as a selector to index an array (or list).
/// This is better described using an example:
Expand Down Expand Up @@ -155,7 +160,7 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)

// See if the format string contains un-nested "|":
using var splitListPooledObject = SplitListPool.Instance.Get(out var splitList);
var parameters = (SplitList) (format is not null ? format.Split('|', 4) : splitList);
var parameters = (SplitList) (format is not null ? format.Split(SplitChar, 4) : splitList);

// Check whether arguments can be handled by this formatter
if (format is null || parameters.Count < 2 || current is not IEnumerable currentAsEnumerable
Expand Down Expand Up @@ -186,7 +191,7 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
if (!itemFormat.HasNested)
{
// The format is not nested,
// so we will treat it as an itemFormat:
// so we will treat it as an ItemFormat:
var newItemFormat = FormatPool.Instance.Get().Initialize(_smartSettings, itemFormat.BaseString,
itemFormat.StartIndex, itemFormat.EndIndex, true);

Expand Down Expand Up @@ -283,4 +288,4 @@ public void Initialize(SmartFormatter smartFormatter)
_isInitialized = true;
}
}
}
}
17 changes: 14 additions & 3 deletions src/SmartFormat/Extensions/PluralLocalizationFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ public PluralLocalizationFormatter(string defaultTwoLetterIsoLanguageName)
///<inheritdoc/>
public bool CanAutoDetect { get; set; } = true;

/// <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)
{
Expand All @@ -82,7 +87,7 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
if (format == null || format.BaseString.Length > 0 && format.BaseString[format.StartIndex] == ':') return false;

// Extract the plural words from the format string:
var pluralWords = format.Split('|');
var pluralWords = format.Split(SplitChar);
// This extension requires at least two plural words:
if (pluralWords.Count == 1)
{
Expand Down Expand Up @@ -148,6 +153,7 @@ private static PluralRules.PluralRuleDelegate GetPluralRule(IFormattingInfo form
if (pluralRuleProvider != null) return pluralRuleProvider.GetPluralRule();

// No CustomPluralRuleProvider, so use the CultureInfo

return PluralRules.GetPluralRule(culture.TwoLetterISOLanguageName);
}

Expand All @@ -160,7 +166,12 @@ private static CultureInfo GetCultureInfo(IFormattingInfo formattingInfo)
if (formattingInfo.FormatDetails.Provider is CultureInfo ci)
cultureInfo = ci;
else
cultureInfo = CultureInfo.CurrentUICulture; // also used this way by ResourceManager
cultureInfo = CultureInfo.CurrentUICulture;

// There is no pluralization rule for invariant culture (TwoLetterISOLanguageName == "iv"),
// so we take English as default
if(cultureInfo.Equals(CultureInfo.InvariantCulture))
cultureInfo = CultureInfo.GetCultureInfo("en");
}
else
{
Expand Down Expand Up @@ -213,4 +224,4 @@ public PluralRules.PluralRuleDelegate GetPluralRule()
return _pluralRule;
}
}
}
}
8 changes: 4 additions & 4 deletions src/SmartFormat/Extensions/SubStringFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public class SubStringFormatter : IFormatter
public bool CanAutoDetect { get; set; } = false;

/// <summary>
/// The delimiter to separate parameters, defaults to comma.
/// Gets or sets the character used to split the option text literals.
/// </summary>
public char ParameterDelimiter { get; set; } = ',';
public char SplitChar { get; set; } = ',';

/// <summary>
/// Get or set the string to display for NULL values, defaults to <see cref="string.Empty"/>.
Expand All @@ -43,7 +43,7 @@ public class SubStringFormatter : IFormatter
///<inheritdoc />
public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
{
var parameters = formattingInfo.FormatterOptions.Split(ParameterDelimiter) ?? Array.Empty<string>();
var parameters = formattingInfo.FormatterOptions.Split(SplitChar);
if (formattingInfo.CurrentValue is not (string or null) || parameters.Length == 1 && parameters[0].Length == 0)
{
// Auto detection calls just return a failure to evaluate
Expand All @@ -61,7 +61,7 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
formattingInfo.Write(NullDisplayString);
return true;
}

var (startPos, length) = GetStartAndLength(currentValue, parameters);

switch(OutOfRangeBehavior)
Expand Down