From 069adb5ff5108e2b2a23ac11d6c0a47436e8b80e Mon Sep 17 00:00:00 2001 From: chrisbell Date: Sun, 30 Nov 2025 21:16:33 -0600 Subject: [PATCH] Factories for templates and objects, Sztl parsing --- SessionZero/AppManager.cs | 97 +++++++++++++- SessionZero/Cogwheel/ConsoleControl.axaml | 2 +- SessionZero/Data/SzDataObject.cs | 6 + SessionZero/Data/SzDataObjectTemplate.cs | 19 +++ SessionZero/Data/SzObject.cs | 74 ++++++++++- SessionZero/Data/SzObjectFactory.cs | 68 ++++++++++ SessionZero/Data/SzTemplate.cs | 16 ++- SessionZero/Data/Sztl/SztlField.cs | 4 +- SessionZero/Data/Sztl/SztlParser.cs | 146 ++++++++++++++++++++++ SessionZero/MainWindow.axaml.cs | 4 + 10 files changed, 421 insertions(+), 15 deletions(-) create mode 100644 SessionZero/Data/SzDataObject.cs create mode 100644 SessionZero/Data/SzDataObjectTemplate.cs create mode 100644 SessionZero/Data/SzObjectFactory.cs diff --git a/SessionZero/AppManager.cs b/SessionZero/AppManager.cs index 41814b7..bdd656d 100644 --- a/SessionZero/AppManager.cs +++ b/SessionZero/AppManager.cs @@ -1,5 +1,8 @@ +using System; using Cogwheel; using SessionZero.Cogwheel; +using SessionZero.Data; +using SessionZero.Data.Sztl; namespace SessionZero; @@ -28,10 +31,98 @@ public static class AppManager { MainWindow.ChangePage(pageName); } - + + + + [Command(Name = "test")] - private static void Test() + public static void TestParseTemplate() { - COGWHEEL.LogWarning($"Trim size is: {ConsoleControl.OutputTrimSize}"); + string toml = @" + [metadata] + template_type = ""data"" + id = ""core_item"" + uuid = ""0"" + data_type = ""item"" + version = ""1.0.0"" + description = ""Core SessionZero item template"" + + [name] + type = ""text"" + + [description] + type = ""textblock"" + + [consumable] + type = ""bool"" + default_value = false + + [stats.value] + type = ""number"" + + [stats.weight] + type = ""number"" + + [stats.modifiers.base] + type = ""number"" + default_value = 1 + "; + + try + { + // Parse template + var template = SztlParser.ParseTemplate(toml); + + COGWHEEL.Log($"Template ID: {template.Id}"); + + COGWHEEL.Log("\nTop-Level Fields:"); + foreach (var f in template.Fields) + { + COGWHEEL.Log($"- {f.Key}: Type={f.Value.Type}, Default={f.Value}, TextBlock={f.Value.IsTextBlock}"); + } + + COGWHEEL.Log("\nGroups:"); + foreach (var g in template.SubGroups) + { + PrintGroup(g.Value, ""); + } + + // --- Instantiate SzDataObject --- + var obj = SzObjectFactory.CreateObject(template); + + // Set some values + obj.SetFieldValue("name", "Excalibur"); + obj.SetFieldValue("consumable", true); + obj.SetFieldValue("stats.value", 10); + obj.SetFieldValue("stats.weight", 2); + obj.SetFieldValue("stats.modifiers.base", 5); + + // Get and log values + COGWHEEL.Log("\nInstanced Object Field Values:"); + COGWHEEL.Log($"name = {obj.GetFieldValueAsString("name")}"); + COGWHEEL.Log($"consumable = {obj.GetFieldValue("consumable")}"); + COGWHEEL.Log($"stats.value = {obj.GetFieldValue("stats.value")}"); + COGWHEEL.Log($"stats.weight = {obj.GetFieldValue("stats.weight")}"); + COGWHEEL.Log($"stats.modifiers.base = {obj.GetFieldValue("stats.modifiers.base")}"); + } + catch (Exception ex) + { + COGWHEEL.Log($"Error parsing template: {ex.Message}"); + } + } + + private static void PrintGroup(SztlFieldGroup group, string indent) + { + COGWHEEL.Log($"{indent}Group: {group.Id}"); + + foreach (var f in group.Fields) + { + COGWHEEL.Log($"{indent} Field: {f.Key}, Type={f.Value.Type}, Default={f.Value}"); + } + + foreach (var sg in group.SubGroups) + { + PrintGroup(sg.Value, indent + " "); + } } } \ No newline at end of file diff --git a/SessionZero/Cogwheel/ConsoleControl.axaml b/SessionZero/Cogwheel/ConsoleControl.axaml index 0be36de..489550e 100644 --- a/SessionZero/Cogwheel/ConsoleControl.axaml +++ b/SessionZero/Cogwheel/ConsoleControl.axaml @@ -6,7 +6,7 @@ x:Class="SessionZero.Cogwheel.ConsoleControl"> - + diff --git a/SessionZero/Data/SzDataObject.cs b/SessionZero/Data/SzDataObject.cs new file mode 100644 index 0000000..28f6699 --- /dev/null +++ b/SessionZero/Data/SzDataObject.cs @@ -0,0 +1,6 @@ +namespace SessionZero.Data; + +public class SzDataObject : SzObject +{ + +} \ No newline at end of file diff --git a/SessionZero/Data/SzDataObjectTemplate.cs b/SessionZero/Data/SzDataObjectTemplate.cs new file mode 100644 index 0000000..a26a525 --- /dev/null +++ b/SessionZero/Data/SzDataObjectTemplate.cs @@ -0,0 +1,19 @@ +using System; +using Tomlyn.Model; + +namespace SessionZero.Data; + +public class SzDataObjectTemplate : SzTemplate +{ + public string DataType { get; set; } + + public override void ParseAdditionalMetaData(TomlTable table) + { + if (!table.ContainsKey("metadata") || table["metadata"] is not TomlTable metadata) return; + + if (metadata.TryGetValue("data_type", out var tempTypeVal)) + { + DataType = tempTypeVal?.ToString() ?? throw new Exception("Template missing type metadata"); + } + } +} \ No newline at end of file diff --git a/SessionZero/Data/SzObject.cs b/SessionZero/Data/SzObject.cs index a49d883..b0cb6e6 100644 --- a/SessionZero/Data/SzObject.cs +++ b/SessionZero/Data/SzObject.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using SessionZero.Data.Sztl; @@ -14,21 +15,82 @@ public class SzObject public virtual object? GetFieldValue(string fieldPath) { - throw new System.NotImplementedException(); + if (string.IsNullOrEmpty(fieldPath)) return null; + + var parts = fieldPath.Split('.'); + if (parts.Length == 1) + { + if (Fields.TryGetValue(parts[0], out var field)) + return field.Value; + return null; + } + + SzFieldGroup? currentGroup = null; + for (int i = 0; i < parts.Length - 1; i++) + { + var key = parts[i]; + currentGroup = currentGroup == null + ? FieldGroups.GetValueOrDefault(key) + : currentGroup.SubGroups.GetValueOrDefault(key); + + if (currentGroup == null) return null; + } + + var lastPart = parts[^1]; + if (currentGroup != null) + { + if (currentGroup.Fields.TryGetValue(lastPart, out var field)) + return field.Value; + } + + return Fields.TryGetValue(lastPart, out var topField) ? topField.Value : null; } public virtual T GetFieldValue(string fieldPath) { - throw new System.NotImplementedException(); + var val = GetFieldValue(fieldPath); + if (val == null) return default!; + return (T)Convert.ChangeType(val, typeof(T)); } public virtual string GetFieldValueAsString(string fieldPath) { - throw new System.NotImplementedException(); + var val = GetFieldValue(fieldPath); + return val?.ToString() ?? string.Empty; } - - public virtual string SetFieldValue(string fieldPath, object value) + + public virtual void SetFieldValue(string fieldPath, object value) { - throw new System.NotImplementedException(); + if (string.IsNullOrEmpty(fieldPath)) return; + + var parts = fieldPath.Split('.'); + if (parts.Length == 1) + { + if (Fields.ContainsKey(parts[0])) + Fields[parts[0]].Value = value; + return; + } + + SzFieldGroup? currentGroup = null; + for (int i = 0; i < parts.Length - 1; i++) + { + var key = parts[i]; + currentGroup = currentGroup == null + ? FieldGroups.GetValueOrDefault(key) + : currentGroup.SubGroups.GetValueOrDefault(key); + + if (currentGroup == null) return; + } + + var lastPart = parts[^1]; + if (currentGroup != null) + { + if (currentGroup.Fields.ContainsKey(lastPart)) + currentGroup.Fields[lastPart].Value = value; + } + else if (Fields.ContainsKey(lastPart)) + { + Fields[lastPart].Value = value; + } } } \ No newline at end of file diff --git a/SessionZero/Data/SzObjectFactory.cs b/SessionZero/Data/SzObjectFactory.cs new file mode 100644 index 0000000..8ef1bb6 --- /dev/null +++ b/SessionZero/Data/SzObjectFactory.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using SessionZero.Data.Sztl; + +namespace SessionZero.Data +{ + public static class SzObjectFactory + { + public static SzObject CreateObject(SzTemplate template) + { + ArgumentNullException.ThrowIfNull(template); + + SzObject obj = template switch + { + SzDataObjectTemplate _ => new SzDataObject(), + _ => throw new NotImplementedException($"Cannot create object for template type '{template.TemplateType}'") + }; + + obj.TemplateId = template.Id; + obj.Id = ""; // TODO: This will be replaces with the top level "id" field whenever it gets filled in in the UI + obj.Uuid = "0"; // TODO: Generate true UUIDs later + + foreach (var field in template.Fields) + { + obj.Fields[field.Key] = new SzField + { + Id = field.Value.Id, + Type = field.Value.Type, + Value = field.Value.DefaultValue, + IsTextBlock = field.Value.IsTextBlock + }; + } + + foreach (var group in template.SubGroups) + { + obj.FieldGroups[group.Key] = InitializeGroup(group.Value); + } + + return obj; + } + + private static SzFieldGroup InitializeGroup(SztlFieldGroup groupTemplate) + { + var group = new SzFieldGroup + { + Id = groupTemplate.Id + }; + + foreach (var field in groupTemplate.Fields) + { + group.Fields[field.Key] = new SzField + { + Id = field.Value.Id, + Type = field.Value.Type, + Value = field.Value.DefaultValue, + IsTextBlock = field.Value.IsTextBlock + }; + } + + foreach (var subGroup in groupTemplate.SubGroups) + { + group.SubGroups[subGroup.Key] = InitializeGroup(subGroup.Value); + } + + return group; + } + } +} diff --git a/SessionZero/Data/SzTemplate.cs b/SessionZero/Data/SzTemplate.cs index 2c45803..5b4f65e 100644 --- a/SessionZero/Data/SzTemplate.cs +++ b/SessionZero/Data/SzTemplate.cs @@ -1,14 +1,24 @@ using System.Collections.Generic; using SessionZero.Data.Sztl; +using Tomlyn.Model; namespace SessionZero.Data; public class SzTemplate { + public string TemplateType { get; set; } public string Id { get; set; } - public string Name { get; set; } - public string DataType { get; set; } + public string Uuid { get; set; } + public string Version { get; set; } + public List CompatibleSystems { get; set; } + public string Description { get; set; } + public Dictionary Fields { get; set; } = new(); public Dictionary SubGroups { get; set; } = new(); - + + public virtual void ParseAdditionalMetaData(TomlTable table) + { + + } + } \ No newline at end of file diff --git a/SessionZero/Data/Sztl/SztlField.cs b/SessionZero/Data/Sztl/SztlField.cs index 932a8c8..57c9c1e 100644 --- a/SessionZero/Data/Sztl/SztlField.cs +++ b/SessionZero/Data/Sztl/SztlField.cs @@ -2,8 +2,8 @@ namespace SessionZero.Data.Sztl; public class SztlField { - public required string Id { get; set; } - public required SzFieldType Type { get; set; } + public string Id { get; set; } + public SzFieldType Type { get; set; } public object? DefaultValue { get; set; } public string? Description { get; set; } public bool IsList { get; set; } diff --git a/SessionZero/Data/Sztl/SztlParser.cs b/SessionZero/Data/Sztl/SztlParser.cs index c21bec4..1c7db3e 100644 --- a/SessionZero/Data/Sztl/SztlParser.cs +++ b/SessionZero/Data/Sztl/SztlParser.cs @@ -1,6 +1,152 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Tomlyn; +using Tomlyn.Model; + namespace SessionZero.Data.Sztl; public static class SztlParser { + public static SzTemplate ParseTemplate(string tomlText) +{ + var doc = Toml.ToModel(tomlText); + + + if (!doc.ContainsKey("metadata") || doc["metadata"] is not TomlTable metadata) + { + throw new Exception("Template missing the [metadata] section"); + } + + // Determine template type and fill metadata + string templateType = metadata.TryGetValue("template_type", out var tempTypeVal) + ? tempTypeVal?.ToString() ?? throw new Exception("Template missing template_type metadata") + : throw new Exception("Template missing template_type metadata"); + SzTemplate template = templateType switch + { + "data" => new SzDataObjectTemplate(), + _ => throw new NotImplementedException($"Unknown template_type '{templateType}'") + }; + + if (metadata.TryGetValue("id", out var idVal)) template.Id = idVal?.ToString()!; + else throw new Exception("Template missing id metadata"); + + if (metadata.TryGetValue("uuid", out var uuidVal)) template.Uuid = uuidVal?.ToString()!; + else throw new Exception("Template missing uuid metadata"); + + if (metadata.TryGetValue("version", out var versionVal)) template.Version = versionVal?.ToString()!; + else throw new Exception("Template missing version metadata"); + + if (metadata.TryGetValue("compatible_systems", out var compSysVal) && compSysVal is string[] arr) + template.CompatibleSystems = arr.ToList(); + + if (metadata.TryGetValue("description", out var descVal)) + template.Description = descVal?.ToString()!; + else throw new Exception("Template missing description metadata"); + + template.ParseAdditionalMetaData(doc); + + // Process all other tables as fields or groups + ProcessTable(template, doc); + + return template; +} + + + + private static void InsertFieldIntoGroup(SzTemplate template, string[] pathParts, TomlTable table) + { + var groupDict = template.SubGroups; + SztlFieldGroup currentGroup = null!; + + for (int i = 0; i < pathParts.Length - 1; i++) + { + var part = pathParts[i]; + + if (!groupDict.TryGetValue(part, out var grp)) + { + grp = new SztlFieldGroup { Id = part }; + groupDict[part] = grp; + } + currentGroup = grp; + groupDict = grp.SubGroups; + } + + var fieldId = pathParts[^1]; + var leafTable = table; + + if (table.TryGetValue(fieldId, out var possibleLeaf) && possibleLeaf is TomlTable t) + { + leafTable = t; + } + + var field = ParseFieldFromTable(fieldId, leafTable); + currentGroup.Fields[fieldId] = field; + } + + + private static SztlField ParseFieldFromTable(string id, TomlTable table) + { + var field = new SztlField + { + Id = id + }; + + if (!table.TryGetValue("type", out var tVal)) + { + throw new Exception($"Field '{id}' missing type"); + } + + var typeStr = tVal?.ToString() ?? throw new Exception($"Field '{id}' has null type"); + if (!Enum.TryParse(typeStr, ignoreCase: true, out var ftype)) + { + throw new Exception($"Field '{id}' has unknown type '{typeStr}'"); + } + field.Type = ftype; + + if (table.TryGetValue("default_value", out var dVal)) + { + field.DefaultValue = dVal; + } + + if (ftype == SzFieldType.Textblock) + { + field.IsTextBlock = true; + } + + return field; + } + + private static void ProcessTable(SzTemplate template, TomlTable table, string parentPath = "") + { + foreach (var (key, value) in table) + { + if (key == "metadata") continue; + + var path = string.IsNullOrEmpty(parentPath) ? key : $"{parentPath}.{key}"; + + if (value is TomlTable t) + { + if (t.ContainsKey("type")) + { + var field = ParseFieldFromTable(key, t); + + if (string.IsNullOrEmpty(parentPath)) + { + template.Fields[key] = field; + } + else + { + InsertFieldIntoGroup(template, path.Split('.'), t); + } + } + else + { + ProcessTable(template, t, path); + } + } + } + } } \ No newline at end of file diff --git a/SessionZero/MainWindow.axaml.cs b/SessionZero/MainWindow.axaml.cs index b6f78b0..93ec1da 100644 --- a/SessionZero/MainWindow.axaml.cs +++ b/SessionZero/MainWindow.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Input; using Cogwheel; using SessionZero.Cogwheel; +using SessionZero.Data.Sztl; using SessionZero.Pages; namespace SessionZero; @@ -98,4 +99,7 @@ public partial class MainWindow : Window _libraryButton?.Click += (_, _) => ChangePage("Library"); _settingsButton?.Click += (_, _) => ChangePage("Settings"); } + + + }