Skip to content

Commit

Permalink
Make FluentBundle abstract and do some API refactoring (#50)
Browse files Browse the repository at this point in the history
* Fix execution

* Make FluentBundle abstract

* Minor refactors.

* Add tests for Equality

* Minor refactors

* Add net8.0 for frozen sets

* Add FrozenBundle

* Finish frozen bundle
  • Loading branch information
Ygg01 authored Jan 12, 2024
1 parent 1e3f5a3 commit 546eb8d
Show file tree
Hide file tree
Showing 24 changed files with 1,186 additions and 399 deletions.
2 changes: 1 addition & 1 deletion Linguini.Bench/Linguini.Bench.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions Linguini.Bundle.Test/Linguini.Bundle.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<PackageVersion>0.7.0</PackageVersion>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageReference Include="YamlDotNet" Version="11.0.1" />
</ItemGroup>

Expand Down
97 changes: 80 additions & 17 deletions Linguini.Bundle.Test/Unit/BundleTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -22,23 +23,23 @@ public class BundleTests
private readonly Func<IFluentType, string> _formatter = _ => "";
private readonly Func<string, string> _transform = str => str.ToUpper(CultureInfo.InvariantCulture);

private static string _res1 = @"
private const string Res1 = @"
term = term
.attr = 3";

private static string _wrong = @"
private const string Wrong = @"
term = 1";

private static string _multi = @"
private const string Multi = @"
term1 = val1
term2 = val2
.attr = 6";

private static string _replace1 = @"
private const string Replace1 = @"
term1 = val1
term2 = val2";

private static string _replace2 = @"
private const string Replace2 = @"
term1 = xxx
new1 = new
.attr = 6";
Expand Down Expand Up @@ -90,15 +91,15 @@ public void TestReplaceMessage()
{
var bundler = LinguiniBuilder.Builder()
.CultureInfo(new CultureInfo("en"))
.AddResource(_replace1);
.AddResource(Replace1);

var bundle = bundler.UncheckedBuild();
Assert.That(bundle.TryGetAttrMessage("term1", null, out _, out var termMsg));
Assert.That("val1", Is.EqualTo(termMsg));
Assert.That(bundle.TryGetAttrMessage("term2", null, out _, out var termMsg2));
Assert.That("val2", Is.EqualTo(termMsg2));

bundle.AddResourceOverriding(_replace2);
bundle.AddResourceOverriding(Replace2);
Assert.That(bundle.TryGetAttrMessage("term2", null, out _, out _));
Assert.That(bundle.TryGetAttrMessage("term1", null, out _, out termMsg));
Assert.That("xxx", Is.EqualTo(termMsg));
Expand All @@ -112,7 +113,7 @@ public void TestExceptions()
{
var bundler = LinguiniBuilder.Builder()
.Locales("en-US", "sr-RS")
.AddResources(_wrong, _res1)
.AddResources(Wrong, Res1)
.SetFormatterFunc(_formatter)
.SetTransformFunc(_transform)
.AddFunction("id", _idFunc)
Expand Down Expand Up @@ -140,7 +141,7 @@ public void TestEnumeration()
{
var bundler = LinguiniBuilder.Builder()
.Locale("en-US")
.AddResource(_multi)
.AddResource(Multi)
.AddFunction("id", _idFunc)
.AddFunction("zero", _zeroFunc)
.UncheckedBuild();
Expand Down Expand Up @@ -176,11 +177,16 @@ public void TestConcurrencyOption()
UseConcurrent = true,
};
var optBundle = FluentBundle.MakeUnchecked(bundleOpt);

Parallel.For(0, 10, i => optBundle.AddResource($"term-1 = {i}", out _));
Parallel.For(0, 10, i => optBundle.AddResource($"term-2= {i}", out _));
Parallel.For(0, 10, i => optBundle.TryGetAttrMessage("term-1", null, out _, out _));
Parallel.For(0, 10, i => optBundle.AddResourceOverriding($"term-2= {i + 1}"));
Assert.That(optBundle.HasMessage("term-1"));

// Frozen bundle are read only and should be thread-safe
var frozenBundle = optBundle.ToFrozenBundle();
Parallel.For(0, 10, i => frozenBundle.TryGetAttrMessage("term-1", null, out _, out _));
}

[Test]
Expand Down Expand Up @@ -208,7 +214,7 @@ public void TestFuncAddBehavior()
bundle.TryAddFunction("id", _idFunc);

Assert.That(bundle.TryAddFunction("id", _zeroFunc), Is.False);
Assert.Throws<KeyNotFoundException>(() => bundle.AddFunctionUnchecked("id", _zeroFunc));
Assert.Throws<ArgumentException>(() => bundle.AddFunctionUnchecked("id", _zeroFunc));
}

[Test]
Expand All @@ -221,7 +227,7 @@ public void TestBehavior(string idWithAttr, bool found)
{
var bundle = LinguiniBuilder.Builder()
.CultureInfo(new CultureInfo("en"))
.AddResource(_replace2)
.AddResource(Replace2)
.UncheckedBuild();

Assert.That(bundle.TryGetAttrMessage(idWithAttr, null, out _, out _),
Expand All @@ -238,7 +244,7 @@ public void TestHasAttrMessage(string idWithAttr, bool found)
{
var bundle = LinguiniBuilder.Builder()
.CultureInfo(new CultureInfo("en"))
.AddResource(_replace2)
.AddResource(Replace2)
.UncheckedBuild();

Assert.That(bundle.TryGetAttrMessage(idWithAttr, null, out _, out _),
Expand All @@ -252,7 +258,7 @@ public static IEnumerable<TestCaseData> TestBundleErrors
yield return new TestCaseData("### Comment\r\nterm1")
.Returns(new List<ErrorSpan?>
{
new ErrorSpan(2, 13, 18, 18, 19)
new(2, 13, 18, 18, 19)
});
}
}
Expand All @@ -264,6 +270,8 @@ public static IEnumerable<TestCaseData> TestBundleErrors
var (_, error) = LinguiniBuilder.Builder().Locale("en-US")
.AddResource(input)
.Build();
Debug.Assert(error != null, nameof(error) + " != null");
Assert.That(error, Is.Not.Empty);
return error.Select(e => e.GetSpan()).ToList();
}

Expand All @@ -287,7 +295,7 @@ public void TestDynamicReference(string input)
var (bundle, err) = LinguiniBuilder.Builder(useExperimental: true).Locale("en-US")
.AddResource(input)
.Build();
Assert.That(err, Is.Empty);
Assert.That(err, Is.Null);
var args = new Dictionary<string, IFluentType>()
{
["attacker"] = (FluentReference)"cat",
Expand All @@ -312,18 +320,23 @@ [neuter] It

[Test]
[Parallelizable]
public void TestMacrosFail()
public void TestExtensionsWork()
{
var (bundle, err) = LinguiniBuilder.Builder(useExperimental: true).Locale("en-US")
.AddResource(Macros)
.Build();
Assert.That(err, Is.Empty);
Assert.That(err, Is.Null);
var args = new Dictionary<string, IFluentType>
{
["style"] = (FluentString)"chicago",
};
Assert.That(bundle.TryGetMessage("call-attr-no-args", args, out _, out var message));
Assert.That("It", Is.EqualTo(message));

// Check Frozen bundle behaves similarly
var frozenBundle = bundle.ToFrozenBundle();
Assert.That(frozenBundle.TryGetMessage("call-attr-no-args", args, out _, out var frozenMessage));
Assert.That("It", Is.EqualTo(frozenMessage));
}
private const string DynamicSelectors = @"
-creature-fairy = fairy
Expand All @@ -344,7 +357,7 @@ public void TestDynamicSelectors()
.Locale("en-US")
.AddResource(DynamicSelectors)
.Build();
Assert.That(err, Is.Empty);
Assert.That(err, Is.Null);
var args = new Dictionary<string, IFluentType>
{
["object"] = (FluentReference)"creature-elf",
Expand All @@ -357,6 +370,56 @@ public void TestDynamicSelectors()
};
Assert.That(bundle.TryGetMessage("you-see", args, out _, out var message2));
Assert.That("You see a fairy.", Is.EqualTo(message2));

// Check Frozen bundle behaves similarly
var frozenBundle = bundle.ToFrozenBundle();
args = new Dictionary<string, IFluentType>
{
["object"] = (FluentReference)"creature-elf",
};
Assert.That(frozenBundle.TryGetMessage("you-see", args, out _, out var frozenMessage1));
Assert.That("You see an elf.", Is.EqualTo(frozenMessage1));
args = new Dictionary<string, IFluentType>
{
["object"] = (FluentReference)"creature-fairy",
};
Assert.That(frozenBundle.TryGetMessage("you-see", args, out _, out var frozenMessage2));
Assert.That("You see a fairy.", Is.EqualTo(frozenMessage2));
}

[Test]
public void TestDeepClone()
{
var originalBundleOption = new FluentBundleOption
{
Locales = { "en-US" },
MaxPlaceable = 123,
UseIsolating = false,
TransformFunc = _transform,
FormatterFunc = _formatter,
Functions = new Dictionary<string, ExternalFunction>()
{
["zero"] = _zeroFunc,
["id"] = _idFunc,
}
};

// Assume FluentBundle object has DeepClone method
FluentBundle originalBundle = FluentBundle.MakeUnchecked(originalBundleOption);
FluentBundle clonedBundle = originalBundle.DeepClone();

// Assert that the original and cloned objects are not the same reference
Assert.That(originalBundle, Is.Not.SameAs(clonedBundle));

// Assert that the properties are copied properly
Assert.That(originalBundle, Is.EqualTo(clonedBundle));

// Assert that if original property is changed, new property isn't.
originalBundle.AddFunctionOverriding("zero", _idFunc);
clonedBundle.TryGetFunction("zero", out var clonedZero);
Assert.That((FluentFunction) _zeroFunc, Is.EqualTo(clonedZero));
originalBundle.TryGetFunction("zero", out var originalZero);
Assert.That((FluentFunction) _idFunc, Is.EqualTo(originalZero));
}
}
}
16 changes: 11 additions & 5 deletions Linguini.Bundle.Test/Yaml/YamlSuiteParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.IO;
using Linguini.Bundle.Builder;
using Linguini.Bundle.Errors;
using Linguini.Bundle.Func;
using Linguini.Bundle.Function;
using Linguini.Bundle.Types;
using NUnit.Framework;
using YamlDotNet.RepresentationModel;
Expand Down Expand Up @@ -88,7 +88,10 @@ public void YamlTestSuiteMethod(ResolverTestSuite parsedTestSuite, LinguiniBuild
foreach (var res in parsedTestSuite.Resources)
{
bundle.AddResource(res, out var err);
errors.AddRange(err);
if (err != null)
{
errors.AddRange(err);
}
}

if (parsedTestSuite.Bundle != null)
Expand Down Expand Up @@ -147,7 +150,10 @@ public void YamlTestSuiteMethod(ResolverTestSuite parsedTestSuite, LinguiniBuild
else
{
testBundle.AddResource(res, out var errs);
errors.AddRange(errs);
if (errs != null)
{
errors.AddRange(errs);
}
}
}
}
Expand Down Expand Up @@ -189,10 +195,10 @@ private static string GetFullPathFor(string file)


