Skip to content
Open
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
9 changes: 7 additions & 2 deletions src/Irihi.Text.Toon/Http/ToonContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ public static ToonContent Create<T>(T value, ToonSerializerOptions? options = nu

/// <inheritdoc />
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
=> SerializeToStreamAsync(stream, context, CancellationToken.None);
=> SerializeCoreAsync(stream, CancellationToken.None);

#if NET8_0_OR_GREATER
/// <inheritdoc />
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken)
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken)
=> SerializeCoreAsync(stream, cancellationToken);
#endif

private async Task SerializeCoreAsync(Stream stream, CancellationToken cancellationToken)
{
if (_value is null)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Irihi.Text.Toon/Http/ToonHttpClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ public static Task<HttpResponseMessage> PutAsToonAsync<T>(this HttpClient client
public static async Task<T?> ReadFromToonAsync<T>(this HttpContent content,
ToonSerializerOptions? options, CancellationToken cancellationToken = default)
{
#if NETSTANDARD2_0
var toon = await content.ReadAsStringAsync().ConfigureAwait(false);
#else
var toon = await content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#endif
if (string.IsNullOrWhiteSpace(toon) || toon == "null") return default;
return ToonSerializer.Deserialize<T>(toon, options);
}
Expand Down
2 changes: 2 additions & 0 deletions src/Irihi.Text.Toon/Internal/BuiltInConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ public override void Write(ref Utf8ToonWriter writer, DateTimeOffset value, Toon
=> writer.WriteStringValue(value.ToString("O"));
}

#if NET8_0_OR_GREATER
internal sealed class DateOnlyToonConverter : ToonConverter<DateOnly>
{
public override DateOnly Read(ref Utf8ToonReader reader, Type typeToConvert, ToonSerializerOptions options)
Expand All @@ -154,6 +155,7 @@ public override TimeOnly Read(ref Utf8ToonReader reader, Type typeToConvert, Too
public override void Write(ref Utf8ToonWriter writer, TimeOnly value, ToonSerializerOptions options)
=> writer.WriteStringValue(value.ToString("O"));
}
#endif

internal sealed class TimeSpanToonConverter : ToonConverter<TimeSpan>
{
Expand Down
22 changes: 22 additions & 0 deletions src/Irihi.Text.Toon/Internal/CallerArgumentExpressionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#if NETSTANDARD2_0

namespace System.Runtime.CompilerServices
{
/// <summary>
/// Indicates that a parameter captures the expression passed for another parameter as a string.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
internal sealed class CallerArgumentExpressionAttribute : Attribute
{
/// <summary>Initializes a new instance with the target parameter name.</summary>
public CallerArgumentExpressionAttribute(string parameterName)
{
ParameterName = parameterName;
}

/// <summary>Gets the name of the parameter whose expression is captured.</summary>
public string ParameterName { get; }
}
}

#endif
4 changes: 4 additions & 0 deletions src/Irihi.Text.Toon/Internal/ConverterFactories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ internal sealed class EnumToonConverter<T> : ToonConverter<T> where T : struct,
public override T Read(ref Utf8ToonReader reader, Type typeToConvert, ToonSerializerOptions options)
{
if (options.EnumFormat == ToonEnumFormat.String || reader.TokenType == ToonTokenType.String)
#if NETSTANDARD2_0
return (T)Enum.Parse(typeof(T), reader.GetString(), ignoreCase: true);
#else
return Enum.Parse<T>(reader.GetString(), ignoreCase: true);
#endif
if (reader.TokenType == ToonTokenType.Number)
return (T)Enum.ToObject(typeof(T), reader.GetInt64());
throw new ToonException($"Cannot deserialize {reader.TokenType} as '{typeof(T).Name}'.");
Expand Down
43 changes: 43 additions & 0 deletions src/Irihi.Text.Toon/Internal/EncodingPolyfill.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Runtime.CompilerServices;
using System.Text;

namespace Irihi.Text.Toon.Internal;

/// <summary>Polyfill for <c>Encoding.UTF8</c> span-based APIs on netstandard2.0.</summary>
internal static class EncodingPolyfill
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetUtf8String(ReadOnlySpan<byte> bytes)
{
#if NETSTANDARD2_0
if (bytes.IsEmpty) return string.Empty;
return Encoding.UTF8.GetString(bytes.ToArray());
#else
return Encoding.UTF8.GetString(bytes);
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetUtf8Bytes(string value, Span<byte> destination)
{
#if NETSTANDARD2_0
byte[] bytes = Encoding.UTF8.GetBytes(value);
bytes.CopyTo(destination);
return bytes.Length;
#else
return Encoding.UTF8.GetBytes(value, destination);
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetUtf8Bytes(ReadOnlySpan<char> value, Span<byte> destination)
{
#if NETSTANDARD2_0
byte[] bytes = Encoding.UTF8.GetBytes(value.ToArray());
bytes.CopyTo(destination);
return bytes.Length;
#else
return Encoding.UTF8.GetBytes(value, destination);
#endif
}
}
110 changes: 110 additions & 0 deletions src/Irihi.Text.Toon/Internal/IndexRange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#if NETSTANDARD2_0

namespace System
{
/// <summary>Represents a type that can index a collection from the start or the end.</summary>
public readonly struct Index : IEquatable<Index>
{
private readonly int _value;

/// <summary>Creates an index with the given value and direction.</summary>
public Index(int value, bool fromEnd = false)
{
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
_value = fromEnd ? ~value : value;
}

/// <summary>Gets the raw value (non-negative when from start, complemented when from end).</summary>
public int Value => _value < 0 ? ~_value : _value;

/// <summary>Whether this index counts from the end.</summary>
public bool IsFromEnd => _value < 0;

/// <summary>Returns the absolute offset from the start for a collection of the given length.</summary>
public int GetOffset(int length)
{
int offset = _value;
if (IsFromEnd)
offset += length + 1;
return offset;
}

/// <inheritdoc />
public bool Equals(Index other) => _value == other._value;

/// <inheritdoc />
public override bool Equals(object? obj) => obj is Index other && Equals(other);

/// <inheritdoc />
public override int GetHashCode() => _value;

/// <inheritdoc />
public override string ToString()
{
if (IsFromEnd)
return "^" + Value;
return Value.ToString();
}

public static implicit operator Index(int value) => new Index(value);

Check failure on line 50 in src/Irihi.Text.Toon/Internal/IndexRange.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Missing XML comment for publicly visible type or member 'Index.implicit operator Index(int)'

Check failure on line 50 in src/Irihi.Text.Toon/Internal/IndexRange.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Missing XML comment for publicly visible type or member 'Index.implicit operator Index(int)'

public static bool operator ==(Index left, Index right) => left.Equals(right);

Check failure on line 52 in src/Irihi.Text.Toon/Internal/IndexRange.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Missing XML comment for publicly visible type or member 'Index.operator ==(Index, Index)'
public static bool operator !=(Index left, Index right) => !left.Equals(right);

Check failure on line 53 in src/Irihi.Text.Toon/Internal/IndexRange.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Missing XML comment for publicly visible type or member 'Index.operator !=(Index, Index)'
}

/// <summary>Represents a range with a start and end index.</summary>
public readonly struct Range : IEquatable<Range>
{
/// <summary>Gets the start index (inclusive).</summary>
public Index Start { get; }

/// <summary>Gets the end index (exclusive).</summary>
public Index End { get; }

/// <summary>Creates a range from start (inclusive) to end (exclusive).</summary>
public Range(Index start, Index end)
{
Start = start;
End = end;
}

/// <summary>Creates a range from <paramref name="start"/> to the end of the collection.</summary>
public static Range StartAt(Index start) => new Range(start, new Index(0, fromEnd: true));

/// <summary>Creates a range from the start of the collection to <paramref name="end"/>.</summary>
public static Range EndAt(Index end) => new Range(new Index(0), end);

/// <summary>A range that covers the entire collection.</summary>
public static Range All => new Range(new Index(0), new Index(0, fromEnd: true));

/// <summary>Calculates the start offset and length for a collection of <paramref name="length"/>.</summary>
public (int Offset, int Length) GetOffsetAndLength(int length)
{
int start = Start.GetOffset(length);
int end = End.GetOffset(length);

if ((uint)end > (uint)length || (uint)start > (uint)end)
throw new ArgumentOutOfRangeException(nameof(length));

return (start, end - start);
}

/// <inheritdoc />
public bool Equals(Range other) => Start.Equals(other.Start) && End.Equals(other.End);

/// <inheritdoc />
public override bool Equals(object? obj) => obj is Range other && Equals(other);

/// <inheritdoc />
public override int GetHashCode() => unchecked(Start.GetHashCode() * 31 + End.GetHashCode());

/// <inheritdoc />
public override string ToString() => Start + ".." + End;

public static bool operator ==(Range left, Range right) => left.Equals(right);

Check failure on line 105 in src/Irihi.Text.Toon/Internal/IndexRange.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Missing XML comment for publicly visible type or member 'Range.operator ==(Range, Range)'
public static bool operator !=(Range left, Range right) => !left.Equals(right);

Check failure on line 106 in src/Irihi.Text.Toon/Internal/IndexRange.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Missing XML comment for publicly visible type or member 'Range.operator !=(Range, Range)'
}
}

#endif
17 changes: 17 additions & 0 deletions src/Irihi.Text.Toon/Internal/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#if NETSTANDARD2_0

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
internal static class IsExternalInit
{
}
}

#endif
19 changes: 19 additions & 0 deletions src/Irihi.Text.Toon/Internal/KvpExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#if NETSTANDARD2_0

using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Irihi.Text.Toon.Internal;

/// <summary>Polyfill <c>Deconstruct</c> for <see cref="KeyValuePair{TKey, TValue}"/> on netstandard2.0.</summary>
internal static class KvpExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value)
{
key = kvp.Key;
value = kvp.Value;
}
}

#endif
18 changes: 18 additions & 0 deletions src/Irihi.Text.Toon/Internal/MemoryMarshalShim.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Irihi.Text.Toon.Internal;

/// <summary>Polyfill for <see cref="MemoryMarshal.CreateSpan{T}(ref T, int)"/> on netstandard2.0.</summary>

Check failure on line 6 in src/Irihi.Text.Toon/Internal/MemoryMarshalShim.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

XML comment has cref attribute 'CreateSpan{T}(ref T, int)' that could not be resolved

Check failure on line 6 in src/Irihi.Text.Toon/Internal/MemoryMarshalShim.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

XML comment has cref attribute 'CreateSpan{T}(ref T, int)' that could not be resolved
internal static class MemoryMarshalShim
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe Span<T> CreateSpan<T>(ref T reference, int length)
{
#if NETSTANDARD2_0
return new Span<T>(Unsafe.AsPointer(ref reference), length);
#else
return MemoryMarshal.CreateSpan(ref reference, length);
#endif
}
}
12 changes: 12 additions & 0 deletions src/Irihi.Text.Toon/Internal/NotNullAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#if NETSTANDARD2_0

namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that an output is not null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)]
internal sealed class NotNullAttribute : Attribute
{
}
}

