using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.CodeAnalysis; namespace AscNet.Table { [Generator] public class TableGenerator : ISourceGenerator { public void Execute(GeneratorExecutionContext context) { List fails = new(); foreach (var table in context.AdditionalFiles.Where(x => x.Path.EndsWith(".tsv"))) { try { string ns = string.Join("", Path.GetDirectoryName(table.Path)?.Split(new string[] { "table" }, StringSplitOptions.None).Skip(1)!) .Replace('\\', '/').Replace('/', '.'); string fileTsv = table.GetText()?.ToString() ?? string.Empty; IEnumerable tsvLines = fileTsv.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries); string[] nameList = tsvLines.First().Split('\t'); List resolvedTypes = new(); foreach (var items in tsvLines.Skip(1).Select(x => x.Split('\t'))) { List types = new(); foreach (var item in items) { if (string.IsNullOrEmpty(item)) { types.Add("global::System.String"); continue; } else if (int.TryParse(item, out var res)) { types.Add("global::System.Int32"); continue; } types.Add("global::System.String"); } resolvedTypes.Add(types.ToArray()); } Dictionary properties = new(); Dictionary listCount = new(); for (int i = 0; i < nameList.Length; i++) { string headName = nameList[i]; if (string.IsNullOrEmpty(headName)) continue; string[] types = resolvedTypes.Select(x => x[i]).ToArray(); string propName = headName.Split('[').First(); string resolvedType = types.All(x => x.Equals(types.First())) ? types.First() : "global::System.String"; if (headName.Contains('[')) { if (listCount.ContainsKey(propName)) listCount[propName] += 1; else listCount.Add(propName, 1); resolvedType = $"global::System.Collections.Generic.List<{resolvedType}>"; } if (!properties.ContainsKey(propName)) properties.Add(propName, $"public {resolvedType} {propName} {{ get; set; }}"); } string file = $@"// namespace AscNet.Table{ns} {{ #nullable enable #pragma warning disable CS8618, CS8602 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. public class {Path.GetFileName(table.Path).Split('.').First()}Table {{ {string.Join("\r\n\t\t", properties.Values)} }} public class {Path.GetFileName(table.Path).Split('.').First()}TableReader : global::AscNet.Common.Util.TableReader<{Path.GetFileName(table.Path).Split('.').First()}TableReader, {Path.GetFileName(table.Path).Split('.').First()}Table> {{ protected override string FilePath {{ get {{ return ""{string.Join("", table.Path.Replace("\\", "/").Split(new string[] {"/Resources/"}, StringSplitOptions.None).Skip(1))}""; }} }} public override void Load() {{ string tsvStr = global::System.IO.File.ReadAllText(FilePath); string[] tsvValues = tsvStr.Split('\t'); string[] csvValues = new string[tsvValues.Length]; for (int i = 0; i < tsvValues.Length; i++) {{ if (tsvValues[i].Contains("","")) {{ csvValues[i] = ""\"""" + tsvValues[i].Replace(""\"""", ""\""\"""") + ""\""""; }} else {{ csvValues[i] = tsvValues[i]; }} }} using var reader = new global::System.IO.StringReader(string.Join("","", csvValues)); using var csv = new global::CsvHelper.CsvReader(reader, new global::CsvHelper.Configuration.CsvConfiguration(global::System.Globalization.CultureInfo.InvariantCulture) {{ BadDataFound = null, HeaderValidated = null, MissingFieldFound = null }}); csv.Context.RegisterClassMap<{Path.GetFileName(table.Path).Split('.').First()}TableMap>(); All = csv.GetRecords<{Path.GetFileName(table.Path).Split('.').First()}Table>().ToList(); }} {string.Join("\r\n", properties.Values.Select(x => x.Split(' ')).Where(x => !x[1].Contains("<")).Select(property => $@" public {Path.GetFileName(table.Path).Split('.').First()}Table? From{property[2]}({property[1]} val) {{ return All.FirstOrDefault(x => x.{property[2]} == val); }} "))} }} public sealed class {Path.GetFileName(table.Path).Split('.').First()}TableMap : global::CsvHelper.Configuration.ClassMap<{Path.GetFileName(table.Path).Split('.').First()}Table> {{ public {Path.GetFileName(table.Path).Split('.').First()}TableMap() {{ {string.Join("\r\n\t\t\t", properties.Keys.Where(x => !listCount.ContainsKey(x)).Select(x => $"Map(m => m.{x}).Name(\"{x}\");"))} {string.Join("\r\n\t\t\t", listCount.Keys.Select(x => $@" Map(m => m.{x}).Convert(args => {{ {properties[x].Split(' ')[1]} tags = new {properties[x].Split(' ')[1]}(); {(nameList.Contains($"{x}[0]") ? $"for (int i = 0; i < {listCount[x]}; i++)" : $"for (int i = 1; i <= {listCount[x]}; i++)")} {{ string? tagValue = args.Row.GetField($""{x}[{{i}}]""); if (!string.IsNullOrEmpty(tagValue)) {{ {(properties[x].Split('<')[1].StartsWith("global::System.Int32") ? @" if (int.TryParse(tagValue, out int tag)) { tags.Add(tag); }": @" tags.Add(tagValue); ")} }} }} return tags; }}); "))} }} }} }}"; context.AddSource($"AscNet.Table{ns}.{Path.GetFileName(table.Path).Split('.').First()}.g.cs", file); } catch (Exception ex) { fails.Add(new { msg = ex.Message, file = table.Path }); } } } public void Initialize(GeneratorInitializationContext context) { } } }