From 4d2d0fd5bf0c5f2648f1caa34435ee0dc5b65804 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 28 May 2024 17:44:14 -0700 Subject: [PATCH 1/3] Implement FluidValue.WriteToAsync Fixes #647 --- Fluid.Tests/Domain/WithInterfaces/PetValue.cs | 7 ++ Fluid.Tests/MvcViewEngine/NoSyncStream.cs | 30 +++++++ Fluid.Tests/MvcViewEngine/ViewEngineTests.cs | 29 +++++- Fluid/Accessors/PropertyInfoAccessor.cs | 2 +- Fluid/Ast/CycleStatement.cs | 2 +- Fluid/Ast/DecrementStatement.cs | 6 +- Fluid/Ast/IncrementStatement.cs | 15 +++- Fluid/Ast/OutputStatement.cs | 18 +++- Fluid/Filters/ColorFilters.cs | 4 +- Fluid/Filters/StringFilters.cs | 10 +-- Fluid/FiltersCollection.cs | 4 + Fluid/FluidParser.cs | 16 +++- Fluid/MemberAccessStrategyExtensions.cs | 14 +-- Fluid/MemberNameStrategies.cs | 89 +++++++++---------- Fluid/Shims.cs | 12 +-- Fluid/Utils/TaskExtensions.cs | 14 ++- Fluid/Values/ArrayValue.cs | 11 +++ Fluid/Values/BlankValue.cs | 6 ++ Fluid/Values/BooleanValue.cs | 25 +++++- Fluid/Values/DateTimeValue.cs | 23 ++++- Fluid/Values/DictionaryValue.cs | 6 ++ Fluid/Values/EmptyValue.cs | 6 ++ Fluid/Values/FactoryValue.cs | 23 ++++- Fluid/Values/FluidValue.cs | 9 ++ Fluid/Values/FluidValueExtensions.cs | 8 +- Fluid/Values/ForLoopValue.cs | 6 ++ Fluid/Values/FunctionValue.cs | 8 +- Fluid/Values/NilValue.cs | 6 ++ Fluid/Values/NumberValue.cs | 25 +++++- Fluid/Values/ObjectValueBase.cs | 25 +++++- Fluid/Values/StringValue.cs | 46 +++++++++- 31 files changed, 409 insertions(+), 96 deletions(-) create mode 100644 Fluid.Tests/MvcViewEngine/NoSyncStream.cs diff --git a/Fluid.Tests/Domain/WithInterfaces/PetValue.cs b/Fluid.Tests/Domain/WithInterfaces/PetValue.cs index 0df45163..ee0ae2ed 100644 --- a/Fluid.Tests/Domain/WithInterfaces/PetValue.cs +++ b/Fluid.Tests/Domain/WithInterfaces/PetValue.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using System.Text.Encodings.Web; +using System.Threading.Tasks; namespace Fluid.Tests.Domain.WithInterfaces { @@ -42,11 +43,17 @@ public override string ToStringValue() throw new NotImplementedException(); } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { throw new NotImplementedException(); } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + throw new NotImplementedException(); + } + protected override FluidValue GetValue(string name, TemplateContext context) { if (name == "Name") diff --git a/Fluid.Tests/MvcViewEngine/NoSyncStream.cs b/Fluid.Tests/MvcViewEngine/NoSyncStream.cs new file mode 100644 index 00000000..a826714e --- /dev/null +++ b/Fluid.Tests/MvcViewEngine/NoSyncStream.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Fluid.Tests.MvcViewEngine +{ + /// + /// Stream implementation that prevents non-async usages. + /// + public sealed class NoSyncStream : Stream + { + public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => Task.CompletedTask; + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) => ValueTask.CompletedTask; + + + public override void Flush() => throw new InvalidOperationException(); + public override int Read(byte[] buffer, int offset, int count) => throw new InvalidOperationException(); + public override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException(); + public override void SetLength(long value) => throw new InvalidOperationException(); + public override void Write(byte[] buffer, int offset, int count) => throw new InvalidOperationException(); + + public override bool CanRead { get; } = false; + public override bool CanSeek { get; } = false; + public override bool CanWrite { get; } = true; + public override long Length { get; } + public override long Position { get; set; } + } +} diff --git a/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs b/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs index d9b999bd..715b7c78 100644 --- a/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs +++ b/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs @@ -1,12 +1,15 @@ using Fluid.Tests.Mocks; using Fluid.ViewEngine; +using System; using System.IO; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using Xunit; namespace Fluid.Tests.MvcViewEngine { - public class ViewEngineTests + public partial class ViewEngineTests { FluidViewEngineOptions _options = new (); FluidViewRenderer _renderer; @@ -278,5 +281,29 @@ public async Task LayoutShouldBeAbleToIncludeVarsFromViewStart() Assert.Equal("[TITLE][SUBTITLE][ViewStart][View]", sw.ToString()); } + + [Fact] + public async Task RenderViewOnlyAsyncStream_LargePropertyValue_Nested_SmallBuffer_BiggerThan128LengthString() + { + _mockFileProvider.Add("Views/Index.liquid", "{% layout '_Layout' %}{% section bigboy %}{{BigString}}{% endsection %} "); + _mockFileProvider.Add("Views/_Layout.liquid", "{% rendersection bigboy %}"); + + await using var sw = new StreamWriter(new NoSyncStream(), bufferSize: 10); + var template = new TemplateContext(new { BigString = new string(Enumerable.Range(0, 129).Select(x => 'b').ToArray()) }); + await _renderer.RenderViewAsync(sw, "Index.liquid", template); + await sw.FlushAsync(); + } + + [Fact] + public async Task RenderViewOnlyAsyncStream_LargePropertyValue_Nested() + { + _mockFileProvider.Add("Views/Index.liquid", "{% layout '_Layout' %}{% section bigboy %}{{BigString}}{% endsection %} "); + _mockFileProvider.Add("Views/_Layout.liquid", "{% rendersection bigboy %}"); + + await using var sw = new StreamWriter(new NoSyncStream()); + var template = new TemplateContext(new { BigString = new string(Enumerable.Range(0, 1500).Select(_ => 'b').ToArray()) }); + await _renderer.RenderViewAsync(sw, "Index.liquid", template); + await sw.FlushAsync(); + } } } diff --git a/Fluid/Accessors/PropertyInfoAccessor.cs b/Fluid/Accessors/PropertyInfoAccessor.cs index 0da4f90c..5f24625e 100644 --- a/Fluid/Accessors/PropertyInfoAccessor.cs +++ b/Fluid/Accessors/PropertyInfoAccessor.cs @@ -12,7 +12,7 @@ public PropertyInfoAccessor(PropertyInfo propertyInfo) var d = propertyInfo.GetGetMethod().CreateDelegate(delegateType); var invokerType = typeof(Invoker<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType); - _invoker = Activator.CreateInstance(invokerType, [d]) as IInvoker; + _invoker = Activator.CreateInstance(invokerType, new object[] { d }) as IInvoker; } public object Get(object obj, string name, TemplateContext ctx) diff --git a/Fluid/Ast/CycleStatement.cs b/Fluid/Ast/CycleStatement.cs index 3be3d7e9..1d34c755 100644 --- a/Fluid/Ast/CycleStatement.cs +++ b/Fluid/Ast/CycleStatement.cs @@ -32,7 +32,7 @@ public override async ValueTask WriteToAsync(TextWriter writer, Text var value = await Values[(int)index].EvaluateAsync(context); context.SetValue(groupValue, NumberValue.Create(index + 1)); - value.WriteTo(writer, encoder, context.CultureInfo); + await value.WriteToAsync(writer, encoder, context.CultureInfo); return Completion.Normal; } diff --git a/Fluid/Ast/DecrementStatement.cs b/Fluid/Ast/DecrementStatement.cs index 9479014c..03150933 100644 --- a/Fluid/Ast/DecrementStatement.cs +++ b/Fluid/Ast/DecrementStatement.cs @@ -12,7 +12,7 @@ public DecrementStatement(string identifier) public string Identifier { get; } - public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context) + public override async ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context) { context.IncrementSteps(); @@ -35,9 +35,9 @@ public override ValueTask WriteToAsync(TextWriter writer, TextEncode context.SetValue(prefixedIdentifier, value); - value.WriteTo(writer, encoder, context.CultureInfo); + await value.WriteToAsync(writer, encoder, context.CultureInfo); - return Normal(); + return Completion.Normal; } protected internal override Statement Accept(AstVisitor visitor) => visitor.VisitDecrementStatement(this); diff --git a/Fluid/Ast/IncrementStatement.cs b/Fluid/Ast/IncrementStatement.cs index 295e52b2..e8551302 100644 --- a/Fluid/Ast/IncrementStatement.cs +++ b/Fluid/Ast/IncrementStatement.cs @@ -36,9 +36,20 @@ public override ValueTask WriteToAsync(TextWriter writer, TextEncode context.SetValue(prefixedIdentifier, value); - value.WriteTo(writer, encoder, context.CultureInfo); + var task = value.WriteToAsync(writer, encoder, context.CultureInfo); - return Normal(); + if (task.IsCompletedSuccessfully) + { + return new ValueTask(Completion.Normal); + } + + return Awaited(task); + + static async ValueTask Awaited(ValueTask t) + { + await t; + return Completion.Normal; + } } protected internal override Statement Accept(AstVisitor visitor) => visitor.VisitIncrementStatement(this); diff --git a/Fluid/Ast/OutputStatement.cs b/Fluid/Ast/OutputStatement.cs index 98be5a96..86e402da 100644 --- a/Fluid/Ast/OutputStatement.cs +++ b/Fluid/Ast/OutputStatement.cs @@ -23,7 +23,7 @@ static async ValueTask Awaited( TemplateContext ctx) { var value = await t; - value.WriteTo(w, enc, ctx.CultureInfo); + await value.WriteToAsync(w, enc, ctx.CultureInfo); return Completion.Normal; } @@ -32,8 +32,20 @@ static async ValueTask Awaited( var task = Expression.EvaluateAsync(context); if (task.IsCompletedSuccessfully) { - task.Result.WriteTo(writer, encoder, context.CultureInfo); - return new ValueTask(Completion.Normal); + var valueTask = task.Result.WriteToAsync(writer, encoder, context.CultureInfo); + + if (valueTask.IsCompletedSuccessfully) + { + return new ValueTask(Completion.Normal); + } + + return AwaitedWriteTo(valueTask); + + static async ValueTask AwaitedWriteTo(ValueTask t) + { + await t; + return Completion.Normal; + } } return Awaited(task, writer, encoder, context); diff --git a/Fluid/Filters/ColorFilters.cs b/Fluid/Filters/ColorFilters.cs index 7764618e..e78f35ce 100644 --- a/Fluid/Filters/ColorFilters.cs +++ b/Fluid/Filters/ColorFilters.cs @@ -635,7 +635,7 @@ public static explicit operator HexColor(RgbColor rgbColor) { private const double DefaultTransperency = 1.0; - private static readonly char[] _colorSeparators = ['(', ',', ' ', ')']; + private static readonly char[] _colorSeparators = new[] { '(', ',', ' ', ')' }; public static readonly RgbColor Empty = default; @@ -814,7 +814,7 @@ private readonly struct HslColor { private const double DefaultTransparency = 1.0; - private static readonly char[] _colorSeparators = ['(', ',', ' ', ')']; + private static readonly char[] _colorSeparators = new[] { '(', ',', ' ', ')' }; public static readonly HslColor Empty = default; diff --git a/Fluid/Filters/StringFilters.cs b/Fluid/Filters/StringFilters.cs index 3d11f4a0..19c6fa79 100644 --- a/Fluid/Filters/StringFilters.cs +++ b/Fluid/Filters/StringFilters.cs @@ -124,7 +124,7 @@ public static ValueTask RemoveLast(FluidValue input, FilterArguments public static ValueTask ReplaceFirst(FluidValue input, FilterArguments arguments, TemplateContext context) { -#if NET6_0_OR_GREATER +#if NETCOREAPP3_0_OR_GREATER var value = input.ToStringValue().AsSpan(); var remove = arguments.At(0).ToStringValue().AsSpan(); #else @@ -138,7 +138,7 @@ public static ValueTask ReplaceFirst(FluidValue input, FilterArgumen return input; } -#if NET6_0_OR_GREATER +#if NETCOREAPP3_0_OR_GREATER var concat = string.Concat(value.Slice(0, index), arguments.At(1).ToStringValue(), value.Slice(index + remove.Length)); #else var concat = string.Concat(value.Substring(0, index), arguments.At(1).ToStringValue(), value.Substring(index + remove.Length)); @@ -153,7 +153,7 @@ public static ValueTask Replace(FluidValue input, FilterArguments ar public static ValueTask ReplaceLast(FluidValue input, FilterArguments arguments, TemplateContext context) { -#if NET6_0_OR_GREATER +#if NETCOREAPP3_0_OR_GREATER var value = input.ToStringValue().AsSpan(); var remove = arguments.At(0).ToStringValue().AsSpan(); #else @@ -167,7 +167,7 @@ public static ValueTask ReplaceLast(FluidValue input, FilterArgument return input; } -#if NET6_0_OR_GREATER +#if NETCOREAPP3_0_OR_GREATER var concat = string.Concat(value.Slice(0, index), arguments.At(1).ToStringValue(), value.Slice(index + remove.Length)); #else var concat = string.Concat(value.Substring(0, index), arguments.At(1).ToStringValue(), value.Substring(index + remove.Length)); @@ -316,7 +316,7 @@ public static ValueTask Truncate(FluidValue input, FilterArguments a var l = Math.Max(0, length - ellipsisStr.Length); -#if NET6_0_OR_GREATER +#if NETCOREAPP3_0_OR_GREATER var concat = string.Concat(inputStr.AsSpan().Slice(0, l), ellipsisStr); #else var concat = string.Concat(inputStr.Substring(0, l), ellipsisStr); diff --git a/Fluid/FiltersCollection.cs b/Fluid/FiltersCollection.cs index 229a5048..74969e6b 100644 --- a/Fluid/FiltersCollection.cs +++ b/Fluid/FiltersCollection.cs @@ -16,6 +16,10 @@ public FilterCollection(int capacity = 0) public int Count => _filters == null ? 0 : _filters.Count; +#if NETSTANDARD2_1 + public void EnsureCapacity(int capacity) => _filters.EnsureCapacity(capacity); +#endif + public void AddFilter(string name, FilterDelegate d) { _filters ??= new Dictionary(); diff --git a/Fluid/FluidParser.cs b/Fluid/FluidParser.cs index 1835a9d4..bab41c7d 100644 --- a/Fluid/FluidParser.cs +++ b/Fluid/FluidParser.cs @@ -98,7 +98,21 @@ public FluidParser(FluidParserOptions parserOptions) Dot.SkipAnd(Identifier.Then(x => new IdentifierSegment(x))) .Or(Indexer) .Or(Call))) - .Then(x => new MemberExpression([x.Item1, .. x.Item2])); + .Then(x => + { + if (x.Item2.Count == 0) + { + return new MemberExpression(x.Item1); + } + + var list = new List(x.Item2.Count + 1); + list.Add(x.Item1); + list.AddRange(x.Item2); + + return new MemberExpression(list); + + // return new MemberExpression([x.Item1, .. x.Item2]); + }); var Range = LParen .SkipAnd(OneOf(Integer, Member.Then(x => x))) diff --git a/Fluid/MemberAccessStrategyExtensions.cs b/Fluid/MemberAccessStrategyExtensions.cs index 19ebaf39..fa3199a5 100644 --- a/Fluid/MemberAccessStrategyExtensions.cs +++ b/Fluid/MemberAccessStrategyExtensions.cs @@ -141,7 +141,7 @@ public static void Register(this MemberAccessStrategy strategy, Type type, param /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, string name, IMemberAccessor getter) { - strategy.Register(typeof(T), [new KeyValuePair(name, getter)]); + strategy.Register(typeof(T), new[] { new KeyValuePair(name, getter) }); } /// @@ -153,7 +153,7 @@ public static void Register(this MemberAccessStrategy strategy, string name, /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, IMemberAccessor getter) { - strategy.Register(typeof(T), [new KeyValuePair("*", getter)]); + strategy.Register(typeof(T), new[] { new KeyValuePair("*", getter) }); } /// @@ -165,7 +165,7 @@ public static void Register(this MemberAccessStrategy strategy, IMemberAccess /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, Type type, IMemberAccessor getter) { - strategy.Register(type, [new KeyValuePair("*", getter)]); + strategy.Register(type, new[] { new KeyValuePair("*", getter) }); } /// @@ -191,7 +191,7 @@ public static void Register(this MemberAccessStrategy strategy, Func /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, Func accessor) { - strategy.Register(typeof(T), [new KeyValuePair("*", new DelegateAccessor(accessor))]); + strategy.Register(typeof(T), new[] { new KeyValuePair("*", new DelegateAccessor(accessor)) }); } /// @@ -213,7 +213,7 @@ public static void Register(this MemberAccessStrategy strategy, Func /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, Func> accessor) { - strategy.Register(typeof(T), [new KeyValuePair("*", new AsyncDelegateAccessor(accessor))]); + strategy.Register(typeof(T), new[] { new KeyValuePair("*", new AsyncDelegateAccessor(accessor)) }); } /// @@ -235,7 +235,7 @@ public static void Register(this MemberAccessStrategy strategy, stri /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, string name, Func> accessor) { - strategy.Register(typeof(T), [new KeyValuePair(name, new AsyncDelegateAccessor((obj, propertyName, ctx) => accessor(obj, ctx)))]); + strategy.Register(typeof(T), new[] { new KeyValuePair(name, new AsyncDelegateAccessor((obj, propertyName, ctx) => accessor(obj, ctx))) }); } /// @@ -257,7 +257,7 @@ public static void Register(this MemberAccessStrategy strategy, stri /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, string name, Func accessor) { - strategy.Register(typeof(T), [new KeyValuePair(name, new DelegateAccessor((obj, propertyName, ctx) => accessor(obj, ctx)))]); + strategy.Register(typeof(T), new[] { new KeyValuePair(name, new DelegateAccessor((obj, propertyName, ctx) => accessor(obj, ctx))) }); } } } diff --git a/Fluid/MemberNameStrategies.cs b/Fluid/MemberNameStrategies.cs index 4f4c673b..e150449e 100644 --- a/Fluid/MemberNameStrategies.cs +++ b/Fluid/MemberNameStrategies.cs @@ -1,7 +1,6 @@ using System.Reflection; -#if !NET6_0_OR_GREATER using System.Text; -#endif + namespace Fluid { public sealed class MemberNameStrategies @@ -12,7 +11,49 @@ public sealed class MemberNameStrategies private static string RenameDefault(MemberInfo member) => member.Name; -#if NET6_0_OR_GREATER +#if NETSTANDARD2_0 + public static string RenameCamelCase(MemberInfo member) + { + var firstChar = member.Name[0]; + + if (firstChar == char.ToLowerInvariant(firstChar)) + { + return member.Name; + } + + var name = member.Name.ToCharArray(); + name[0] = char.ToLowerInvariant(firstChar); + + return new String(name); + } + + public static string RenameSnakeCase(MemberInfo member) + { + var builder = new StringBuilder(); + var name = member.Name; + var previousUpper = false; + + for (var i = 0; i < name.Length; i++) + { + var c = name[i]; + if (char.IsUpper(c)) + { + if (i > 0 && !previousUpper) + { + builder.Append('_'); + } + builder.Append(char.ToLowerInvariant(c)); + previousUpper = true; + } + else + { + builder.Append(c); + previousUpper = false; + } + } + return builder.ToString(); + } +#else public static string RenameCamelCase(MemberInfo member) { return String.Create(member.Name.Length, member.Name, (data, name) => @@ -58,48 +99,6 @@ public static string RenameSnakeCase(MemberInfo member) } }); } -#else - public static string RenameCamelCase(MemberInfo member) - { - var firstChar = member.Name[0]; - - if (firstChar == char.ToLowerInvariant(firstChar)) - { - return member.Name; - } - - var name = member.Name.ToCharArray(); - name[0] = char.ToLowerInvariant(firstChar); - - return new String(name); - } - - public static string RenameSnakeCase(MemberInfo member) - { - var builder = new StringBuilder(); - var name = member.Name; - var previousUpper = false; - - for (var i = 0; i < name.Length; i++) - { - var c = name[i]; - if (char.IsUpper(c)) - { - if (i > 0 && !previousUpper) - { - builder.Append('_'); - } - builder.Append(char.ToLowerInvariant(c)); - previousUpper = true; - } - else - { - builder.Append(c); - previousUpper = false; - } - } - return builder.ToString(); - } #endif } } diff --git a/Fluid/Shims.cs b/Fluid/Shims.cs index 7068bf7d..3eb09ef4 100644 --- a/Fluid/Shims.cs +++ b/Fluid/Shims.cs @@ -1,4 +1,3 @@ -#if !NET6_0_OR_GREATER using System.Runtime.CompilerServices; #nullable enable @@ -6,20 +5,21 @@ namespace Fluid { /// - /// Filling missing bits between netstandard2.0 and higher libs and frameworks. + /// Filling missing bits between netstandard2.0 and highers libs and frameworks. /// internal static class Shims { +#if NETSTANDARD2_0 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string[] Split(this string s, string? separator, StringSplitOptions options = StringSplitOptions.None) { return s.Split(new[] { separator }, options); } - [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions) 512)] public static bool EndsWith(this string s, char c) { - return s.Length > 0 && s[^1] == c; + return s.Length > 0 && s[s.Length - 1] == c; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -27,6 +27,6 @@ public static bool Contains(this string s, char c) { return s.IndexOf(c) != -1; } - } -} #endif + } +} \ No newline at end of file diff --git a/Fluid/Utils/TaskExtensions.cs b/Fluid/Utils/TaskExtensions.cs index 192f54c7..eeb14585 100644 --- a/Fluid/Utils/TaskExtensions.cs +++ b/Fluid/Utils/TaskExtensions.cs @@ -1,16 +1,22 @@ -using System.Runtime.CompilerServices; - namespace Fluid.Utils { internal static class TaskExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsCompletedSuccessfully(this Task t) { -#if NET6_0_OR_GREATER +#if !NETSTANDARD2_0 return t.IsCompletedSuccessfully; #else return t.Status == TaskStatus.RanToCompletion && !t.IsFaulted && !t.IsCanceled; +#endif + } + + public static bool IsCompletedSuccessfully(this ValueTask t) + { +#if !NETSTANDARD2_0 + return t.IsCompletedSuccessfully; +#else + return t.IsCompleted && !t.IsFaulted && !t.IsCanceled; #endif } } diff --git a/Fluid/Values/ArrayValue.cs b/Fluid/Values/ArrayValue.cs index 3ef598bf..030f695d 100644 --- a/Fluid/Values/ArrayValue.cs +++ b/Fluid/Values/ArrayValue.cs @@ -99,6 +99,7 @@ public override decimal ToNumberValue() public IReadOnlyList Values { get; } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { AssertWriteToParameters(writer, encoder, cultureInfo); @@ -109,6 +110,16 @@ public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo } } + public override async ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + AssertWriteToParameters(writer, encoder, cultureInfo); + + foreach (var v in Values) + { + await writer.WriteAsync(v.ToStringValue()); + } + } + public override string ToStringValue() { return String.Join("", Values.Select(x => x.ToStringValue())); diff --git a/Fluid/Values/BlankValue.cs b/Fluid/Values/BlankValue.cs index a0963cd9..3710e275 100644 --- a/Fluid/Values/BlankValue.cs +++ b/Fluid/Values/BlankValue.cs @@ -50,10 +50,16 @@ public override bool IsNil() return true; } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + return default; + } + public override bool Equals(object obj) { // The is operator will return false if null diff --git a/Fluid/Values/BooleanValue.cs b/Fluid/Values/BooleanValue.cs index ad40cf47..4f2711d5 100644 --- a/Fluid/Values/BooleanValue.cs +++ b/Fluid/Values/BooleanValue.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using Fluid.Utils; +using System.Globalization; using System.Text.Encodings.Web; namespace Fluid.Values @@ -28,7 +29,7 @@ public static BooleanValue Create(bool value) public override bool Equals(FluidValue other) { // blank == false -> true - if (other.Type == FluidValues.Blank) return _value == false; + if (other.Type == FluidValues.Blank) return !_value; return _value == other.ToBooleanValue(); } @@ -48,12 +49,32 @@ public override string ToStringValue() return _value ? "true" : "false"; } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { AssertWriteToParameters(writer, encoder, cultureInfo); writer.Write(encoder.Encode(ToStringValue())); } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + AssertWriteToParameters(writer, encoder, cultureInfo); + var task = writer.WriteAsync(encoder.Encode(ToStringValue())); + + if (task.IsCompletedSuccessfully()) + { + return default; + } + + return Awaited(task); + + static async ValueTask Awaited(Task t) + { + await t; + return; + } + } + public override object ToObjectValue() { return _value ? BoxedTrue : BoxedFalse; diff --git a/Fluid/Values/DateTimeValue.cs b/Fluid/Values/DateTimeValue.cs index 57420eaa..2e1ea48d 100644 --- a/Fluid/Values/DateTimeValue.cs +++ b/Fluid/Values/DateTimeValue.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using Fluid.Utils; +using System.Globalization; using System.Text.Encodings.Web; namespace Fluid.Values @@ -44,12 +45,32 @@ public override string ToStringValue() return _value.ToString("u", CultureInfo.InvariantCulture); } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { AssertWriteToParameters(writer, encoder, cultureInfo); writer.Write(_value.ToString("u", cultureInfo)); } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + AssertWriteToParameters(writer, encoder, cultureInfo); + var task = writer.WriteAsync(_value.ToString("u", cultureInfo)); + + if (task.IsCompletedSuccessfully()) + { + return default; + } + + return Awaited(task); + + static async ValueTask Awaited(Task t) + { + await t; + return; + } + } + public override object ToObjectValue() { return _value; diff --git a/Fluid/Values/DictionaryValue.cs b/Fluid/Values/DictionaryValue.cs index a8962fcf..a4659967 100644 --- a/Fluid/Values/DictionaryValue.cs +++ b/Fluid/Values/DictionaryValue.cs @@ -90,10 +90,16 @@ public override decimal ToNumberValue() return 0; } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + return default; + } + public override string ToStringValue() { return ""; diff --git a/Fluid/Values/EmptyValue.cs b/Fluid/Values/EmptyValue.cs index 233d31f6..7042a7b7 100644 --- a/Fluid/Values/EmptyValue.cs +++ b/Fluid/Values/EmptyValue.cs @@ -50,10 +50,16 @@ public override bool IsNil() return true; } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + return default; + } + public override bool Equals(object obj) { // The is operator will return false if null diff --git a/Fluid/Values/FactoryValue.cs b/Fluid/Values/FactoryValue.cs index 9367b547..0ce72632 100644 --- a/Fluid/Values/FactoryValue.cs +++ b/Fluid/Values/FactoryValue.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using Fluid.Utils; +using System.Globalization; using System.Text.Encodings.Web; namespace Fluid.Values @@ -79,10 +80,30 @@ public override string ToStringValue() return _factory.Value.ToStringValue(); } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { AssertWriteToParameters(writer, encoder, cultureInfo); _factory.Value.WriteTo(writer, encoder, cultureInfo); } + + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + AssertWriteToParameters(writer, encoder, cultureInfo); + var task = _factory.Value.WriteToAsync(writer, encoder, cultureInfo); + + if (task.IsCompletedSuccessfully()) + { + return default; + } + + return Awaited(task); + + static async ValueTask Awaited(ValueTask t) + { + await t; + return; + } + } } } diff --git a/Fluid/Values/FluidValue.cs b/Fluid/Values/FluidValue.cs index c49e0e1c..5c463a8c 100644 --- a/Fluid/Values/FluidValue.cs +++ b/Fluid/Values/FluidValue.cs @@ -9,8 +9,17 @@ namespace Fluid.Values public abstract class FluidValue : IEquatable #pragma warning restore CA1067 { + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public abstract void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo); + public virtual ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { +#pragma warning disable CS0618 // Type or member is obsolete + WriteTo(writer, encoder, cultureInfo); +#pragma warning restore CS0618 // Type or member is obsolete + return default; + } + private static Dictionary _genericDictionaryTypeCache = new(); [Conditional("DEBUG")] diff --git a/Fluid/Values/FluidValueExtensions.cs b/Fluid/Values/FluidValueExtensions.cs index 21e613c3..2c1abcff 100644 --- a/Fluid/Values/FluidValueExtensions.cs +++ b/Fluid/Values/FluidValueExtensions.cs @@ -10,16 +10,16 @@ public static class FluidValueExtensions // The K specifier is optional when used in TryParseExact, so // if a TZ is not specified, it will still match - private static readonly string[] DefaultFormats = [ + private static readonly string[] DefaultFormats = { "yyyy-MM-ddTHH:mm:ss.FFFK", "yyyy-MM-ddTHH:mm:ssK", "yyyy-MM-ddTHH:mmK", "yyyy-MM-dd", "yyyy-MM", "yyyy" - ]; + }; - private static readonly string[] SecondaryFormats = [ + private static readonly string[] SecondaryFormats = { // Formats used in DatePrototype toString methods "ddd MMM dd yyyy HH:mm:ss 'GMT'K", "ddd MMM dd yyyy", @@ -47,7 +47,7 @@ public static class FluidValueExtensions "THH:mm:ssK", "THH:mmK", "THHK" - ]; + }; public static bool TryGetDateTimeInput(this FluidValue input, TemplateContext context, out DateTimeOffset result) { diff --git a/Fluid/Values/ForLoopValue.cs b/Fluid/Values/ForLoopValue.cs index 097fcbf0..fb3b58c4 100644 --- a/Fluid/Values/ForLoopValue.cs +++ b/Fluid/Values/ForLoopValue.cs @@ -57,8 +57,14 @@ public override ValueTask GetValueAsync(string name, TemplateContext }; } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { } + + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + return default; + } } } diff --git a/Fluid/Values/FunctionValue.cs b/Fluid/Values/FunctionValue.cs index 0e4a6933..d43dd207 100644 --- a/Fluid/Values/FunctionValue.cs +++ b/Fluid/Values/FunctionValue.cs @@ -55,10 +55,16 @@ public override bool IsNil() return false; } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { // A function value should be invoked and its result used instead. - // Calling write to is equivalent to renderding {{ alert }} instead of {{ alert() }} + // Calling write to is equivalent to rendering {{ alert }} instead of {{ alert() }} + } + + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + return default; } public override bool Equals(object obj) diff --git a/Fluid/Values/NilValue.cs b/Fluid/Values/NilValue.cs index e5dc7145..79cc9d47 100644 --- a/Fluid/Values/NilValue.cs +++ b/Fluid/Values/NilValue.cs @@ -52,10 +52,16 @@ public override bool IsNil() return true; } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + return default; + } + public override bool Equals(object obj) { // The is operator will return false if null diff --git a/Fluid/Values/NumberValue.cs b/Fluid/Values/NumberValue.cs index 98bc5325..31075579 100644 --- a/Fluid/Values/NumberValue.cs +++ b/Fluid/Values/NumberValue.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using Fluid.Utils; +using System.Globalization; using System.Text.Encodings.Web; namespace Fluid.Values @@ -57,12 +58,32 @@ public override string ToStringValue() return _value.ToString(CultureInfo.InvariantCulture); } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { AssertWriteToParameters(writer, encoder, cultureInfo); writer.Write(encoder.Encode(_value.ToString(cultureInfo))); } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + AssertWriteToParameters(writer, encoder, cultureInfo); + var task = writer.WriteAsync(encoder.Encode(_value.ToString(cultureInfo))); + + if (task.IsCompletedSuccessfully()) + { + return default; + } + + return Awaited(task); + + static async ValueTask Awaited(Task t) + { + await t; + return; + } + } + public override object ToObjectValue() { return _value; @@ -135,7 +156,7 @@ public static int GetScale(decimal value) return 0; } - int[] bits = decimal.GetBits(value); + var bits = decimal.GetBits(value); return (int)((bits[3] >> 16) & 0x7F); } diff --git a/Fluid/Values/ObjectValueBase.cs b/Fluid/Values/ObjectValueBase.cs index 793a3366..e7ebf524 100644 --- a/Fluid/Values/ObjectValueBase.cs +++ b/Fluid/Values/ObjectValueBase.cs @@ -1,4 +1,5 @@ -using System.Collections; +using Fluid.Utils; +using System.Collections; using System.Globalization; using System.Text.Encodings.Web; @@ -104,7 +105,7 @@ private async ValueTask GetNestedValueAsync(string name, TemplateCon { var members = name.Split(MemberSeparators); - object target = Value; + var target = Value; foreach (var prop in members) { @@ -148,12 +149,32 @@ public override decimal ToNumberValue() return Convert.ToDecimal(Value); } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { AssertWriteToParameters(writer, encoder, cultureInfo); writer.Write(encoder.Encode(ToStringValue())); } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + AssertWriteToParameters(writer, encoder, cultureInfo); + var task = writer.WriteAsync(encoder.Encode(ToStringValue())); + + if (task.IsCompletedSuccessfully()) + { + return default; + } + + return Awaited(task); + + static async ValueTask Awaited(Task t) + { + await t; + return; + } + } + public override string ToStringValue() { return Convert.ToString(Value); diff --git a/Fluid/Values/StringValue.cs b/Fluid/Values/StringValue.cs index 1c01fd6b..a5d92cf5 100644 --- a/Fluid/Values/StringValue.cs +++ b/Fluid/Values/StringValue.cs @@ -1,4 +1,5 @@ -using Parlot; +using Fluid.Utils; +using Parlot; using System.Globalization; using System.Runtime.CompilerServices; using System.Text.Encodings.Web; @@ -110,7 +111,7 @@ protected override FluidValue GetValue(string name, TemplateContext context) { "size" => NumberValue.Create(_value.Length), "first" => _value.Length > 0 ? Create(_value[0]) : NilValue.Instance, - "last" => _value.Length > 0 ? Create(_value[_value.Length - 1]) : NilValue.Instance, + "last" => _value.Length > 0 ? Create(_value[^1]) : NilValue.Instance, _ => NilValue.Instance, }; } @@ -140,6 +141,7 @@ public override string ToStringValue() return _value; } + [Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")] public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { AssertWriteToParameters(writer, encoder, cultureInfo); @@ -165,6 +167,46 @@ public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo } } + public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) + { + AssertWriteToParameters(writer, encoder, cultureInfo); + if (string.IsNullOrEmpty(_value)) + { + return default; + } + + Task task; + + if (Encode) + { + // perf: Don't use this overload + // encoder.Encode(writer, _value); + + // Use a transient string instead of calling + // encoder.Encode(TextWriter) since it would + // call writer.Write on each char if the string + // has even a single char to encode + task = writer.WriteAsync(encoder.Encode(_value)); + } + else + { + task = writer.WriteAsync(_value); + } + + if (task.IsCompletedSuccessfully()) + { + return default; + } + + return Awaited(task); + + static async ValueTask Awaited(Task t) + { + await t; + return; + } + } + public override object ToObjectValue() { return _value; From 07c735a905099e6a8045bd123b5c5e543aad3f97 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 28 May 2024 17:54:54 -0700 Subject: [PATCH 2/3] Fix merge --- Fluid.Tests/MvcViewEngine/ViewEngineTests.cs | 4 +- Fluid/Accessors/PropertyInfoAccessor.cs | 2 +- Fluid/Filters/ColorFilters.cs | 4 +- Fluid/Filters/StringFilters.cs | 10 +-- Fluid/FiltersCollection.cs | 4 - Fluid/FluidParser.cs | 16 +--- Fluid/MemberAccessStrategyExtensions.cs | 14 +-- Fluid/MemberNameStrategies.cs | 89 ++++++++++---------- Fluid/Shims.cs | 12 +-- Fluid/Utils/TaskExtensions.cs | 9 -- Fluid/Values/FactoryValue.cs | 5 +- Fluid/Values/FluidValueExtensions.cs | 8 +- 12 files changed, 74 insertions(+), 103 deletions(-) diff --git a/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs b/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs index 715b7c78..94494012 100644 --- a/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs +++ b/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs @@ -1,15 +1,13 @@ using Fluid.Tests.Mocks; using Fluid.ViewEngine; -using System; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Xunit; namespace Fluid.Tests.MvcViewEngine { - public partial class ViewEngineTests + public class ViewEngineTests { FluidViewEngineOptions _options = new (); FluidViewRenderer _renderer; diff --git a/Fluid/Accessors/PropertyInfoAccessor.cs b/Fluid/Accessors/PropertyInfoAccessor.cs index 5f24625e..0da4f90c 100644 --- a/Fluid/Accessors/PropertyInfoAccessor.cs +++ b/Fluid/Accessors/PropertyInfoAccessor.cs @@ -12,7 +12,7 @@ public PropertyInfoAccessor(PropertyInfo propertyInfo) var d = propertyInfo.GetGetMethod().CreateDelegate(delegateType); var invokerType = typeof(Invoker<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType); - _invoker = Activator.CreateInstance(invokerType, new object[] { d }) as IInvoker; + _invoker = Activator.CreateInstance(invokerType, [d]) as IInvoker; } public object Get(object obj, string name, TemplateContext ctx) diff --git a/Fluid/Filters/ColorFilters.cs b/Fluid/Filters/ColorFilters.cs index e78f35ce..7764618e 100644 --- a/Fluid/Filters/ColorFilters.cs +++ b/Fluid/Filters/ColorFilters.cs @@ -635,7 +635,7 @@ public static explicit operator HexColor(RgbColor rgbColor) { private const double DefaultTransperency = 1.0; - private static readonly char[] _colorSeparators = new[] { '(', ',', ' ', ')' }; + private static readonly char[] _colorSeparators = ['(', ',', ' ', ')']; public static readonly RgbColor Empty = default; @@ -814,7 +814,7 @@ private readonly struct HslColor { private const double DefaultTransparency = 1.0; - private static readonly char[] _colorSeparators = new[] { '(', ',', ' ', ')' }; + private static readonly char[] _colorSeparators = ['(', ',', ' ', ')']; public static readonly HslColor Empty = default; diff --git a/Fluid/Filters/StringFilters.cs b/Fluid/Filters/StringFilters.cs index 19c6fa79..3d11f4a0 100644 --- a/Fluid/Filters/StringFilters.cs +++ b/Fluid/Filters/StringFilters.cs @@ -124,7 +124,7 @@ public static ValueTask RemoveLast(FluidValue input, FilterArguments public static ValueTask ReplaceFirst(FluidValue input, FilterArguments arguments, TemplateContext context) { -#if NETCOREAPP3_0_OR_GREATER +#if NET6_0_OR_GREATER var value = input.ToStringValue().AsSpan(); var remove = arguments.At(0).ToStringValue().AsSpan(); #else @@ -138,7 +138,7 @@ public static ValueTask ReplaceFirst(FluidValue input, FilterArgumen return input; } -#if NETCOREAPP3_0_OR_GREATER +#if NET6_0_OR_GREATER var concat = string.Concat(value.Slice(0, index), arguments.At(1).ToStringValue(), value.Slice(index + remove.Length)); #else var concat = string.Concat(value.Substring(0, index), arguments.At(1).ToStringValue(), value.Substring(index + remove.Length)); @@ -153,7 +153,7 @@ public static ValueTask Replace(FluidValue input, FilterArguments ar public static ValueTask ReplaceLast(FluidValue input, FilterArguments arguments, TemplateContext context) { -#if NETCOREAPP3_0_OR_GREATER +#if NET6_0_OR_GREATER var value = input.ToStringValue().AsSpan(); var remove = arguments.At(0).ToStringValue().AsSpan(); #else @@ -167,7 +167,7 @@ public static ValueTask ReplaceLast(FluidValue input, FilterArgument return input; } -#if NETCOREAPP3_0_OR_GREATER +#if NET6_0_OR_GREATER var concat = string.Concat(value.Slice(0, index), arguments.At(1).ToStringValue(), value.Slice(index + remove.Length)); #else var concat = string.Concat(value.Substring(0, index), arguments.At(1).ToStringValue(), value.Substring(index + remove.Length)); @@ -316,7 +316,7 @@ public static ValueTask Truncate(FluidValue input, FilterArguments a var l = Math.Max(0, length - ellipsisStr.Length); -#if NETCOREAPP3_0_OR_GREATER +#if NET6_0_OR_GREATER var concat = string.Concat(inputStr.AsSpan().Slice(0, l), ellipsisStr); #else var concat = string.Concat(inputStr.Substring(0, l), ellipsisStr); diff --git a/Fluid/FiltersCollection.cs b/Fluid/FiltersCollection.cs index 74969e6b..229a5048 100644 --- a/Fluid/FiltersCollection.cs +++ b/Fluid/FiltersCollection.cs @@ -16,10 +16,6 @@ public FilterCollection(int capacity = 0) public int Count => _filters == null ? 0 : _filters.Count; -#if NETSTANDARD2_1 - public void EnsureCapacity(int capacity) => _filters.EnsureCapacity(capacity); -#endif - public void AddFilter(string name, FilterDelegate d) { _filters ??= new Dictionary(); diff --git a/Fluid/FluidParser.cs b/Fluid/FluidParser.cs index bab41c7d..1835a9d4 100644 --- a/Fluid/FluidParser.cs +++ b/Fluid/FluidParser.cs @@ -98,21 +98,7 @@ public FluidParser(FluidParserOptions parserOptions) Dot.SkipAnd(Identifier.Then(x => new IdentifierSegment(x))) .Or(Indexer) .Or(Call))) - .Then(x => - { - if (x.Item2.Count == 0) - { - return new MemberExpression(x.Item1); - } - - var list = new List(x.Item2.Count + 1); - list.Add(x.Item1); - list.AddRange(x.Item2); - - return new MemberExpression(list); - - // return new MemberExpression([x.Item1, .. x.Item2]); - }); + .Then(x => new MemberExpression([x.Item1, .. x.Item2])); var Range = LParen .SkipAnd(OneOf(Integer, Member.Then(x => x))) diff --git a/Fluid/MemberAccessStrategyExtensions.cs b/Fluid/MemberAccessStrategyExtensions.cs index fa3199a5..19ebaf39 100644 --- a/Fluid/MemberAccessStrategyExtensions.cs +++ b/Fluid/MemberAccessStrategyExtensions.cs @@ -141,7 +141,7 @@ public static void Register(this MemberAccessStrategy strategy, Type type, param /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, string name, IMemberAccessor getter) { - strategy.Register(typeof(T), new[] { new KeyValuePair(name, getter) }); + strategy.Register(typeof(T), [new KeyValuePair(name, getter)]); } /// @@ -153,7 +153,7 @@ public static void Register(this MemberAccessStrategy strategy, string name, /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, IMemberAccessor getter) { - strategy.Register(typeof(T), new[] { new KeyValuePair("*", getter) }); + strategy.Register(typeof(T), [new KeyValuePair("*", getter)]); } /// @@ -165,7 +165,7 @@ public static void Register(this MemberAccessStrategy strategy, IMemberAccess /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, Type type, IMemberAccessor getter) { - strategy.Register(type, new[] { new KeyValuePair("*", getter) }); + strategy.Register(type, [new KeyValuePair("*", getter)]); } /// @@ -191,7 +191,7 @@ public static void Register(this MemberAccessStrategy strategy, Func /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, Func accessor) { - strategy.Register(typeof(T), new[] { new KeyValuePair("*", new DelegateAccessor(accessor)) }); + strategy.Register(typeof(T), [new KeyValuePair("*", new DelegateAccessor(accessor))]); } /// @@ -213,7 +213,7 @@ public static void Register(this MemberAccessStrategy strategy, Func /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, Func> accessor) { - strategy.Register(typeof(T), new[] { new KeyValuePair("*", new AsyncDelegateAccessor(accessor)) }); + strategy.Register(typeof(T), [new KeyValuePair("*", new AsyncDelegateAccessor(accessor))]); } /// @@ -235,7 +235,7 @@ public static void Register(this MemberAccessStrategy strategy, stri /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, string name, Func> accessor) { - strategy.Register(typeof(T), new[] { new KeyValuePair(name, new AsyncDelegateAccessor((obj, propertyName, ctx) => accessor(obj, ctx))) }); + strategy.Register(typeof(T), [new KeyValuePair(name, new AsyncDelegateAccessor((obj, propertyName, ctx) => accessor(obj, ctx)))]); } /// @@ -257,7 +257,7 @@ public static void Register(this MemberAccessStrategy strategy, stri /// The instance used to retrieve the value. public static void Register(this MemberAccessStrategy strategy, string name, Func accessor) { - strategy.Register(typeof(T), new[] { new KeyValuePair(name, new DelegateAccessor((obj, propertyName, ctx) => accessor(obj, ctx))) }); + strategy.Register(typeof(T), [new KeyValuePair(name, new DelegateAccessor((obj, propertyName, ctx) => accessor(obj, ctx)))]); } } } diff --git a/Fluid/MemberNameStrategies.cs b/Fluid/MemberNameStrategies.cs index e150449e..4f4c673b 100644 --- a/Fluid/MemberNameStrategies.cs +++ b/Fluid/MemberNameStrategies.cs @@ -1,6 +1,7 @@ using System.Reflection; +#if !NET6_0_OR_GREATER using System.Text; - +#endif namespace Fluid { public sealed class MemberNameStrategies @@ -11,49 +12,7 @@ public sealed class MemberNameStrategies private static string RenameDefault(MemberInfo member) => member.Name; -#if NETSTANDARD2_0 - public static string RenameCamelCase(MemberInfo member) - { - var firstChar = member.Name[0]; - - if (firstChar == char.ToLowerInvariant(firstChar)) - { - return member.Name; - } - - var name = member.Name.ToCharArray(); - name[0] = char.ToLowerInvariant(firstChar); - - return new String(name); - } - - public static string RenameSnakeCase(MemberInfo member) - { - var builder = new StringBuilder(); - var name = member.Name; - var previousUpper = false; - - for (var i = 0; i < name.Length; i++) - { - var c = name[i]; - if (char.IsUpper(c)) - { - if (i > 0 && !previousUpper) - { - builder.Append('_'); - } - builder.Append(char.ToLowerInvariant(c)); - previousUpper = true; - } - else - { - builder.Append(c); - previousUpper = false; - } - } - return builder.ToString(); - } -#else +#if NET6_0_OR_GREATER public static string RenameCamelCase(MemberInfo member) { return String.Create(member.Name.Length, member.Name, (data, name) => @@ -99,6 +58,48 @@ public static string RenameSnakeCase(MemberInfo member) } }); } +#else + public static string RenameCamelCase(MemberInfo member) + { + var firstChar = member.Name[0]; + + if (firstChar == char.ToLowerInvariant(firstChar)) + { + return member.Name; + } + + var name = member.Name.ToCharArray(); + name[0] = char.ToLowerInvariant(firstChar); + + return new String(name); + } + + public static string RenameSnakeCase(MemberInfo member) + { + var builder = new StringBuilder(); + var name = member.Name; + var previousUpper = false; + + for (var i = 0; i < name.Length; i++) + { + var c = name[i]; + if (char.IsUpper(c)) + { + if (i > 0 && !previousUpper) + { + builder.Append('_'); + } + builder.Append(char.ToLowerInvariant(c)); + previousUpper = true; + } + else + { + builder.Append(c); + previousUpper = false; + } + } + return builder.ToString(); + } #endif } } diff --git a/Fluid/Shims.cs b/Fluid/Shims.cs index 3eb09ef4..7068bf7d 100644 --- a/Fluid/Shims.cs +++ b/Fluid/Shims.cs @@ -1,3 +1,4 @@ +#if !NET6_0_OR_GREATER using System.Runtime.CompilerServices; #nullable enable @@ -5,21 +6,20 @@ namespace Fluid { /// - /// Filling missing bits between netstandard2.0 and highers libs and frameworks. + /// Filling missing bits between netstandard2.0 and higher libs and frameworks. /// internal static class Shims { -#if NETSTANDARD2_0 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string[] Split(this string s, string? separator, StringSplitOptions options = StringSplitOptions.None) { return s.Split(new[] { separator }, options); } - [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions) 512)] + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] public static bool EndsWith(this string s, char c) { - return s.Length > 0 && s[s.Length - 1] == c; + return s.Length > 0 && s[^1] == c; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -27,6 +27,6 @@ public static bool Contains(this string s, char c) { return s.IndexOf(c) != -1; } -#endif } -} \ No newline at end of file +} +#endif diff --git a/Fluid/Utils/TaskExtensions.cs b/Fluid/Utils/TaskExtensions.cs index eeb14585..21f63fb7 100644 --- a/Fluid/Utils/TaskExtensions.cs +++ b/Fluid/Utils/TaskExtensions.cs @@ -8,15 +8,6 @@ public static bool IsCompletedSuccessfully(this Task t) return t.IsCompletedSuccessfully; #else return t.Status == TaskStatus.RanToCompletion && !t.IsFaulted && !t.IsCanceled; -#endif - } - - public static bool IsCompletedSuccessfully(this ValueTask t) - { -#if !NETSTANDARD2_0 - return t.IsCompletedSuccessfully; -#else - return t.IsCompleted && !t.IsFaulted && !t.IsCanceled; #endif } } diff --git a/Fluid/Values/FactoryValue.cs b/Fluid/Values/FactoryValue.cs index 0ce72632..d198b4ac 100644 --- a/Fluid/Values/FactoryValue.cs +++ b/Fluid/Values/FactoryValue.cs @@ -1,5 +1,4 @@ -using Fluid.Utils; -using System.Globalization; +using System.Globalization; using System.Text.Encodings.Web; namespace Fluid.Values @@ -92,7 +91,7 @@ public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, C AssertWriteToParameters(writer, encoder, cultureInfo); var task = _factory.Value.WriteToAsync(writer, encoder, cultureInfo); - if (task.IsCompletedSuccessfully()) + if (task.IsCompletedSuccessfully) { return default; } diff --git a/Fluid/Values/FluidValueExtensions.cs b/Fluid/Values/FluidValueExtensions.cs index 2c1abcff..21e613c3 100644 --- a/Fluid/Values/FluidValueExtensions.cs +++ b/Fluid/Values/FluidValueExtensions.cs @@ -10,16 +10,16 @@ public static class FluidValueExtensions // The K specifier is optional when used in TryParseExact, so // if a TZ is not specified, it will still match - private static readonly string[] DefaultFormats = { + private static readonly string[] DefaultFormats = [ "yyyy-MM-ddTHH:mm:ss.FFFK", "yyyy-MM-ddTHH:mm:ssK", "yyyy-MM-ddTHH:mmK", "yyyy-MM-dd", "yyyy-MM", "yyyy" - }; + ]; - private static readonly string[] SecondaryFormats = { + private static readonly string[] SecondaryFormats = [ // Formats used in DatePrototype toString methods "ddd MMM dd yyyy HH:mm:ss 'GMT'K", "ddd MMM dd yyyy", @@ -47,7 +47,7 @@ public static class FluidValueExtensions "THH:mm:ssK", "THH:mmK", "THHK" - }; + ]; public static bool TryGetDateTimeInput(this FluidValue input, TemplateContext context, out DateTimeOffset result) { From 5e87b907e6dfe1e1899ecb146be18b5ca721527d Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 28 May 2024 17:59:11 -0700 Subject: [PATCH 3/3] Revert again --- Fluid/Utils/TaskExtensions.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Fluid/Utils/TaskExtensions.cs b/Fluid/Utils/TaskExtensions.cs index 21f63fb7..192f54c7 100644 --- a/Fluid/Utils/TaskExtensions.cs +++ b/Fluid/Utils/TaskExtensions.cs @@ -1,10 +1,13 @@ +using System.Runtime.CompilerServices; + namespace Fluid.Utils { internal static class TaskExtensions { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsCompletedSuccessfully(this Task t) { -#if !NETSTANDARD2_0 +#if NET6_0_OR_GREATER return t.IsCompletedSuccessfully; #else return t.Status == TaskStatus.RanToCompletion && !t.IsFaulted && !t.IsCanceled;