using System; using System.Dynamic; using System.Reflection; using System.Runtime.CompilerServices; using Newtonsoft.Json; [assembly: InternalsVisibleTo("deployment.tests")] namespace greyn.Deployment { public static class ConfigurationBootstrapper { private const string confpath = "appsettings.json"; public static T Load() where T : new() { if (File.Exists("appsettings.json")) { /* "aaaahhh this is the most abstract and reflective thing in the goddamn world aaaahhhhh next you'll make an ECS on a dynamic aaaahhh" * Ok, yes. I share these fears. but. * this way we can read a *very* arbitrary configuration file. * some random external file's configuration values is a good reason to aim for very high flexibility, imo * if the configuration expects new values we write them in, * if you left other junk for whatever reason, we don't delete them. */ var actualConfig = JsonConvert.DeserializeObject(File.ReadAllText(confpath)) ?? new ExpandoObject(); var toReturn = new T(); populateExpando(toReturn, ref actualConfig); File.WriteAllText(confpath, JsonConvert.SerializeObject(actualConfig, Formatting.Indented)); readFromExpando(ref toReturn, actualConfig); return toReturn; } else { var toReturn = new T(); File.WriteAllText(confpath, JsonConvert.SerializeObject(toReturn)); return toReturn; } } //TODO: make private but get tests to cooperate public static void populateExpando(T config, ref ExpandoObject fromFile) { if (config == null) return; if (fromFile == null) { var expandoPopulated = new ExpandoObject(); var dictionaryFromExpandoPopulated = (IDictionary)expandoPopulated; foreach (var property in config.GetType().GetProperties()) dictionaryFromExpandoPopulated.Add(property.Name, property.GetValue(config)); expandoPopulated = (ExpandoObject)dictionaryFromExpandoPopulated; fromFile = expandoPopulated; return; } var dictionaryFromExpandoFromFile = (IDictionary)fromFile; foreach (var property in config.GetType().GetProperties()) { if (dictionaryFromExpandoFromFile.TryGetValue(property.Name, out object? value)) { if (value != null) { if (property.PropertyType.GetMembers() == null) { var childProperty = (ExpandoObject)property.GetValue(fromFile); populateExpando(property.GetValue(config), ref childProperty); property.SetValue(fromFile, childProperty); } } } else { fromFile.TryAdd(property.Name, property.GetValue(config)); } } } public static void readFromExpando(ref T config, ExpandoObject readFromFile) { //TODO: read from Expando } // private static T sync(ref T config, ref ExpandoObject readFromFile) // { // var fieldsFoundThisLevel = new List(); // if(config == null) // { // //TODO: it's entirely possible this is null. // throw new ArgumentNullException(); // } // if(readFromFile == null) // { // //TODO: it's entirely possible this is null. // throw new ArgumentNullException(); // } // var readUnExpandoed = (IDictionary)readFromFile; // //if it's present on what was read // //set it on Config // //if it's not found on what was read, or type is different // foreach (var prop in config.GetType().GetProperties()) // { // fieldsFoundThisLevel.Add(prop.Name); // if(readUnExpandoed.TryGetValue(prop.Name, out object? valueFromFile)) //so a configuration file can *set* a value *to null* // { // if(valueFromFile != null) // { // var defaultConfigValue = prop.GetValue(config); // if (defaultConfigValue != null) // { // //expandofy, since we objectified it. // var expandoFromFile = new ExpandoObject(); // var dictionaryFromExpandoFromFile = (IDictionary)expandoFromFile; // foreach (var property in valueFromFile.GetType().GetProperties()) // dictionaryFromExpandoFromFile.Add(property.Name, property.GetValue(valueFromFile)); // expandoFromFile = (ExpandoObject)dictionaryFromExpandoFromFile; // //dive in, repeat // sync(ref defaultConfigValue, ref expandoFromFile); // //we've now Sync'd expandoFromFile and defaultConfigValue // prop.SetValue(readFromFile, expandoFromFile); // prop.SetValue(config, expandoFromFile); // } // } // } // else // { // readFromFile.TryAdd(prop.Name, prop.GetValue(config)); // } // } // } } }