using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace LibKubernetesGenerator { [Generator] public class VersionConverterGenerator : ISourceGenerator { private IEnumerable PathSplit(string path) { var p = path; while (!string.IsNullOrEmpty(p)) { yield return Path.GetFileName(p); p = Path.GetDirectoryName(p); } } private bool PathSuffixMath(string path, string suffix) { var s = PathSplit(suffix).ToList(); return PathSplit(path).Take(s.Count).SequenceEqual(s); } public void Execute(GeneratorExecutionContext context) { // TODO should parse syntax node instead of text var allGeneratedModelClassNames = new List(); var manualMaps = new List<(string, string)>(); foreach (var s in context.Compilation.SyntaxTrees) { var p = s.FilePath; if (PathSuffixMath(p, "Versioning/VersionConverter.cs")) { manualMaps = Regex.Matches(s.GetText().ToString(), @"\.CreateMap<(?.+?),\s?(?.+?)>") .OfType() .Select(x => (x.Groups["T1"].Value, x.Groups["T2"].Value)) .ToList(); } else if (PathSuffixMath(Path.GetDirectoryName(p), "generated/Models")) { allGeneratedModelClassNames.Add(Path.GetFileNameWithoutExtension(p)); } } var versionRegex = @"(^V|v)[0-9]+((alpha|beta)[0-9]+)?"; var typePairs = allGeneratedModelClassNames .OrderBy(x => x) .Select(x => new { Version = Regex.Match(x, versionRegex).Value?.ToLower(), Kinda = Regex.Replace(x, versionRegex, string.Empty), Type = x, }) .Where(x => !string.IsNullOrEmpty(x.Version)) .GroupBy(x => x.Kinda) .Where(x => x.Count() > 1) .SelectMany(x => x.SelectMany((value, index) => x.Skip(index + 1), (first, second) => new { first, second })) .OrderBy(x => x.first.Kinda) .ThenBy(x => x.first.Version) .Select(x => (x.first.Type, x.second.Type)) .ToList(); var versionConverterPairs = typePairs.Except(manualMaps).ToList(); var sbmodel = new StringBuilder(@"// namespace k8s.Models; using k8s.Versioning; "); var sbversion = new StringBuilder(@"// namespace k8s.Versioning; using AutoMapper; using k8s.Models; public static partial class VersionConverter { private static void AutoConfigurations(IMapperConfigurationExpression cfg) { "); foreach (var (t0, t1) in versionConverterPairs) { sbmodel.AppendLine($@" public partial class {t0} {{ public static explicit operator {t0}({t1} s) => VersionConverter.Mapper.Map<{t0}>(s); }} public partial class {t1} {{ public static explicit operator {t1}({t0} s) => VersionConverter.Mapper.Map<{t1}>(s); }}"); sbversion.AppendLine($@"cfg.CreateMap<{t0}, {t1}>().ReverseMap();"); } sbversion.AppendLine("}}"); context.AddSource($"generated_ModelOperators.cs", SourceText.From(sbmodel.ToString(), Encoding.UTF8)); context.AddSource($"generated_VersionConverter.cs", SourceText.From(sbversion.ToString(), Encoding.UTF8)); } public void Initialize(GeneratorInitializationContext context) { } } }