using System.Text;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Core;
// Resolve paths: when run as pre-build from Web project, working dir is Web/
var solutionDir = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), ".."));
var docsDir = Path.GetFullPath(Path.Combine(solutionDir, "..", "fellowship.docs"));
var outputFile = Path.Combine(solutionDir, "Model", "DocsData.cs");
if (!Directory.Exists(docsDir))
{
Console.Error.WriteLine($"Docs directory not found: {docsDir}");
return 1;
}
var mdFiles = Directory.EnumerateFiles(docsDir, "*.md", SearchOption.AllDirectories)
.OrderBy(f => f)
.ToArray();
Console.WriteLine($"Found {mdFiles.Length} markdown files in {docsDir}");
var sb = new StringBuilder();
sb.AppendLine("// ");
sb.AppendLine("// Generated by the Build project. Do not edit manually.");
sb.AppendLine();
sb.AppendLine("namespace Model;");
sb.AppendLine();
sb.AppendLine("public static class DocsData");
sb.AppendLine("{");
sb.AppendLine(" public static readonly List All = new()");
sb.AppendLine(" {");
foreach (var file in mdFiles)
{
var content = File.ReadAllText(file);
var (yamlBlock, body) = ExtractFrontmatter(content);
if (yamlBlock == null) continue;
var fields = ParseYamlFields(yamlBlock);
var relativePath = Path.GetRelativePath(docsDir, file);
var fileName = Path.GetFileName(file);
var typeName = GetValue(fields, "type") ?? "Unknown";
var csharpType = typeName switch
{
"Skill" => "SkillDoc",
"Debuff" => "DebuffDoc",
"Buff" => "BuffDoc",
"Key" or "Mouse" => "KeyDoc",
"Character" => "CharacterDoc",
_ => "SkillDoc"
};
sb.AppendLine($" new {csharpType}");
sb.AppendLine(" {");
sb.AppendLine($" FileName = {EscapeString(fileName)},");
sb.AppendLine($" FilePath = {EscapeString(relativePath)},");
if (body != null)
sb.AppendLine($" Body = {EscapeString(body.TrimEnd())},");
var knownProps = typeName switch
{
"Skill" => GetSkillProperties(fields),
"Debuff" => GetDebuffProperties(fields),
"Buff" => GetBuffProperties(fields),
"Key" or "Mouse" => GetKeyProperties(fields),
"Character" => GetCharacterProperties(fields),
_ => GetSkillProperties(fields)
};
foreach (var (propName, propValue) in knownProps)
sb.AppendLine($" {propName} = {propValue},");
sb.AppendLine(" },");
}
sb.AppendLine(" };");
sb.AppendLine("}");
File.WriteAllText(outputFile, sb.ToString());
Console.WriteLine($"Generated {outputFile}");
return 0;
static (string? yaml, string? body) ExtractFrontmatter(string content)
{
if (!content.StartsWith("---")) return (null, null);
var endIndex = content.IndexOf("---", 3, StringComparison.Ordinal);
if (endIndex == -1) return (null, null);
var yaml = content[3..endIndex].Trim();
var body = content[(endIndex + 3)..].Trim();
return (yaml, string.IsNullOrEmpty(body) ? null : body);
}
static Dictionary ParseYamlFields(string yamlBlock)
{
var result = new Dictionary();
try
{
var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(yamlBlock));
if (yamlStream.Documents.Count > 0 && yamlStream.Documents[0].RootNode is YamlMappingNode mapping)
foreach (var (keyNode, valueNode) in mapping)
{
var key = keyNode.ToString();
result[key] = ConvertYamlNode(valueNode);
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"Warning: Failed to parse YAML: {ex.Message}");
}
return result;
}
static object? ConvertYamlNode(YamlNode node)
{
return node switch
{
YamlScalarNode scalar => scalar.Value,
YamlSequenceNode seq => seq.Children.Select(ConvertYamlNode).ToList(),
YamlMappingNode map => map.ToDictionary(kvp => kvp.Key.ToString(), kvp => ConvertYamlNode(kvp.Value)),
_ => node.ToString()
};
}
static string? GetValue(Dictionary fields, string key)
{
if (fields.TryGetValue(key, out var val) && val is string s && !string.IsNullOrEmpty(s))
return s;
return null;
}
static string EscapeString(string? s)
{
if (s == null) return "null";
if (s.Contains('\n') || s.Contains('"') || s.Contains('\\'))
{
var escaped = s.Replace("\"", "\"\"");
return $"@\"{escaped}\"";
}
return $"\"{s}\"";
}
static string EscapeBool(object? val)
{
if (val is string s)
{
if (bool.TryParse(s, out var b)) return b ? "true" : "false";
return "null";
}
return "null";
}
static string EscapeStringOrNull(object? val)
{
if (val is string s) return EscapeString(s);
return "null";
}
static string EscapeList(object? val)
{
if (val is List