private static void AssertErrorCases(List<ResolverTestSuite.ResolverTestError> expectedErrors,
IList<FluentError> errs,
IList<FluentError>? errs,
String testName)
{
Assert.That(expectedErrors.Count, Is.EqualTo(errs.Count), testName);
Assert.That(expectedErrors.Count, Is.EqualTo(errs!.Count), testName);
for (var i = 0; i < expectedErrors.Count; i++)
{
var actualError = errs[i];
Expand Down
2 changes: 1 addition & 1 deletion Linguini.Bundle/Builder/FluentBundleOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class FluentBundleOption
public bool UseIsolating { get; init; } = true;
public byte MaxPlaceable { get; init; } = 100;

public IList<string> Locales { get; init; } = new List<string>();
public List<string> Locales { get; init; } = new List<string>();

public IDictionary<string, ExternalFunction> Functions { get; init; } =
new Dictionary<string, ExternalFunction>();
Expand Down
16 changes: 10 additions & 6 deletions Linguini.Bundle/Builder/LinguiniBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public interface IBuildStep : IStep
{
FluentBundle UncheckedBuild();

(FluentBundle, List<FluentError>) Build();
(FluentBundle, List<FluentError>?) Build();
}

public interface IReadyStep : IBuildStep
Expand All @@ -65,7 +65,6 @@ private class StepBuilder : IReadyStep, ILocaleStep, IResourceStep
private CultureInfo _culture;
private readonly List<string> _locales = new();
private readonly List<Resource> _resources = new();
private readonly List<object> _source = new();
private bool _useIsolating;
private Func<IFluentType, string>? _formatterFunc;
private Func<string, string>? _transformFunc;
Expand Down Expand Up @@ -113,15 +112,15 @@ public FluentBundle UncheckedBuild()
{
var (bundle, errors) = Build();

if (errors.Count > 0)
if (errors is { Count: > 0 })
{
throw new LinguiniException(errors);
}

return bundle;
}

public (FluentBundle, List<FluentError>) Build()
public (FluentBundle, List<FluentError>?) Build()
{
var concurrent = new FluentBundleOption
{
Expand All @@ -134,18 +133,23 @@ public FluentBundle UncheckedBuild()
};
var bundle = FluentBundle.MakeUnchecked(concurrent);
bundle.Culture = _culture;
List<FluentError>? errors = null;

var errors = new List<FluentError>();
if (_functions.Count > 0)
{
bundle.AddFunctions(_functions, out var funcErr);
errors.AddRange(funcErr);
if (funcErr != null)
{
errors ??= new List<FluentError>();
errors.AddRange(funcErr);
}
}

foreach (var resource in _resources)
{
if (!bundle.AddResource(resource,out var resErr))
{
errors ??= new List<FluentError>();
errors.AddRange(resErr);
}
}
Expand Down
Loading

0 comments on commit 546eb8d

Please sign in to comment.