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

Investigate ISpanFormattable #245

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions specs/Qowaiv.Specs/Chemistry/CAS_Registry_Number_specs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ public class With_domain_logic
[TestCase("?")]
public void has_length_zero_for_empty_and_unknown(CasRegistryNumber svo)
=> svo.Length.Should().Be(0);



[TestCase(5, "73–24–5")]
[TestCase(7, "7732-18-5")]
[TestCase(8, "10028-14-5")]
Expand Down
10 changes: 10 additions & 0 deletions specs/Qowaiv.Specs/Financial/Amount_specs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,13 @@ public void to_divide_amount_by_amount_as_decimal()
}
}
}

public class Supports_try_format
{
[Test]
public void On_interpolated_strings()
{
FormattableString.Invariant($"{Svo.Amount}").Should().Be("42.17");
}

}
12 changes: 12 additions & 0 deletions specs/Qowaiv.Specs/IFormatable_specs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace IFormatable_specs;

public class All_SVOs : SvoTypeTest
{
[TestCaseSource(nameof(AllSvos))]
public void implement_IFormattable(Type type) => type.Should().Implement(typeof(IFormattable));

#if NET6_0_OR_GREATER
[TestCaseSource(nameof(AllSvos))]
public void implement_ISpanFormattable(Type type) => type.Should().Implement(typeof(ISpanFormattable));
#endif
}
74 changes: 74 additions & 0 deletions src/Qowaiv/Chemistry/CasRegistryNumber.ToString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#pragma warning disable S1210
// "Equals" and the comparison operators should be overridden when implementing "IComparable"
// See README.md => Sortable

namespace Qowaiv.Chemistry;

/// <summary>
/// A CAS Registry Number, is a unique numerical identifier assigned by the
/// Chemical Abstracts Service (CAS), US to every chemical substance described
/// in the open scientific literature. It includes all substances described
/// from 1957 through the present, plus some substances from as far back as the
/// early 1800's.
/// </summary>
#if NET6_0_OR_GREATER
public readonly partial struct CasRegistryNumber : ISpanFormattable
#else
public readonly partial struct CasRegistryNumber : IFormattable
#endif
{
/// <summary>Returns a formatted <see cref="string" /> that represents the CAS Registry Number.</summary>
/// <param name="format">
/// The format that this describes the formatting.
/// </param>
/// <param name="formatProvider">
/// The format provider.
/// </param>
/// <remarks>
/// The formats:
/// f: as formatted.
///
/// other (not empty) formats are applied on the number (long).
/// </remarks>
[Pure]
public string ToString(string? format, IFormatProvider? formatProvider)
{
if (StringFormatter.TryApplyCustomFormatter(format, this, formatProvider, out string formatted))
{
return formatted;
}
else if (IsEmpty()) return string.Empty;
else if (IsUnknown()) return "?";
else return format.WithDefault("f") == "f"
? m_Value.ToString(@"#00\-00\-0", formatProvider)
: m_Value.ToString(format, formatProvider);
}
#if NET6_0_OR_GREATER
/// <inheritdoc />
[Pure]
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
{
if (StringFormatter.TryApplyCustomFormatter(format, this, provider, out string formatted))
{
return destination.TryWrite(formatted, out charsWritten);
}
else if (IsEmpty()) return destination.TryWrite(string.Empty, out charsWritten);
else if (IsUnknown()) return destination.TryWrite('?', out charsWritten);
else return format.WithDefault("f") == "f"
? m_Value.TryFormat(destination, out charsWritten, @"#00\-00\-0", provider)
: m_Value.TryFormat(destination, out charsWritten, format, provider);
}
#endif
/// <summary>Gets an XML string representation of the CAS Registry Number.</summary>
[Pure]
private string ToXmlString() => ToString(CultureInfo.InvariantCulture);

/// <summary>Serializes the CAS Registry Number to a JSON node.</summary>
/// <returns>
/// The serialized JSON string.
/// </returns>
[Pure]
public string? ToJson() => m_Value == default ? null : ToString(CultureInfo.InvariantCulture);


}
40 changes: 1 addition & 39 deletions src/Qowaiv/Chemistry/CasRegistryNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Qowaiv.Chemistry;
#if NET5_0_OR_GREATER
[System.Text.Json.Serialization.JsonConverter(typeof(Json.Chemistry.CasRegistryNumberJsonConverter))]
#endif
public readonly partial struct CasRegistryNumber : ISerializable, IXmlSerializable, IFormattable, IEquatable<CasRegistryNumber>, IComparable, IComparable<CasRegistryNumber>
public readonly partial struct CasRegistryNumber : ISerializable, IXmlSerializable, IEquatable<CasRegistryNumber>, IComparable, IComparable<CasRegistryNumber>
{
/// <summary>Represents an empty/not set CAS Registry Number.</summary>
public static readonly CasRegistryNumber Empty;
Expand All @@ -35,44 +35,6 @@ namespace Qowaiv.Chemistry;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => this.DebuggerDisplay("{0:f}");

/// <summary>Returns a formatted <see cref="string" /> that represents the CAS Registry Number.</summary>
/// <param name="format">
/// The format that this describes the formatting.
/// </param>
/// <param name="formatProvider">
/// The format provider.
/// </param>
/// <remarks>
/// The formats:
/// f: as formatted.
///
/// other (not empty) formats are applied on the number (long).
/// </remarks>
[Pure]
public string ToString(string? format, IFormatProvider? formatProvider)
{
if (StringFormatter.TryApplyCustomFormatter(format, this, formatProvider, out string formatted))
{
return formatted;
}
else if (IsEmpty()) return string.Empty;
else if (IsUnknown()) return "?";
else return format.WithDefault("f") == "f"
? m_Value.ToString(@"#00\-00\-0", formatProvider)
: m_Value.ToString(format, formatProvider);
}

/// <summary>Gets an XML string representation of the CAS Registry Number.</summary>
[Pure]
private string ToXmlString() => ToString(CultureInfo.InvariantCulture);

/// <summary>Serializes the CAS Registry Number to a JSON node.</summary>
/// <returns>
/// The serialized JSON string.
/// </returns>
[Pure]
public string? ToJson() => m_Value == default ? null : ToString(CultureInfo.InvariantCulture);

/// <summary>Deserializes the CAS Registry Number from a JSON number.</summary>
/// <param name="json">
/// The JSON number to deserialize.
Expand Down
20 changes: 18 additions & 2 deletions src/Qowaiv/Date.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
#if NET5_0_OR_GREATER
[System.Text.Json.Serialization.JsonConverter(typeof(Json.DateJsonConverter))]
#endif
public readonly partial struct Date : ISerializable, IXmlSerializable, IFormattable, IEquatable<Date>, IComparable, IComparable<Date>
public readonly partial struct Date : ISerializable, IXmlSerializable, IEquatable<Date>, IComparable, IComparable<Date>
#if NET6_0_OR_GREATER
, ISpanFormattable
#else
, IFormattable
#endif
#if NET7_0_OR_GREATER
, IIncrementOperators<Date>, IDecrementOperators<Date>
, IAdditionOperators<Date, TimeSpan, Date>, ISubtractionOperators<Date, TimeSpan, Date>
Expand Down Expand Up @@ -412,7 +417,18 @@ public string ToString(string? format, IFormatProvider? formatProvider)
: m_Value.ToString(format, formatProvider);
}

