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

Make FluentBundle abstract and do some API refactoring #50

Merged
merged 8 commits into from
Jan 12, 2024
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
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
Loading