< Summary

Information
Class: MyNet.Text.Templating.TemplateTransform
Assembly: MyNet.Text
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Text/Templating/TemplateTransform.cs
Tag: 323_28699572109
Line coverage
97%
Covered lines: 42
Uncovered lines: 1
Coverable lines: 43
Total lines: 132
Line coverage: 97.6%
Branch coverage
93%
Covered branches: 15
Total branches: 16
Branch coverage: 93.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Apply(...)100%22100%
TryResolveArgument(...)100%44100%
FormatValue(...)100%22100%
ApplyQuantityRendering(...)87.5%8890.9%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Text/Templating/TemplateTransform.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="TemplateTransform.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Concurrent;
 9using System.Globalization;
 10using System.Text.RegularExpressions;
 11
 12namespace MyNet.Text.Templating;
 13
 14/// <summary>
 15/// Current implementation of <see cref="ITemplateTransform"/> that supports named placeholders with optional format spe
 16/// <example>
 17/// {count}
 18/// {count:N2}
 19/// {price:C}
 20/// {date:yyyy-MM-dd}
 21/// </example>
 22/// </summary>
 23public sealed partial class TemplateTransform(TextTemplateOptions options) : ITemplateTransform
 24{
 925    private static readonly ConcurrentDictionary<string, Func<object?, CultureInfo, string?, string>> CachedFormatters =
 26
 27    /// <inheritdoc />
 28    public string Apply(string input, CultureInfo culture)
 29    {
 489030        if (string.IsNullOrWhiteSpace(input))
 331            return string.Empty;
 32
 488733        ArgumentNullException.ThrowIfNull(options);
 488434        ArgumentNullException.ThrowIfNull(culture);
 35
 488136        var rendered = PlaceholderRegex().Replace(input, match =>
 488137        {
 488138            var argumentName = match.Groups["name"].Value;
 488139            var format = match.Groups["format"].Success ? match.Groups["format"].Value : null;
 488140
 488141            return !TryResolveArgument(argumentName, options, out var value) ? match.Value : FormatValue(value, culture,
 488142        });
 43
 488144        return ApplyQuantityRendering(rendered, options, culture);
 45    }
 46
 47    /// <summary>
 48    /// Tries to resolve an argument value by name from the provided translation options.
 49    /// </summary>
 50    /// <param name="argumentName">The name of the argument to resolve.</param>
 51    /// <param name="options">The translation options containing the arguments.</param>
 52    /// <param name="value">The resolved value, if found.</param>
 53    /// <returns>True if the argument was successfully resolved; otherwise, false.</returns>
 54    private static bool TryResolveArgument(string argumentName, TextTemplateOptions options, out object? value)
 55    {
 56        // Built-in count support
 41157        if (argumentName.Equals(nameof(TextTemplateOptions.Quantity), StringComparison.OrdinalIgnoreCase))
 58        {
 32159            value = options.Quantity;
 32160            return options.Quantity.HasValue;
 61        }
 62
 63        // Custom arguments
 9064        if (options.Arguments.TryGetValue(argumentName, out value))
 65        {
 7566            return true;
 67        }
 68
 1569        value = null;
 1570        return false;
 71    }
 72
 73    /// <summary>
 74    /// Formats a value using the provided culture and optional format string.
 75    /// </summary>
 76    private static string FormatValue(object? value, CultureInfo culture, string? format)
 77    {
 129378        if (value is null)
 79        {
 380            return string.Empty;
 81        }
 82
 129083        var formatter = CachedFormatters.GetOrAdd(
 129084            value.GetType().FullName + "|" + format,
 129085            static _ => static (v, c, f) => v is null
 129086                ? string.Empty
 129087                : v switch
 129088                {
 129089                    IFormattable formattable => formattable.ToString(f, c),
 129090                    _ => v.ToString() ?? string.Empty
 129091                });
 92
 129093        return formatter(value, culture, format);
 94    }
 95
 96    /// <summary>
 97    /// Applies quantity rendering to the rendered translation string based on the provided options and culture. This me
 98    /// </summary>
 99    /// <param name="rendered">The rendered translation string.</param>
 100    /// <param name="options">The translation options containing the quantity and rendering settings.</param>
 101    /// <param name="culture">The culture to use for formatting the quantity.</param>
 102    /// <returns>The translation string with quantity rendering applied, if applicable.</returns>
 103    private static string ApplyQuantityRendering(string rendered, TextTemplateOptions options, CultureInfo culture)
 104    {
 4881105        if (!options.Quantity.HasValue)
 3984106            return rendered;
 107
 108        // Already handled by placeholder
 897109        if (rendered.Contains($"{{{{{nameof(TextTemplateOptions.Quantity)}", StringComparison.OrdinalIgnoreCase))
 0110            return rendered;
 111
 897112        var quantity = FormatValue(options.Quantity.Value, culture, options.QuantityFormat);
 113
 897114        return options.QuantityRenderingMode switch
 897115        {
 39116            QuantityRenderingMode.Prefix => $"{quantity}{options.QuantitySeparator}{rendered}",
 3117            QuantityRenderingMode.Suffix => $"{rendered}{options.QuantitySeparator}{quantity}",
 855118            _ => rendered
 897119        };
 120    }
 121
 122    /// <summary>
 123    /// Matches named placeholders with optional format specifier.
 124    /// Examples:
 125    /// {count}
 126    /// {count:N2}
 127    /// {price:C}
 128    /// </summary>
 129    [GeneratedRegex(@"\{(?<name>[a-zA-Z0-9_]+)(:(?<format>[^}]+))?\}", RegexOptions.Compiled | RegexOptions.CultureInvar
 130    private static partial Regex PlaceholderRegex();
 131}
 132