< Summary

Information
Class: TeleFlow.Telegram.I18n.Fluent.Internal.FluentFunctions
Assembly: TeleFlow.Framework.I18n.Fluent
File(s): /_/src/TeleFlow.Framework.I18n.Fluent/Internal/FluentFunctions.cs
Line coverage
85%
Covered lines: 66
Uncovered lines: 11
Coverable lines: 77
Total lines: 146
Line coverage: 85.7%
Branch coverage
67%
Covered branches: 31
Total branches: 46
Branch coverage: 67.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
CreateNumber(...)62.5%8888.88%
CreateDateTime(...)50%4484.61%
FormatNumber(...)83.33%121293.33%
GetIntegerOption(...)60%111077.77%
GetBooleanOption(...)60%131070%
GetStringOption(...)100%22100%

File(s)

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

#LineLine coverage
 1using System.Globalization;
 2using Linguini.Bundle.Types;
 3using Linguini.Shared.Types.Bundle;
 4
 5namespace TeleFlow.Telegram.I18n.Fluent.Internal;
 6
 7/// <summary>
 8/// Implements TeleFlow's documented NUMBER and DATETIME Fluent functions with locale-aware, mode-safe output.
 9/// </summary>
 10internal static class FluentFunctions
 11{
 12    public static ExternalFunction CreateNumber(CultureInfo culture, FluentRenderingMode mode)
 13    {
 7814        return (arguments, namedArguments) =>
 7815        {
 416            if (arguments.Count != 1 || arguments[0] is not FluentNumber number)
 7817            {
 018                throw new InvalidOperationException("NUMBER requires exactly one numeric positional argument.");
 7819            }
 7820
 421            var minimumFractionDigits = GetIntegerOption(namedArguments, "minimumFractionDigits", 0, 100) ?? 0;
 422            var maximumFractionDigits = GetIntegerOption(namedArguments, "maximumFractionDigits", 0, 100) ??
 423                                        Math.Max(minimumFractionDigits, 3);
 7824
 425            if (maximumFractionDigits < minimumFractionDigits)
 7826            {
 027                throw new InvalidOperationException(
 028                    "NUMBER maximumFractionDigits must be greater than or equal to minimumFractionDigits.");
 7829            }
 7830
 431            var useGrouping = GetBooleanOption(namedArguments, "useGrouping") ?? true;
 432            var formatted = FormatNumber(
 433                number.Value,
 434                culture,
 435                useGrouping,
 436                minimumFractionDigits,
 437                maximumFractionDigits);
 7838
 439            return (FluentString)FluentValueConverter.Escape(formatted, mode);
 7840        };
 41    }
 42
 43    public static ExternalFunction CreateDateTime(CultureInfo culture, FluentRenderingMode mode)
 44    {
 7845        return (arguments, namedArguments) =>
 7846        {
 947            if (arguments.Count != 1 || arguments[0] is not FluentDateTimeValue dateTime)
 7848            {
 049                throw new InvalidOperationException(
 050                    "DATETIME requires exactly one DateTime, DateTimeOffset, DateOnly, or TimeOnly argument.");
 7851            }
 7852
 953            var dateStyle = GetStringOption(namedArguments, "dateStyle");
 954            var timeStyle = GetStringOption(namedArguments, "timeStyle");
 955            var formatted = dateTime.Format(culture, dateStyle, timeStyle);
 756            return (FluentString)FluentValueConverter.Escape(formatted, mode);
 7857        };
 58    }
 59
 60    private static string FormatNumber(
 61        double value,
 62        CultureInfo culture,
 63        bool useGrouping,
 64        int minimumFractionDigits,
 65        int maximumFractionDigits)
 66    {
 467        var format = (useGrouping ? "N" : "F") + maximumFractionDigits.ToString(CultureInfo.InvariantCulture);
 468        var formatted = value.ToString(format, culture);
 69
 470        if (maximumFractionDigits == minimumFractionDigits)
 71        {
 272            return formatted;
 73        }
 74
 275        var separator = culture.NumberFormat.NumberDecimalSeparator;
 276        var separatorIndex = formatted.LastIndexOf(separator, StringComparison.Ordinal);
 77
 278        if (separatorIndex < 0)
 79        {
 080            return formatted;
 81        }
 82
 283        var trimEnd = formatted.Length;
 284        var minimumEnd = separatorIndex + separator.Length + minimumFractionDigits;
 85
 886        while (trimEnd > minimumEnd && formatted[trimEnd - 1] == '0')
 87        {
 688            trimEnd--;
 89        }
 90
 291        if (trimEnd == separatorIndex + separator.Length)
 92        {
 293            trimEnd = separatorIndex;
 94        }
 95
 296        return formatted[..trimEnd];
 97    }
 98
 99    private static int? GetIntegerOption(
 100        IDictionary<string, IFluentType> arguments,
 101        string name,
 102        int minimum,
 103        int maximum)
 104    {
 8105        if (!arguments.TryGetValue(name, out var value))
 106        {
 5107            return null;
 108        }
 109
 3110        if (value is not FluentNumber number ||
 3111            !double.IsInteger(number.Value) ||
 3112            number.Value < minimum ||
 3113            number.Value > maximum)
 114        {
 0115            throw new InvalidOperationException(
 0116                $"Fluent option '{name}' must be an integer from {minimum} through {maximum}.");
 117        }
 118
 3119        return (int)number.Value;
 120    }
 121
 122    private static bool? GetBooleanOption(IDictionary<string, IFluentType> arguments, string name)
 123    {
 4124        var value = GetStringOption(arguments, name);
 125
 4126        if (value is null)
 127        {
 3128            return null;
 129        }
 130
 1131        return value.ToUpperInvariant() switch
 1132        {
 0133            "TRUE" or "1" => true,
 1134            "FALSE" or "0" => false,
 0135            _ => throw new InvalidOperationException(
 0136                $"Fluent option '{name}' must be true, false, 1, or 0.")
 1137        };
 138    }
 139
 140    private static string? GetStringOption(IDictionary<string, IFluentType> arguments, string name)
 141    {
 22142        return arguments.TryGetValue(name, out var value)
 22143            ? value.AsString()
 22144            : null;
 145    }
 146}