using System.Text.Json; using System.Dynamic; using System.Runtime.CompilerServices; namespace ComfySharp; public class NodeDBGenerator { readonly private List nodes; readonly private ConversionSettings settings; readonly private List knownTypes = new(); public List GetKnownTypes() => knownTypes; readonly private Dictionary> knownEnums = new(); public Dictionary> GetKnownEnums() => knownEnums; readonly private Dictionary> knownStringLists = new(); public Dictionary> GetKnownStringLists() => knownStringLists; public int Count => nodes.Count; private int typeFields; private int enumFields; public NodeDBGenerator(ConversionSettings settings) { nodes = new(); this.settings = settings; } public void ResetDb() => nodes.Clear(); /// /// Adds a new type to the list of known types, avoids duplicates /// /// type name as string private void AddKnownType(string type) { if (!knownTypes.Contains(type)) { knownTypes.Add(type); Logger.Info($"Added new known type: {type}"); } else { Logger.Trace($"Skipped already known type: {type}"); } typeFields++; } /// /// Adds or updates a known enum, avoids duplicates. /// /// enum type name as string /// Collection of valid string values for this enum private void AddKnownEnum(string type, ICollection values) { if (!knownEnums.ContainsKey(type)) { knownEnums.Add(type, values.ToList()); Logger.Info($"Added new known enum: {type}"); Logger.Trace($"\tAdded values: {string.Join(", ", values)}"); } else { values.ToList().ForEach(value => { if (!knownEnums[type].Contains(value)) { knownEnums[type].Add(value); Logger.Info($"Added new value to already known enum: {value}"); } else { Logger.Trace($"Skipped already known enum value: {type}.{value}"); } }); } enumFields++; } /// /// Adds or updates a known string list, avoids duplicates. /// /// stringList typename /// Collection of string values for this StringList private void AddKnownStringList(string type, ICollection values) { if (!knownStringLists.ContainsKey(type)) { knownStringLists.Add(type, values.ToList()); Logger.Info($"Added new known stringList: {type}"); Logger.Trace($"\tAdded values: {string.Join(", ", values)}"); } else { values.ToList().ForEach(value => { if (!knownStringLists[type].Contains(value)) { knownStringLists[type].Add(value); Logger.Info($"Added new value to already known stringList: {type}"); } else { Logger.Trace($"Skipped already known stringList value: {type}.{value}"); } }); } } /// /// Generates C# classes for all known types, enums and stringLists, then also for every node /// /// JsonDocument containing the nodes off an objectInfo api call public void GenerateClasses(JsonDocument document) { Logger.Info("NodeDB Scan phase 1: building Types, Enum and StringLists DataBases"); foreach (var node in document.RootElement.EnumerateObject()) ScanNode(node); string types = ""; foreach (var knownType in knownTypes) types += $"\t{knownType}"; Logger.Debug($"List of recognized Types:\n{types}"); Logger.Info($"Total amount of types iterated: {typeFields}\n"); string enums = ""; foreach (var knownEnum in knownEnums) { enums += $"{knownEnum.Key}\n"; foreach (var value in knownEnum.Value) enums += $"\t{value}"; enums += "\n"; } Logger.Debug($"List of recognized Enums: {enums}"); Logger.Info($"Total amount of enums iterated: {enumFields}\n"); Logger.Info("NodeDB Scan phase 2: generating types"); } /// /// executed for a single node, progresses through the properties of the node /// private void ScanNode(JsonProperty node) { Logger.Debug($"Scanning node: {node.Name}"); // if this node is blacklisted, skip it if (settings.BlacklistedNodes.Contains(node.Name)) { Logger.Debug($"Skipping blacklisted node: {node.Name}"); return; } // each of this are top level properties of the node foreach (var property in node.Value.EnumerateObject()) { //if this is a list of input properties: if (property is { Name: "input", Value.ValueKind: JsonValueKind.Object }) ScanInputs(property); else if (property is { Name: "output", Value.ValueKind: JsonValueKind.Array }) ScanOutputs(property); } } /// /// Executed on the input property inside a node /// private void ScanInputs(JsonProperty input) { Logger.Trace($"Scanning inputs of node: {input.Name}"); foreach (var inputType in input.Value.EnumerateObject()) { //these are related to the nodes themselves and useless for us if (inputType.Name == "hidden") continue; // required and optionals have the same structure, so we can parse them the same way if (inputType.Name == "required" || inputType.Name == "optional") foreach (var inputProperty in inputType.Value.EnumerateObject()) ScanInputField(inputProperty); } } /// /// Executed for each of the elements inside a required or optional input /// private void ScanInputField(JsonProperty inputProperty) { Logger.Trace($"Scanning input field: {inputProperty.Name}"); // if element 0 is a string, this is a type if (inputProperty.Value[0].ValueKind == JsonValueKind.String) { AddKnownType(inputProperty.Value[0].ToString()); return; } // if element 0 is an array, this is an enum or a list. if (inputProperty.Value[0].ValueKind == JsonValueKind.Array) { var firstElement = inputProperty.Value.EnumerateArray().ToArray()[0]; if (firstElement.GetArrayLength() > 0) { //if the elements inside the array are not strings, this is a list of objects and might require special handling if (firstElement[0].ValueKind != JsonValueKind.String) { Logger.Info($"Encountered a special case: {inputProperty.Name}" + $"\nFirst element of array is not a string, but a {firstElement[0].ValueKind}"); return; } List enumValues = new(); //holds all the values of the enum, valid for both following cases firstElement.EnumerateArray().ToList().ForEach(value => enumValues.Add(value.ToString())); // these are all lists of strings and not enums if (settings.EnumConvertAsString.Contains(inputProperty.Name)) { AddKnownStringList(inputProperty.Name, enumValues); } else { AddKnownEnum(inputProperty.Name, enumValues); } } else { //if the array is empty, this is a list of unknown and might require special handling later on, for now, skip it Logger.Info($"Encountered a special case: {inputProperty.Name}" + "\nFirst element of array is empty"+ "\nAdding to known types as empty list..."); AddKnownStringList(inputProperty.Name, new List()); } } } /// /// Executed for each output array of every node. /// private void ScanOutputs(JsonProperty output) { Logger.Trace($"Scanning outputs of node: {output.Name}"); foreach (var outputType in output.Value.EnumerateArray()) AddKnownType(outputType.GetString()!); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void GenerateNode(JsonElement data) { var node = new ExpandoObject(); Logger.Info($"Generating node: {data.GetProperty("name").GetString() ?? ""}" ); foreach (var property in data.EnumerateObject()) { Logger.Info($"Adding new property: {property.Name}\nType: {property.Value}\nValue: {property.Value.GetType()}\n"); node.TryAdd(property.Name, property.Value); } nodes.Add(node); } public void GenerateNode(JsonProperty node) { GenerateNode(node.Value); } public void GenerateNodes(JsonDocument document) { foreach (var node in document.RootElement.EnumerateObject()) { GenerateNode(node.Value); } } public void GenerateNodes(string json) { var document = JsonDocument.Parse(json); GenerateNodes(document); } public void GenerateNodes(ICollection elements) { foreach (var element in elements) { GenerateNode(element); } } public void GenerateNodes(ICollection elements) { foreach (var element in elements) { GenerateNode(element); } } public List GetNodes() => nodes; }