using System.Text; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.RegularExpressions; using System.Text.Json.Serialization; var repoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..")); var docsDir = Path.Combine(repoRoot, "chrono.docs"); var webWwwRoot = Path.Combine(repoRoot, "Chrono", "Web", "wwwroot"); Console.WriteLine($"Repo root: {repoRoot}"); Console.WriteLine($"Docs dir: {docsDir}"); Console.WriteLine($"Web wwwroot: {webWwwRoot}"); if (!Directory.Exists(docsDir)) { Console.Error.WriteLine($"ERROR: docs dir not found: {docsDir}"); return 1; } var mdFiles = Directory.GetFiles(docsDir, "*.md"); var cards = new List(); foreach (var file in mdFiles) { var content = Encoding.UTF8.GetString(File.ReadAllBytes(file)); if (!content.StartsWith("---")) continue; var endIndex = content.IndexOf("---", 3, StringComparison.Ordinal); if (endIndex < 0) continue; var frontmatter = content[3..endIndex].Trim(); var yaml = ParseYaml(frontmatter); var name = Path.GetFileNameWithoutExtension(file); var category = yaml.GetValueOrDefault("category"); if (category == null) continue; var card = new CardData { Name = name, Category = category, Cost = ParseInt(yaml, "cost"), Attack = ParseInt(yaml, "attack"), Health = ParseInt(yaml, "health"), Description = StripWikiLinks(yaml.GetValueOrDefault("description")), Faction = StripWikiLink(yaml.GetValueOrDefault("faction")), Set = StripWikiLink(yaml.GetValueOrDefault("set")), Speed = StripWikiLink(yaml.GetValueOrDefault("speed")), Archetypes = ParseList(yaml, "archetypes").Select(s => StripWikiLink(s) ?? "").Where(s => s != "").ToList(), ImmortalizeTo = yaml.ContainsKey("immortalizeTo") ? ParseListOrScalar(yaml, "immortalizeTo").Select(s => StripWikiLink(s) ?? "").Where(s => s != "").ToList() : null, ImmortalizeFrom = StripWikiLink(yaml.GetValueOrDefault("immortalizeFrom")), ImmortalizeWhen = NullIfNa(StripWikiLinks(yaml.GetValueOrDefault("immortalizeWhen"))), ImageFile = StripWikiLink(yaml.GetValueOrDefault("imageLink")), }; if (card.ImageFile != null && !card.ImageFile.EndsWith(".png")) card.ImageFile += ".png"; cards.Add(card); } // Copy PNGs to wwwroot/cards var cardsDir = Path.Combine(webWwwRoot, "cards"); Directory.CreateDirectory(cardsDir); foreach (var card in cards) { if (card.ImageFile == null) continue; var src = Path.Combine(docsDir, card.ImageFile); var dst = Path.Combine(cardsDir, card.ImageFile); if (File.Exists(src)) File.Copy(src, dst, overwrite: true); } // Write cards.json var output = new { cards }; var json = JsonSerializer.Serialize(output, new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }); var jsonPath = Path.Combine(webWwwRoot, "sample-data", "cards.json"); File.WriteAllText(jsonPath, json); Console.WriteLine($"Processed {cards.Count} cards"); Console.WriteLine($"Written to {jsonPath}"); return 0; // --- Helpers --- static int? ParseInt(Dictionary yaml, string key) { if (!yaml.TryGetValue(key, out var val)) return null; if (int.TryParse(val, out var i)) return i; return null; } static string? StripWikiLink(string? s) { if (s == null || s == "N/A") return null; return Regex.Replace(s, @"\[\[([^\]]*)\]\]", "$1").Trim('"'); } static string? NullIfNa(string? s) => s is "N/A" or null ? null : s.Trim('"'); static string? StripWikiLinks(string? s) { if (s == null || s == "N/A") return null; return Regex.Replace(s.Trim('"'), @"\[\[([^\]]*)\]\]", "$1").Trim(); } static List ParseList(Dictionary yaml, string key) { if (!yaml.TryGetValue(key, out var raw)) return []; var result = new List(); foreach (var item in raw.Split('\n', StringSplitOptions.RemoveEmptyEntries)) { var trimmed = item.TrimStart('-', ' ').Trim(' ', '"'); if (trimmed.Length > 0) result.Add(trimmed); } return result; } static List ParseListOrScalar(Dictionary yaml, string key) { if (!yaml.TryGetValue(key, out var raw)) return []; raw = raw.Trim(); if (!raw.StartsWith('-')) return [raw.Trim('"')]; var result = new List(); foreach (var item in raw.Split('\n', StringSplitOptions.RemoveEmptyEntries)) { var trimmed = item.TrimStart('-', ' ').Trim(' ', '"'); if (trimmed.Length > 0) result.Add(trimmed); } return result; } static Dictionary ParseYaml(string yaml) { var dict = new Dictionary(); var lines = yaml.Split('\n'); string? currentKey = null; var listBuffer = new List(); foreach (var line in lines) { var trimmed = line.Trim(); if (trimmed.Length == 0) continue; if (trimmed.StartsWith("- ")) { listBuffer.Add(trimmed); continue; } if (listBuffer.Count > 0 && currentKey != null) { dict[currentKey] = string.Join("\n", listBuffer); listBuffer.Clear(); } var colonIndex = trimmed.IndexOf(':'); if (colonIndex < 0) continue; currentKey = trimmed[..colonIndex].Trim(); var value = trimmed[(colonIndex + 1)..].Trim(); if (value.Length == 0) { // Could be a list or multi-line value starting on next line continue; } dict[currentKey] = value; } if (listBuffer.Count > 0 && currentKey != null) dict[currentKey] = string.Join("\n", listBuffer); return dict; } record CardData { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("category")] public string Category { get; set; } = ""; [JsonPropertyName("cost")] public int? Cost { get; set; } [JsonPropertyName("attack")] public int? Attack { get; set; } [JsonPropertyName("health")] public int? Health { get; set; } [JsonPropertyName("description")] public string? Description { get; set; } [JsonPropertyName("faction")] public string? Faction { get; set; } [JsonPropertyName("set")] public string? Set { get; set; } [JsonPropertyName("speed")] public string? Speed { get; set; } [JsonPropertyName("archetypes")] public List? Archetypes { get; set; } [JsonPropertyName("immortalizeTo")] public List? ImmortalizeTo { get; set; } [JsonPropertyName("immortalizeFrom")] public string? ImmortalizeFrom { get; set; } [JsonPropertyName("immortalizeWhen")] public string? ImmortalizeWhen { get; set; } [JsonPropertyName("imageFile")] public string? ImageFile { get; set; } }