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

Refactored handling of source and formatter extensions #180

Merged
merged 1 commit into from
Jul 18, 2021
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
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