Skip to content

Commit

Permalink
Refactored handling of source and formatter extensions (#180)
Browse files Browse the repository at this point in the history
See the [change log](CHANGES.md) for a list of all changes.
  • Loading branch information
axunonb authored Jul 18, 2021
1 parent 72649df commit 747fbe9
Show file tree
Hide file tree
Showing 43 changed files with 441 additions and 299 deletions.
19 changes: 19 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ v3.0.0-alpha.1

### Current changes merged into the `version/v3.0` branch:

#### Refactored handling of source and formatter extensions ([#179](https://github.com/axuno/SmartFormat/pull/179))

SmartFormatter:
* `SourceExtensions` is a `IReadOnlyList<ISource>`, and can only be manipulated with the methods below. `ISource` instances will be added only once per type.
* `FormatterExtensions` is a `IReadOnlyList<IFormatter>`, and can only be manipulated with the methods below. `IFormatter` instances will be added only once per type. Trying to add a formatter with an existing name will throw.
* Extension can be added and removed using
* `AddExtensions(params ISource[] sourceExtensions)`
* `AddExtensions(int position, params ISource[] sourceExtensions)`
* `AddExtensions(params IFormatter[] formatterExtensions)`
* `AddExtensions(int position, params IFormatter[] formatterExtensions)`
* `RemoveSourceExtension<T>()`
* `RemoveFormatterExtension<T>()`

Extensions:
* For performance it is highly recommended to only add such `ISource` and `IFormatter` extensions that are actually required. `Smart.Format(...)` uses all available extensions as the default. Also, use explicit formatter names instead of letting the `SmartFormatter` implicitly find a matching formatter.
* Neither `ISource` nore `IFormatter` extensions have a CTOR with an argument. This allows for adding extension instances to different `SmartFormatter`s.
* Any exensions can implement `IInitializer`. If this is implemented, the `SmartFormatter` will call the method `Initialize(SmartFormatter smartFormatter)` of the extension, before adding it to the extension list.
* The `Source` abstract class implements `IInitializer`. The `SmartFormatter` and the `SmartSettings` are accessible for classes with `Source` as the base class.

#### Added `StringSource` as another `ISource` ([#178](https://github.com/axuno/SmartFormat/pull/178))

`StringSource` adds the following selector names, which have before been implemented with `ReflectionSource`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ public NullFormatterChooseFormatterTests()
public void Setup()
{
_smartNullFormatter = new SmartFormatter();
_smartNullFormatter.AddExtensions(new ISource[] { new DefaultSource(_smartNullFormatter) });
_smartNullFormatter.AddExtensions(new ISource[] { new DefaultSource() });
_smartNullFormatter.AddExtensions(new IFormatter[] { new NullFormatter(), new DefaultFormatter()});

_smartChooseFormatter = new SmartFormatter();
_smartChooseFormatter.AddExtensions(new ISource[] { new DefaultSource(_smartChooseFormatter) });
_smartChooseFormatter.AddExtensions(new ISource[] { new DefaultSource() });
_smartChooseFormatter.AddExtensions(new IFormatter[] { new ChooseFormatter(), new DefaultFormatter()});

_nullFormatCache = new FormatCache(_smartNullFormatter.Parser.ParseFormat("{0:isnull:nothing}"));
Expand Down
8 changes: 4 additions & 4 deletions src/SmartFormat.Performance/ReflectionVsStringSourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,17 @@ public ReflectionVsStringSourceTests()
{
_reflectionSourceFormatter = new SmartFormatter();
_reflectionSourceFormatter.AddExtensions(
new ReflectionSource(_reflectionSourceFormatter),
new DefaultSource(_reflectionSourceFormatter)
new ReflectionSource(),
new DefaultSource()
);
_reflectionSourceFormatter.AddExtensions(
new DefaultFormatter()
);

_stringSourceFormatter = new SmartFormatter();
_stringSourceFormatter.AddExtensions(
new StringSource(_stringSourceFormatter),
new DefaultSource(_stringSourceFormatter)
new StringSource(),
new DefaultSource()
);
_stringSourceFormatter.AddExtensions(
new DefaultFormatter()
Expand Down
14 changes: 7 additions & 7 deletions src/SmartFormat.Performance/SourcePerformanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,34 @@ public SourcePerformanceTests()
{
_literalFormatter = new SmartFormatter();
_literalFormatter.AddExtensions(
new DefaultSource(_literalFormatter)
new DefaultSource()
);
_literalFormatter.AddExtensions(
new DefaultFormatter()
);

_reflectionFormatter = new SmartFormatter();
_reflectionFormatter.AddExtensions(
new ReflectionSource(_reflectionFormatter),
new DefaultSource(_reflectionFormatter)
new ReflectionSource(),
new DefaultSource()
);
_reflectionFormatter.AddExtensions(
new DefaultFormatter()
);

_dictionaryFormatter = new SmartFormatter();
_dictionaryFormatter.AddExtensions(
new DictionarySource(_dictionaryFormatter),
new DefaultSource(_dictionaryFormatter)
new DictionarySource(),
new DefaultSource()
);
_dictionaryFormatter.AddExtensions(
new DefaultFormatter()
);

_jsonFormatter = new SmartFormatter();
_jsonFormatter.AddExtensions(
new SystemTextJsonSource(_jsonFormatter),
new DefaultSource(_jsonFormatter)
new SystemTextJsonSource(),
new DefaultSource()
);
_jsonFormatter.AddExtensions(
new DefaultFormatter()
Expand Down
12 changes: 6 additions & 6 deletions src/SmartFormat.Tests/Core/AlignmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using SmartFormat.Core.Extensions;
using SmartFormat.Extensions;
using SmartFormat.Utilities;

Expand All @@ -16,9 +17,8 @@ public class AlignmentTests
private SmartFormatter GetSimpleFormatter()
{
var formatter = new SmartFormatter();
formatter.FormatterExtensions.Add(new DefaultFormatter());
formatter.SourceExtensions.Add(new ReflectionSource(formatter));
formatter.SourceExtensions.Add(new DefaultSource(formatter));
formatter.AddExtensions(new DefaultFormatter());
formatter.AddExtensions(new ReflectionSource(), new DefaultSource());
return formatter;
}

Expand Down Expand Up @@ -69,7 +69,7 @@ public void Formatter_AlignNull()
public void ChooseFormatter_Alignment(int alignment)
{
var smart = GetSimpleFormatter();
smart.FormatterExtensions.Add(new ChooseFormatter());
smart.AddExtensions(new ChooseFormatter());

var data = new {Number = 2};
var format = $"{{Number,{alignment}:choose(1|2|3):one|two|three}}";
Expand All @@ -90,7 +90,7 @@ public void ChooseFormatter_Alignment(int alignment)
public void ListFormatter_NestedFormats_Alignment(int alignment)
{
var smart = GetSimpleFormatter();
smart.FormatterExtensions.Add(new ListFormatter(smart));
smart.AddExtensions((IFormatter) new ListFormatter());

var items = new [] { "one", "two", "three" };
var result = smart.Format($"{{items,{alignment}:list:{{}}|, |, and }}", new { items }); // important: not only "items" as the parameter
Expand All @@ -112,7 +112,7 @@ public void ListFormatter_NestedFormats_Alignment(int alignment)
public void ListFormatter_UnnestedFormats_Alignment(string format, string expected)
{
var smart = GetSimpleFormatter();
smart.FormatterExtensions.Add(new ListFormatter(smart));
smart.AddExtensions((IFormatter) new ListFormatter());

var args = new[] {1, 2, 3, 4, 5};
var result = smart.Format(CultureInfo.InvariantCulture, format, args);
Expand Down
5 changes: 2 additions & 3 deletions src/SmartFormat.Tests/Core/FormatCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ class FormatCacheTests
private SmartFormatter GetSimpleFormatter()
{
var formatter = new SmartFormatter();
formatter.FormatterExtensions.Add(new DefaultFormatter());
formatter.SourceExtensions.Add(new ReflectionSource(formatter));
formatter.SourceExtensions.Add(new DefaultSource(formatter));
formatter.AddExtensions(new DefaultFormatter());
formatter.AddExtensions(new ReflectionSource(), new DefaultSource());
return formatter;
}

Expand Down
66 changes: 59 additions & 7 deletions src/SmartFormat.Tests/Core/FormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using NUnit.Framework;
using SmartFormat.Core.Formatting;
using SmartFormat.Core.Output;
using SmartFormat.Core.Parsing;
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;
using SmartFormat.Tests.TestUtils;
Expand All @@ -19,9 +20,8 @@ public class FormatterTests
private SmartFormatter GetSimpleFormatter()
{
var formatter = new SmartFormatter();
formatter.FormatterExtensions.Add(new DefaultFormatter());
formatter.SourceExtensions.Add(new ReflectionSource(formatter));
formatter.SourceExtensions.Add(new DefaultSource(formatter));
formatter.AddExtensions(new DefaultFormatter());
formatter.AddExtensions(new ReflectionSource(), new DefaultSource());
return formatter;
}

Expand Down Expand Up @@ -185,21 +185,73 @@ public void SmartFormatter_FormatDetails()
[Test]
public void Missing_FormatExtensions_Should_Throw()
{
var formatter = GetSimpleFormatter();
var formatter = new SmartFormatter();
// make sure we test against missing format extensions
formatter.AddExtensions(new DefaultSource());

formatter.FormatterExtensions.Clear();
Assert.That(formatter.FormatterExtensions.Count, Is.EqualTo(0));
Assert.Throws<InvalidOperationException>(() => formatter.Format("", Array.Empty<object>()));
}

[Test]
public void Missing_SourceExtensions_Should_Throw()
{
var formatter = GetSimpleFormatter();
var formatter = new SmartFormatter();
// make sure we test against missing source extensions
formatter.AddExtensions(new DefaultFormatter());

formatter.SourceExtensions.Clear();
Assert.That(formatter.SourceExtensions.Count, Is.EqualTo(0));
Assert.Throws<InvalidOperationException>(() => formatter.Format("", Array.Empty<object>()));
}

[Test]
public void Adding_FormatExtension_With_Existing_Name_Should_Throw()
{
var formatter = new SmartFormatter();
var firstExtension = new DefaultFormatter();
formatter.AddExtensions(firstExtension);
var dupeExtension = new NullFormatter {Names = firstExtension.Names};
Assert.That(() => formatter.AddExtensions(dupeExtension), Throws.TypeOf(typeof(ArgumentException)));
}

[Test]
public void Remove_None_Existing_Source()
{
var formatter = new SmartFormatter();

Assert.That(formatter.SourceExtensions.Count, Is.EqualTo(0));
Assert.That(formatter.RemoveSourceExtension<StringSource>(), Is.EqualTo(false));
}

[Test]
public void Remove_Existing_Source()
{
var formatter = new SmartFormatter();

Assert.That(formatter.SourceExtensions.Count, Is.EqualTo(0));
formatter.AddExtensions(new StringSource());
Assert.That(formatter.RemoveSourceExtension<StringSource>(), Is.EqualTo(true));
}

[Test]
public void Remove_None_Existing_Formatter()
{
var formatter = new SmartFormatter();

Assert.That(formatter.FormatterExtensions.Count, Is.EqualTo(0));
Assert.That(formatter.RemoveFormatterExtension<DefaultFormatter>(), Is.EqualTo(false));
}

[Test]
public void Remove_Existing_Formatter()
{
var formatter = new SmartFormatter();

Assert.That(formatter.FormatterExtensions.Count, Is.EqualTo(0));
formatter.AddExtensions(new DefaultFormatter());
Assert.That(formatter.RemoveFormatterExtension<DefaultFormatter>(), Is.EqualTo(true));
}

[Test]
public void Formatter_GetSourceExtension()
{
Expand Down
2 changes: 1 addition & 1 deletion src/SmartFormat.Tests/Core/NamedFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ private SmartFormatter GetCustomFormatter()
{
var testFormatter = new SmartFormatter();
testFormatter.AddExtensions(new TestExtension1(), new TestExtension2(), new DefaultFormatter());
testFormatter.AddExtensions(new DefaultSource(testFormatter));
testFormatter.AddExtensions(new DefaultSource());
testFormatter.Settings.Formatter.ErrorAction = FormatErrorAction.ThrowError;
return testFormatter;
}
Expand Down
11 changes: 0 additions & 11 deletions src/SmartFormat.Tests/Core/NestingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,6 @@ public void Nesting_can_access_root_via_number(string format, string expectedOut
Assert.AreEqual(expectedOutput, actual);
}

[Test]
[TestCase("{ChildOne.ChildTwo.ChildThree: {Four} {One} }", " 4 1 ")]
[TestCase("{ChildOne: {ChildTwo: {ChildThree: {Four} {Three} {Two} {One} } } }", " 4 3 2 1 ")]
[TestCase("{ChildOne: {ChildTwo: {ChildThree: {Four} {ChildTwo.Three} {ChildOne.Two} {One} } } }", " 4 3 2 1 ")]
[TestCase("{ChildOne: {ChildTwo: {ChildThree: {ChildOne: {ChildTwo: {ChildThree: {Four} } } } } } }", " 4 ")]
public void Nesting_can_access_outer_scopes(string format, string expectedOutput)
{
var actual = Smart.Format(format, data);
Assert.AreEqual(expectedOutput, actual);
}

[Test]
public void Nesting_CurrentScope_propertyName_outrules_OuterScope_propertyName()
{
Expand Down
22 changes: 11 additions & 11 deletions src/SmartFormat.Tests/Extensions/ConditionalFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ public void Test_Numbers()
public void Test_Enum()
{
var formats = new[] {
"{0.Friends.0:{FirstName} is a {Gender:man|woman}.}",
"{0.Friends.1:{FirstName} is a {Gender:man|woman}.}",
"{2.DayOfWeek:Sunday|Monday|Some other day} / {3.DayOfWeek:Sunday|Monday|Some other day}",
"{0.Friends.0:{FirstName} is a {Gender:cond:man|woman}.}",
"{0.Friends.1:{FirstName} is a {Gender:cond:man|woman}.}",
"{2.DayOfWeek:cond:Sunday|Monday|Some other day} / {3.DayOfWeek:cond:Sunday|Monday|Some other day}",
};
var expected = new[] {
"Jim is a man.",
Expand All @@ -47,8 +47,8 @@ public void Test_Enum()
public void Test_Bool()
{
var formats = new[] {
"{0:Yes|No}",
"{1:Yes|No}",
"{0:cond:Yes|No}",
"{1:cond:Yes|No}",
};
var expected = new[] {
"No",
Expand All @@ -63,8 +63,8 @@ public void Test_Bool()
public void Test_Dates()
{
var formats = new[] {
"{0:Past|Future} {1:Past|Future} {2:Past|Future}",
"{0:Past|Present|Future} {1:Past|Present|Future} {2:Past|Present|Future}",
"{0:cond:Past|Future} {1:cond:Past|Future} {2:cond:Past|Future}",
"{0:cond:Past|Present|Future} {1:cond:Past|Present|Future} {2:cond:Past|Present|Future}",
};
var expected = new[] {
"Past Past Future",
Expand All @@ -79,8 +79,8 @@ public void Test_Dates()
public void Test_DateTimeOffset_Dates()
{
var formats = new[] {
"{0:Past|Future} {1:Past|Future} {2:Past|Future}",
"{0:Past|Present|Future} {1:Past|Present|Future} {2:Past|Present|Future}",
"{0:cond:Past|Future} {1:cond:Past|Future} {2:cond:Past|Future}",
"{0:cond:Past|Present|Future} {1:cond:Past|Present|Future} {2:cond:Past|Present|Future}",
};
var expected = new[] {
"Past Past Future",
Expand All @@ -97,8 +97,8 @@ public void Test_DateTimeOffset_Dates()
public void Test_TimeSpan()
{
var formats = new[] {
"{0:Past|Future} {1:Past|Future} {2:Past|Future}",
"{0:Past|Zero|Future} {1:Past|Zero|Future} {2:Past|Zero|Future}",
"{0:cond:Past|Future} {1:cond:Past|Future} {2:cond:Past|Future}",
"{0:cond:Past|Zero|Future} {1:cond:Past|Zero|Future} {2:cond:Past|Zero|Future}",
};
var expected = new[] {
"Past Past Future",
Expand Down
8 changes: 4 additions & 4 deletions src/SmartFormat.Tests/Extensions/DictionaryFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public dynamic GetDynamicArgs()
public void Test_Dictionary()
{
var formatter = Smart.CreateDefaultSmartFormat();
formatter.AddExtensions(new DictionarySource(formatter));
formatter.AddExtensions(new DictionarySource());

var formats = new[]
{
Expand All @@ -75,7 +75,7 @@ public void Test_Dictionary()
public void Test_Dynamic()
{
var formatter = Smart.CreateDefaultSmartFormat();
formatter.AddExtensions(new DictionarySource(formatter));
formatter.AddExtensions(new DictionarySource());

var formats = new[]
{
Expand All @@ -96,7 +96,7 @@ public void Test_Dynamic_CaseInsensitive()
{
var formatter = Smart.CreateDefaultSmartFormat();
formatter.Settings.CaseSensitivity = CaseSensitivityType.CaseInsensitive;
formatter.AddExtensions(new DictionarySource(formatter));
formatter.AddExtensions(new DictionarySource());

var formats = new string[]
{
Expand Down Expand Up @@ -149,7 +149,7 @@ public void Dictionary_Dot_Notation_Nullable()
$"Name: {addr.Person.FirstName} {addr.Person.LastName}";

var smart = new SmartFormatter();
smart.AddExtensions(new ISource[] { new DefaultSource(smart), new DictionarySource(smart) });
smart.AddExtensions(new ISource[] { new DefaultSource(), new DictionarySource() });
smart.AddExtensions(new IFormatter[] {new DefaultFormatter()});

var result = smart.Format(format, addrDict);
Expand Down
2 changes: 1 addition & 1 deletion src/SmartFormat.Tests/Extensions/IsMatchFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class IsMatchFormatterTests
public IsMatchFormatterTests()
{
_formatter = Smart.CreateDefaultSmartFormat();
_formatter.FormatterExtensions.Add(new IsMatchFormatter {RegexOptions = RegexOptions.CultureInvariant});
_formatter.AddExtensions(new IsMatchFormatter {RegexOptions = RegexOptions.CultureInvariant});
_formatter.Settings.Formatter.ErrorAction = FormatErrorAction.ThrowError;
}

Expand Down
Loading

0 comments on commit 747fbe9

Please sign in to comment.