< Summary

Information
Class: Amusoft.Toolkit.Mvvm.Core.PatternMappingEngine
Assembly: Amusoft.Toolkit.Mvvm.Core
File(s): /home/runner/work/Amusoft.Toolkit.Mvvm/Amusoft.Toolkit.Mvvm/src/Amusoft.Toolkit.Mvvm.Core/Navigation/PatternMappingEngine.cs
Tag: 8_14199367891
Line coverage
0%
Covered lines: 0
Uncovered lines: 17
Coverable lines: 17
Total lines: 266
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 10
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%210%
GetMappings()0%110100%

File(s)

/home/runner/work/Amusoft.Toolkit.Mvvm/Amusoft.Toolkit.Mvvm/src/Amusoft.Toolkit.Mvvm.Core/Navigation/PatternMappingEngine.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Reflection;
 5using System.Text.RegularExpressions;
 6using Microsoft.Extensions.Logging;
 7using Microsoft.Extensions.Options;
 8
 9namespace Amusoft.Toolkit.Mvvm.Core;
 10
 11internal class PatternMappingEngine : IDataTemplateSource
 12{
 13  private readonly IMvvmMappingPattern[] _patterns;
 14  private readonly ILogger<PatternMappingEngine> _logger;
 15  private readonly MvvmOptions _options;
 16
 017  public PatternMappingEngine(IEnumerable<IMvvmMappingPattern> patterns, ILogger<PatternMappingEngine> logger, IOptions<
 18  {
 019    _patterns = patterns.ToArray();
 020    _logger = logger;
 021    _options = options.Value;
 022  }
 23
 24  public HashSet<(Type view, Type viewModel)> GetMappings()
 25  {
 026    var results = new HashSet<(Type view, Type viewModel)>();
 027    foreach (var pattern in _patterns)
 28    {
 029      _logger.LogDebug("Collecting patterns through {Name}.", pattern.GetType().Name);
 030      var patternResult = pattern.GetResult(_options.MappingInput);
 031      foreach (var patternResults in patternResult.Matches)
 32      {
 033        if(!results.Add((patternResults.viewType, patternResults.viewModelType)))
 034          _logger.LogInformation("Skipped adding mapping for {View} to {ViewModel} because that mapping already exists",
 35      }
 36
 037      foreach (var type in patternResult.MismatchedViews)
 38      {
 039        _logger.LogWarning("Found no match for view type {Name}", type.Name);
 40      }
 41
 042      foreach (var type in patternResult.MismatchedViewModels)
 43      {
 044        _logger.LogWarning("Found no match for viewModel type {Name}", type.Name);
 45      }
 46    }
 47
 048    return results;
 49  }
 50}
 51
 52/// <summary>
 53/// Provides an array of types that are mapped together as datatemplates
 54/// </summary>
 55internal interface IDataTemplateSource
 56{
 57  /// <summary>
 58  /// Gets an array for templates that have to be mapped
 59  /// </summary>
 60  /// <returns></returns>
 61  HashSet<(Type view, Type viewModel)> GetMappings();
 62}
 63
 64/// <summary>
 65/// Inputs used for the mapping process
 66/// </summary>
 67public class MvvmMappingInput
 68{
 69  internal MvvmMappingInput()
 70  {
 71    ViewTypes = new (() => GetViewTypes().ToArray());
 72    ViewModelTypes = new (() => GetViewModelTypes().ToArray());
 73  }
 74
 75  private readonly List<Assembly> _assemblies = [];
 76  private readonly List<Predicate<Type>> _viewFilters = [];
 77  private readonly List<Predicate<Type>> _viewModelFilters = [];
 78  internal Lazy<Type[]> ViewTypes;
 79  internal Lazy<Type[]> ViewModelTypes;
 80
 81  /// <summary>
 82  /// Gets all view types
 83  /// </summary>
 84  /// <returns></returns>
 85  private IEnumerable<Type> GetViewTypes()
 86  {
 87    var types = _assemblies.SelectMany(a => a.GetTypes());
 88    foreach (var filter in _viewFilters)
 89    {
 90      types = types.Where(t => filter(t));
 91    }
 92    return types;
 93  }
 94
 95  /// <summary>
 96  /// Gets all view types
 97  /// </summary>
 98  /// <returns></returns>
 99  private IEnumerable<Type> GetViewModelTypes()
 100  {
 101    var types = _assemblies.SelectMany(a => a.GetTypes());
 102    foreach (var filter in _viewModelFilters)
 103    {
 104      types = types.Where(t => filter(t));
 105    }
 106    return types;
 107  }
 108
 109  /// <summary>
 110  /// Picks assemblies for the mapping process
 111  /// </summary>
 112  /// <param name="assembly"></param>
 113  /// <returns></returns>
 114  public MvvmMappingInput WithAssembly(Assembly assembly)
 115  {
 116    _assemblies.Add(assembly);
 117    return this;
 118  }
 119
 120  /// <summary>
 121  /// Uses the given filter to narrow down view types
 122  /// </summary>
 123  /// <param name="filter"></param>
 124  /// <returns></returns>
 125  public MvvmMappingInput WithViewFilter(Predicate<Type> filter)
 126  {
 127    _viewFilters.Add(filter);
 128    return this;
 129  }
 130
 131  /// <summary>
 132  /// Uses the given filter to narrow down view types
 133  /// </summary>
 134  /// <param name="filter"></param>
 135  /// <returns></returns>
 136  public MvvmMappingInput WithViewModelFilter(Predicate<Type> filter)
 137  {
 138    _viewModelFilters.Add(filter);
 139    return this;
 140  }
 141}
 142
 143/// <summary>
 144/// Matches values by names
 145/// </summary>
 146public class NamespaceConventionPattern : IMvvmMappingPattern
 147{
 148  /// <summary>
 149  /// Wildcard to match the start of namespace mapping e.g. "ViewModels"
 150  /// </summary>
 151  public Regex ViewModelPattern { get; set; } = new Regex(@".+\.ViewModels\.(?<match>.+)", RegexOptions.Compiled | Regex
 152
 153  /// <summary>
 154  /// Removes the end parts from a full name
 155  /// </summary>
 156  public Regex ViewModelTruncateEndPattern { get; set; } = new Regex(@"(?:ViewModel|Model|VM)$", RegexOptions.Compiled |
 157
 158  /// <summary>
 159  /// Wildcard to match the start of namespace mapping e.g. "Views"
 160  /// </summary>
 161  public Regex ViewPattern { get; set; } = new Regex(@".+\.Views\.(?<match>.+)", RegexOptions.Compiled | RegexOptions.Si
 162
 163  /// <summary>
 164  /// Removes the end parts from a full name
 165  /// </summary>
 166  public Regex ViewTruncateEndPattern { get; set; } = new Regex(@"(?:Page|View)$", RegexOptions.Compiled | RegexOptions.
 167
 168  /// <summary>
 169  ///
 170  /// </summary>
 171  /// <param name="input"></param>
 172  /// <returns></returns>
 173  /// <exception cref="NotImplementedException"></exception>
 174  public MvvmMappingResult GetResult(MvvmMappingInput input)
 175  {
 176    var viewTypes = input.ViewTypes.Value;
 177    var modelTypes = input.ViewModelTypes.Value;
 178
 179    string? GetMatchingName(string fullName, Regex matchPattern, Regex truncateEndPattern)
 180    {
 181      if (!matchPattern.IsMatch(fullName))
 182        return null;
 183
 184      var match = matchPattern.Match(fullName);
 185      var matchText = match.Groups["match"].Success ? match.Groups["match"].Value : null;
 186      if (matchText == null)
 187        return null;
 188
 189      return truncateEndPattern.Replace(matchText, string.Empty);
 190    }
 191
 192    var viewMap = viewTypes
 193      .Select(d => (type: d, name: GetMatchingName(d.FullName!, ViewPattern, ViewTruncateEndPattern)))
 194      .Where(d => d.name != null)
 195      .ToDictionary(d => d.name!, d => d.type);
 196    var viewModelMap = modelTypes
 197      .Select(d => (type: d, name: GetMatchingName(d.FullName!, ViewModelPattern, ViewModelTruncateEndPattern)))
 198      .Where(d => d.name != null)
 199      .ToDictionary(d => d.name!, d => d.type);
 200
 201    var mapped = new HashSet<(Type view, Type viewModel)>();
 202    var mappedView = new HashSet<Type>();
 203    var mappedViewModel = new HashSet<Type>();
 204    foreach (var viewType in viewMap)
 205    {
 206      if (viewModelMap.TryGetValue(viewType.Key, out var viewModel))
 207      {
 208        mapped.Add((viewType.Value, viewModel));
 209        mappedView.Add(viewType.Value);
 210        mappedViewModel.Add(viewModel);
 211      }
 212    }
 213
 214    var missingViews = viewMap
 215      .Select(d => d.Value)
 216      .Where(d => !mappedView.Contains(d))
 217      .ToArray();
 218    var missingViewModels = viewModelMap
 219      .Select(d => d.Value)
 220      .Where(d => !mappedViewModel.Contains(d))
 221      .ToArray();
 222    var results = mapped
 223      .Select(d => (d.viewModel, d.view))
 224      .ToArray();
 225
 226    return new MvvmMappingResult(results, missingViews, missingViewModels);
 227  }
 228}
 229
 230internal interface IMvvmMappingPattern
 231{
 232  MvvmMappingResult GetResult(MvvmMappingInput input);
 233}
 234
 235/// <summary>
 236/// Results of the mapping process
 237/// </summary>
 238public class MvvmMappingResult
 239{
 240  /// <summary>
 241  /// </summary>
 242  /// <param name="matches"></param>
 243  /// <param name="mismatchedViews"></param>
 244  /// <param name="mismatchedViewModels"></param>
 245  public MvvmMappingResult((Type viewModelType, Type viewType)[] matches, Type[] mismatchedViews, Type[] mismatchedViewM
 246  {
 247    Matches = matches;
 248    MismatchedViews = mismatchedViews;
 249    MismatchedViewModels = mismatchedViewModels;
 250  }
 251
 252  /// <summary>
 253  /// Matches built from the input
 254  /// </summary>
 255  public (Type viewModelType, Type viewType)[] Matches { get; private set; }
 256
 257  /// <summary>
 258  /// Mismatched entries from the view types
 259  /// </summary>
 260  public Type[] MismatchedViews { get; private set; }
 261
 262  /// <summary>
 263  /// Mismatched entries from the viewmodel types
 264  /// </summary>
 265  public Type[] MismatchedViewModels { get; private set; }
 266}