-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathExportPathsCommand.cs
110 lines (89 loc) · 4.25 KB
/
ExportPathsCommand.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using System.Collections.Immutable;
using DotMake.CommandLine;
using EXDTooler.Schema;
using Lumina.Data.Structs.Excel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace EXDTooler;
[CliCommand(Parent = typeof(MainCommand))]
public sealed class ExportPathsCommand
{
public required MainCommand Parent { get; set; }
[CliOption(Required = false, Description = "Path to the game directory. Should be the root of the game's repository.", ValidationRules = CliValidationRules.ExistingDirectory)]
public string? GamePath { get; set; }
[CliOption(Required = false, Description = "Path to the columns file generated by export-columns.", ValidationRules = CliValidationRules.ExistingFile)]
public string? ColumnsFile { get; set; }
[CliOption(Required = true, Description = "Path to the schema directory. Should be a folder with just .yml schemas.", ValidationRules = CliValidationRules.ExistingDirectory)]
public required string SchemaPath { get; set; }
[CliOption(Required = true, Description = "Path to the output directory.")]
public required string OutputPath { get; set; }
public Task RunAsync()
{
var token = Parent.Init();
var sheets = ColDefReader.FromInputs(GamePath, ColumnsFile);
Directory.CreateDirectory(OutputPath);
var schemaDeserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
var schemaSerializer = new SerializerBuilder()
.DisableAliases()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.WithEnumNamingConvention(LowerCaseNamingConvention.Instance)
.WithIndentedSequences()
.Build();
var files = Directory.EnumerateFiles(SchemaPath, "*.yml").ToArray();
foreach (var (sheetName, defs) in sheets.Sheets)
{
var hash = $"{sheets.GetColumnsHash(sheetName):X8}";
IEnumerable<string>? schemaPaths = null;
var sheetFile = Path.Combine(SchemaPath, $"{sheetName}.yml");
if (File.Exists(sheetFile))
{
using var f = File.OpenText(sheetFile);
var sheet = schemaDeserializer.Deserialize<Sheet>(f);
schemaPaths = [.. GenerateFieldPaths([], sheet.Fields)];
}
var orderedColumns = defs.GroupBy(c => c.Offset).OrderBy(c => c.Key).SelectMany(g => g.OrderBy(c => c.Type)).ToArray();
var orderedPaths = schemaPaths?.ToArray() ?? new string[orderedColumns.Length];
Array.Resize(ref orderedPaths, orderedColumns.Length);
var entries = orderedColumns.Zip(orderedPaths, (c, p) => new FieldEntry(c, p));
var outPath = Path.Combine(OutputPath, $"{sheetName}.yml");
{
using var f = File.OpenWrite(outPath);
f.SetLength(0);
using var writer = new StreamWriter(f);
schemaSerializer.Serialize(writer, entries.ToArray());
}
}
return Task.CompletedTask;
}
public static IEnumerable<string> GenerateFieldPaths(ImmutableArray<string> scope, List<Field> fields, bool isArray = false)
{
foreach (var field in fields)
{
var fieldScope = scope;
if (isArray)
{
if (field.Name != null)
fieldScope = fieldScope.Add($".{field.Name}");
}
else
fieldScope = fieldScope.Add(field.Name ?? "Unk");
if (field.Type == FieldType.Array)
{
var subfields = field.Fields ?? [new Field() { Type = FieldType.Scalar }];
for (var i = 0; i < (field.Count ?? 1); ++i)
{
foreach (var item in GenerateFieldPaths(fieldScope.Add($"[{i}]"), subfields, true))
yield return item;
}
}
else
yield return string.Join("", fieldScope);
}
}
private sealed class FieldEntry(ExcelColumnDefinition definition, string? path)
{
public ushort Offset { get; } = definition.Offset;
public ExcelColumnDataType Type { get; } = definition.Type;
public string? Path { get; } = path;
}
}