/// <summary>Gets an XML string representation of the @FullName.</summary>
#if NET6_0_OR_GREATER
/// <inheritdoc />
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
{
format = format.WithDefault("d");
return StringFormatter.TryApplyCustomFormatter(format, this, provider, out var formatted)
? destination.TryWrite(formatted, out charsWritten)
: m_Value.TryFormat(destination, out charsWritten, format, provider);
}
#endif

/// <summary>Gets an XML string representation of the date.</summary>
[Pure]
private string ToXmlString() => ToString(SerializableFormat, CultureInfo.InvariantCulture);

Expand Down
2 changes: 1 addition & 1 deletion src/Qowaiv/EmailAddressCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ public string ToString(string? format, IFormatProvider? formatProvider)
/// s is not in the correct format.
/// </exception>
[Pure]
public static EmailAddressCollection Parse(string? s, IFormatProvider formatProvider)
public static EmailAddressCollection Parse(string? s, IFormatProvider? formatProvider)
{
if (TryParse(s, formatProvider, out EmailAddressCollection val))
{
Expand Down
48 changes: 48 additions & 0 deletions src/Qowaiv/Extensions/System.Span.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#if NET6_0_OR_GREATER
namespace System;

/// <summary>Extensions on <see cref="Span{T}"/>.</summary>
internal static class QowaivSpanExtensions
{
/// <summary>Tries to write a nullable <see cref="string"/> to a <see cref="Span{T}"/>&lt;<see cref="char"/>&gt;.</summary>
[Pure]
public static bool TryWrite(this Span<char> span, string? value, out int charsWritten)
{
if (value is { Length: > 0 })
{
if (value.TryCopyTo(span))
{
charsWritten = value.Length;
return true;
}
else
{
charsWritten = 0;
return false;
}
}
else
{
charsWritten = 0;
return true;
}
}

/// <summary>Tries to write a <see cref="char"/> to a <see cref="Span{T}"/>&lt;<see cref="char"/>&gt;.</summary>
[Pure]
public static bool TryWrite(this Span<char> span, char ch, out int charsWritten)
{
if (span.Length != 0)
{
span.Fill(ch);
charsWritten = 1;
return true;
}
else
{
charsWritten = 0;
return false;
}
}
}
#endif
9 changes: 9 additions & 0 deletions src/Qowaiv/Extensions/System.String.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ public static string ToTitleCase(this string str, IFormatProvider provider)
internal static string WithDefault(this string? str, string @default = "")
=> str is { Length: > 0 } ? str : @default;

#if NET6_0_OR_GREATER
/// <summary>Returns the provided default if <see cref="string.IsNullOrEmpty(string)"/>,
/// otherwise the string value.
/// </summary>
[Pure]
internal static ReadOnlySpan<char> WithDefault(this ReadOnlySpan<char> str, ReadOnlySpan<char> @default)
=> str.IsEmpty ? @default : str;
#endif

[Pure]
internal static string Unify(this string? str) => str.Buffer().Unify();

Expand Down
13 changes: 12 additions & 1 deletion src/Qowaiv/Financial/Amount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ namespace Qowaiv.Financial;
[System.Text.Json.Serialization.JsonConverter(typeof(Json.Financial.AmountJsonConverter))]
#endif
public readonly partial struct Amount : ISerializable, IXmlSerializable, IFormattable, IEquatable<Amount>, IComparable, IComparable<Amount>
#if NET6_0_OR_GREATER
, ISpanFormattable
#endif
#if NET7_0_OR_GREATER
, IIncrementOperators<Amount>, IDecrementOperators<Amount>
, IUnaryPlusOperators<Amount, Amount>, IUnaryNegationOperators<Amount, Amount>
Expand Down Expand Up @@ -401,11 +404,19 @@ namespace Qowaiv.Financial;
/// The format provider.
/// </param>
[Pure]
public string ToString(string? format, IFormatProvider? formatProvider)
public string ToString(string? format, IFormatProvider? formatProvider)
=> StringFormatter.TryApplyCustomFormatter(format, this, formatProvider, out string formatted)
? formatted
: m_Value.ToString(format, Money.GetNumberFormatInfo(formatProvider));

#if NET6_0_OR_GREATER
/// <inheritdoc />
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
=> StringFormatter.TryApplyCustomFormatter(format, this, provider, out var formatted)
? destination.TryWrite(formatted, out charsWritten)
: m_Value.TryFormat(destination, out charsWritten, format, Money.GetNumberFormatInfo(provider));
#endif

/// <summary>Gets an XML string representation of the amount.</summary>
[Pure]
private string ToXmlString() => ToString(CultureInfo.InvariantCulture);
Expand Down
26 changes: 25 additions & 1 deletion src/Qowaiv/Formatting/StringFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Qowaiv.Formatting;
using Microsoft.VisualBasic;
using System.ComponentModel;

namespace Qowaiv.Formatting;

/// <summary>A string formatter class.</summary>
public static class StringFormatter
Expand Down Expand Up @@ -128,6 +131,27 @@ public static bool TryApplyCustomFormatter(string? format, object obj, IFormatPr
}
}

#if NET6_0_OR_GREATER
/// <summary>Tries to apply the custom formatter to format the object.</summary>
/// <param name="format">
/// The format to apply
/// </param>
/// <param name="obj">
/// The object to format.
/// </param>
/// <param name="formatProvider">
/// The format provider.
/// </param>
/// <param name="formatted">
/// The formatted result.
/// </param>
/// <returns>
/// True, if the format provider supports custom formatting, otherwise false.
/// </returns>
public static bool TryApplyCustomFormatter(ReadOnlySpan<char> format, object obj, IFormatProvider? formatProvider, out string formatted)
=> TryApplyCustomFormatter(format.ToString(), obj, formatProvider, out formatted);
#endif

/// <summary>Replaces diacritic characters by non diacritic ones.</summary>
/// <param name="str">
/// The string to remove the diacritics from.
Expand Down
9 changes: 9 additions & 0 deletions src/Qowaiv/Generated/EmailAddress.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ public partial struct EmailAddress : IFormattable
public string ToString(IFormatProvider? provider) => ToString(format: null, provider);
}

