< Summary

Information
Class: TeleFlow.Telegram.I18n.Fluent.Internal.TrustedFormattedFluentValue
Assembly: TeleFlow.Framework.I18n.Fluent
File(s): /_/src/TeleFlow.Framework.I18n.Fluent/Internal/FluentValues.cs
Line coverage
37%
Covered lines: 3
Uncovered lines: 5
Coverable lines: 8
Total lines: 183
Line coverage: 37.5%
Branch coverage
0%
Covered branches: 0
Total branches: 2
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
AsString()100%11100%
IsError()100%210%
Matches(...)0%620%
Copy()100%11100%

File(s)

/_/src/TeleFlow.Framework.I18n.Fluent/Internal/FluentValues.cs

#LineLine coverage
 1using System.Globalization;
 2using Linguini.Shared.Types.Bundle;
 3using TeleFlow.Telegram.Formatting;
 4
 5namespace TeleFlow.Telegram.I18n.Fluent.Internal;
 6
 7/// <summary>
 8/// Converts application arguments into Linguini values while applying the selected Telegram escaping boundary exactly o
 9/// </summary>
 10internal static class FluentValueConverter
 11{
 12    public static IFluentType Convert(
 13        object? value,
 14        CultureInfo culture,
 15        FluentRenderingMode mode)
 16    {
 17        return value switch
 18        {
 19            null => (FluentString)string.Empty,
 20            TelegramFormattedText formattedText => ConvertFormattedText(formattedText, mode),
 21            DateTime dateTime => new FluentDateTimeValue(dateTime, culture, mode),
 22            DateTimeOffset dateTimeOffset => new FluentDateTimeValue(dateTimeOffset, culture, mode),
 23            DateOnly date => new FluentDateTimeValue(date, culture, mode),
 24            TimeOnly time => new FluentDateTimeValue(time, culture, mode),
 25            byte number => (FluentNumber)(double)number,
 26            sbyte number => (FluentNumber)(double)number,
 27            short number => (FluentNumber)(double)number,
 28            ushort number => (FluentNumber)(double)number,
 29            int number => (FluentNumber)(double)number,
 30            uint number => (FluentNumber)(double)number,
 31            long number => (FluentNumber)number,
 32            ulong number => (FluentNumber)number,
 33            float number => (FluentNumber)number,
 34            double number => (FluentNumber)number,
 35            decimal number => (FluentNumber)(double)number,
 36            string text => (FluentString)Escape(text, mode),
 37            char character => (FluentString)Escape(character.ToString(), mode),
 38            IFormattable formattable => (FluentString)Escape(formattable.ToString(null, culture) ?? string.Empty, mode),
 39            _ => (FluentString)Escape(value.ToString() ?? string.Empty, mode)
 40        };
 41    }
 42
 43    public static string Escape(string value, FluentRenderingMode mode)
 44    {
 45        return mode switch
 46        {
 47            FluentRenderingMode.Plain => value,
 48            FluentRenderingMode.Html => TelegramHtml.Escape(value),
 49            FluentRenderingMode.MarkdownV2 => TelegramMarkdownV2.Escape(value),
 50            _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unknown Fluent rendering mode.")
 51        };
 52    }
 53
 54    private static IFluentType ConvertFormattedText(
 55        TelegramFormattedText formattedText,
 56        FluentRenderingMode mode)
 57    {
 58        var expectedParseMode = mode switch
 59        {
 60            FluentRenderingMode.Html => TelegramParseMode.Html,
 61            FluentRenderingMode.MarkdownV2 => TelegramParseMode.MarkdownV2,
 62            FluentRenderingMode.Plain => throw new InvalidOperationException(
 63                "TelegramFormattedText cannot be inserted into a plain Fluent message."),
 64            _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unknown Fluent rendering mode.")
 65        };
 66
 67        if (formattedText.ParseMode != expectedParseMode)
 68        {
 69            throw new InvalidOperationException(
 70                $"TelegramFormattedText with parse mode '{formattedText.ParseMode}' cannot be inserted into '{expectedPa
 71        }
 72
 73        return new TrustedFormattedFluentValue(formattedText.Text);
 74    }
 75}
 76
 77/// <summary>
 78/// Carries a reviewed formatted Telegram fragment through Fluent placeable resolution without escaping it again.
 79/// </summary>
 80internal sealed class TrustedFormattedFluentValue(string text) : IFluentType
 81{
 182    private readonly string _text = text;
 83
 184    public string AsString() => _text;
 85
 086    public bool IsError() => false;
 87
 88    public bool Matches(IFluentType other, IScope scope)
 89    {
 090        ArgumentNullException.ThrowIfNull(other);
 091        ArgumentNullException.ThrowIfNull(scope);
 092        return other is TrustedFormattedFluentValue formatted &&
 093               string.Equals(_text, formatted._text, StringComparison.Ordinal);
 94    }
 95
 196    public IFluentType Copy() => this;
 97}
 98
 99/// <summary>
 100/// Carries a .NET temporal value through Fluent so DATETIME can apply options and direct placeables remain locale-aware
 101/// </summary>
 102internal sealed class FluentDateTimeValue(
 103    IFormattable value,
 104    CultureInfo culture,
 105    FluentRenderingMode mode) : IFluentType
 106{
 107    private readonly IFormattable _value = value;
 108
 109    public string AsString()
 110    {
 111        return FluentValueConverter.Escape(Format(culture, null, null), mode);
 112    }
 113
 114    public bool IsError() => false;
 115
 116    public bool Matches(IFluentType other, IScope scope)
 117    {
 118        ArgumentNullException.ThrowIfNull(other);
 119        ArgumentNullException.ThrowIfNull(scope);
 120        return other is FluentDateTimeValue dateTime && Equals(_value, dateTime._value);
 121    }
 122
 123    public IFluentType Copy() => this;
 124
 125    public string Format(CultureInfo targetCulture, string? dateStyle, string? timeStyle)
 126    {
 127        var normalizedDateStyle = NormalizeStyle(dateStyle, "dateStyle");
 128        var normalizedTimeStyle = NormalizeStyle(timeStyle, "timeStyle");
 129
 130        if (_value is DateOnly date)
 131        {
 132            if (normalizedTimeStyle is not null)
 133            {
 134                throw new InvalidOperationException("DATETIME cannot apply timeStyle to a DateOnly value.");
 135            }
 136
 137            return date.ToString(normalizedDateStyle == "long" ? "D" : "d", targetCulture);
 138        }
 139
 140        if (_value is TimeOnly time)
 141        {
 142            if (normalizedDateStyle is not null)
 143            {
 144                throw new InvalidOperationException("DATETIME cannot apply dateStyle to a TimeOnly value.");
 145            }
 146
 147            return time.ToString(normalizedTimeStyle == "long" ? "T" : "t", targetCulture);
 148        }
 149
 150        var format = (normalizedDateStyle, normalizedTimeStyle) switch
 151        {
 152            (null, null) => "G",
 153            ("short", null) => "d",
 154            ("long", null) => "D",
 155            (null, "short") => "t",
 156            (null, "long") => "T",
 157            ("short", "short") => "g",
 158            ("short", "long") => "G",
 159            ("long", "short") => "f",
 160            ("long", "long") => "F",
 161            _ => throw new InvalidOperationException("DATETIME could not select a date or time format.")
 162        };
 163
 164        return _value.ToString(format, targetCulture);
 165    }
 166
 167    private static string? NormalizeStyle(string? style, string optionName)
 168    {
 169        if (style is null)
 170        {
 171            return null;
 172        }
 173
 174        var normalized = style.ToLowerInvariant();
 175
 176        return normalized switch
 177        {
 178            "short" or "long" => normalized,
 179            _ => throw new InvalidOperationException(
 180                $"DATETIME option '{optionName}' must be short or long.")
 181        };
 182    }
 183}