#endif
10 changes: 8 additions & 2 deletions src/Irihi.Text.Toon/Internal/ObjectToonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,16 @@ public override void Write(ref Utf8ToonWriter writer, T value, ToonSerializerOpt
if (ShouldSkip(pv, prop)) continue;

// Collection property: embed [N] header or use tabular
if (pv is IEnumerable enumerable
var enumerable = pv as IEnumerable;
if (enumerable is not null
&& prop.PropertyType != typeof(string)
&& pv is not IDictionary
&& !prop.PropertyType.IsAssignableTo(typeof(IDictionary)))
&& !
#if NETSTANDARD2_0
typeof(IDictionary).IsAssignableFrom(prop.PropertyType))
#else
prop.PropertyType.IsAssignableTo(typeof(IDictionary)))
#endif
{
var items = new List<object?>();
foreach (object? item in enumerable) items.Add(item);
Expand Down
60 changes: 60 additions & 0 deletions src/Irihi.Text.Toon/Internal/PolyfillArrayBufferWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#if NETSTANDARD2_0

using System.Buffers;

namespace Irihi.Text.Toon.Internal;

/// <summary>Polyfill for <see cref="System.Buffers.ArrayBufferWriter{T}"/> on netstandard2.0.</summary>

Check failure on line 7 in src/Irihi.Text.Toon/Internal/PolyfillArrayBufferWriter.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

XML comment has cref attribute 'ArrayBufferWriter{T}' that could not be resolved

Check failure on line 7 in src/Irihi.Text.Toon/Internal/PolyfillArrayBufferWriter.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

XML comment has cref attribute 'ArrayBufferWriter{T}' that could not be resolved
internal sealed class PolyfillArrayBufferWriter<T> : IBufferWriter<T>
{
private T[] _buffer;
private int _index;

public PolyfillArrayBufferWriter(int initialCapacity = 256)
{
_buffer = ArrayPool<T>.Shared.Rent(initialCapacity);
}

public ReadOnlyMemory<T> WrittenMemory => new ReadOnlyMemory<T>(_buffer, 0, _index);
public ReadOnlySpan<T> WrittenSpan => new ReadOnlySpan<T>(_buffer, 0, _index);
public int WrittenCount => _index;
public int Capacity => _buffer.Length;
public int FreeCapacity => _buffer.Length - _index;

public void Clear() => _index = 0;

public void Advance(int count)
{
if (count < 0 || _index + count > _buffer.Length)
throw new InvalidOperationException("Cannot advance past capacity.");
_index += count;
}

public Memory<T> GetMemory(int sizeHint = 0)
{
Ensure(sizeHint);
return new Memory<T>(_buffer, _index, _buffer.Length - _index);
}

public Span<T> GetSpan(int sizeHint = 0)
{
Ensure(sizeHint);
return new Span<T>(_buffer, _index, _buffer.Length - _index);
}

private void Ensure(int sizeHint)
{
if (sizeHint < 0) sizeHint = 0;
if (sizeHint == 0 && FreeCapacity > 0) return;

int growBy = Math.Max(sizeHint, _buffer.Length);
int newSize = _buffer.Length + growBy;

var newBuf = ArrayPool<T>.Shared.Rent(newSize);
Array.Copy(_buffer, 0, newBuf, 0, _index);
ArrayPool<T>.Shared.Return(_buffer);
_buffer = newBuf;
}
}

#endif
Loading
Loading