#if NET6_0_OR_GREATER
public partial struct EmailAddress : ISpanFormattable
{
/// <inheritdoc />
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
=> destination.TryWrite(ToString(format.ToString(), provider), out charsWritten);
}
#endif

public partial struct EmailAddress : ISerializable
{
/// <summary>Initializes a new instance of the email address based on the serialization info.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ public partial struct BusinessIdentifierCode : IFormattable
public string ToString(IFormatProvider? provider) => ToString(format: null, provider);
}

#if NET6_0_OR_GREATER
public partial struct BusinessIdentifierCode : ISpanFormattable
{
/// <inheritdoc />
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
=> destination.TryWrite(ToString(format.ToString(), provider), out charsWritten);
}
#endif

public partial struct BusinessIdentifierCode : ISerializable
{
/// <summary>Initializes a new instance of the BIC based on the serialization info.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ public partial struct InternationalBankAccountNumber : IFormattable
public string ToString(IFormatProvider? provider) => ToString(format: null, provider);
}

#if NET6_0_OR_GREATER
public partial struct InternationalBankAccountNumber : ISpanFormattable
{
/// <inheritdoc />
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
=> destination.TryWrite(ToString(format.ToString(), provider), out charsWritten);
}
#endif

public partial struct InternationalBankAccountNumber : ISerializable
{
/// <summary>Initializes a new instance of the IBAN based on the serialization info.</summary>
Expand Down