private static void CheckDescribedOrHidden(PropertyInfo property, object instance) { ConfigItemAttribute attribute = property.GetCustomAttribute <ConfigItemAttribute>(); if (string.IsNullOrWhiteSpace(attribute?.Description) && !(attribute?.HiddenFromDocs ?? false)) { ConfigCategoryAttribute categoryLevel = property.DeclaringType?.GetCustomAttribute <ConfigCategoryAttribute>(); if (!(categoryLevel?.HiddenFromDocs ?? false)) { throw new AssertionException( $"Config {instance?.GetType().Name}.{property.Name} has no description and is in the docs."); } } }
public void Generate() { StringBuilder descriptionsBuilder = new StringBuilder(@"Configuration ************* Use '/' as the path separator so the configs can be shared between all platforms supported (Linux, Windows, MacOS). '--config', '--baseDbPath', and '--log' options are available from the command line to select config file, base DB directory prefix and log level respectively. "); StringBuilder exampleBuilder = new StringBuilder(@"Sample configuration (mainnet) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: [ "); List <(Type ConfigType, Type ConfigInterface)> configTypes = new List <(Type, Type)>(); foreach (string assemblyName in _assemblyNames) { Assembly assembly = Assembly.Load(new AssemblyName(assemblyName)); foreach (Type type in assembly.GetTypes().Where(t => typeof(IConfig).IsAssignableFrom(t)).Where(t => !t.IsInterface)) { var configInterface = type.GetInterfaces().Single(i => i != typeof(IConfig)); configTypes.Add((type, configInterface)); } } foreach ((Type configType, Type configInterface) in configTypes.OrderBy(t => t.ConfigType.Name)) { descriptionsBuilder.Append($@"{configType.Name} {string.Empty.PadLeft(configType.Name.Length, '^')} "); ConfigCategoryAttribute categoryAttribute = configInterface.GetCustomAttribute <ConfigCategoryAttribute>(); if (categoryAttribute != null) { descriptionsBuilder.AppendLine($"{categoryAttribute.Description}").AppendLine(); } exampleBuilder.AppendLine(" {"); exampleBuilder.AppendLine($" \"ConfigModule\": \"{configType.Name}\""); exampleBuilder.AppendLine(" \"ConfigItems\": {"); var properties = configType.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo propertyInfo in properties.OrderBy(p => p.Name)) { PropertyInfo interfaceProperty = configInterface.GetProperty(propertyInfo.Name); if (interfaceProperty == null) { Console.WriteLine($"Property {propertyInfo.Name} is missing from interface {configInterface.Name}."); } ConfigItemAttribute attribute = interfaceProperty.GetCustomAttribute <ConfigItemAttribute>(); string defaultValue = attribute == null ? "[MISSING_DOCS]" : attribute.DefaultValue; exampleBuilder.AppendLine($" \"{propertyInfo.Name}\" : {defaultValue},"); if (attribute == null) { descriptionsBuilder.AppendLine($" {propertyInfo.Name}").AppendLine(); continue; } descriptionsBuilder .AppendLine($" {propertyInfo.Name}") .AppendLine($" {attribute.Description}") .AppendLine($" default value: {defaultValue}") .AppendLine(); } if (exampleBuilder.Length > 0 && exampleBuilder[exampleBuilder.Length - 1] == ',') { exampleBuilder.Remove(exampleBuilder.Length - 1, 1); } exampleBuilder.AppendLine(" }"); exampleBuilder.AppendLine(" },"); } exampleBuilder.AppendLine(" ]"); string result = string.Concat(descriptionsBuilder.ToString(), exampleBuilder.ToString()); Console.WriteLine(result); File.WriteAllText("configuration.rst", result); File.WriteAllText("../../../docs/source/configuration.rst", result); }
// POPULATES EVERY CLASS WITH THE CONFIG VALUES. THESE VALUES CAN STILL BE OBTAINED IN A CONVENTIONAL WAY THROUGH FILES internal static void Populate() { foreach (Assembly modDll in cachedModDlls) { foreach (Type type in modDll.GetTypes()) { foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) { object[] attributes = field.GetCustomAttributes(typeof(ConfigValueAttribute), false); ConfigValueAttribute configValue = attributes.Length > 0 ? attributes[0] as ConfigValueAttribute : null; attributes = field.GetCustomAttributes(typeof(ConfigDescriptionAttribute), false); ConfigDescriptionAttribute description = attributes.Length > 0 ? attributes[0] as ConfigDescriptionAttribute : null; attributes = field.GetCustomAttributes(typeof(ConfigCategoryAttribute), false); ConfigCategoryAttribute category = attributes.Length > 0 ? attributes[0] as ConfigCategoryAttribute : null; attributes = field.GetCustomAttributes(typeof(ConfigConverterAttribute), false); ConfigConverterAttribute converter = attributes.Length > 0 ? attributes[0] as ConfigConverterAttribute : null; attributes = field.GetCustomAttributes(typeof(ConfigUIAttribute), false); ConfigUIAttribute uiDesign = attributes.Length > 0 ? attributes[0] as ConfigUIAttribute : null; if (configValue == null) { continue; } ConfigFile file; FileID id = new FileID(modDll, configValue.file == null ? "main.config" : configValue.file + ".config"); if (!files.ContainsKey(id)) { file = new ConfigFile(modDll, configValue.file == null ? "main.config" : configValue.file + ".config"); files.Add(id, file); } else { file = files[id]; } IConfigConverter convert = converter?.converter; if (uiDesign != null) { file.AddDesign(configValue.name, uiDesign, category?.category); } string value = file.Get(configValue.name, convert != null ? convert.ConvertFromValue(configValue.defaultValue) : configValue.defaultValue.ToString(), null, category?.category, description?.description); try { field.SetValue(null, Convert.ChangeType(value, field.FieldType)); } catch (Exception e) { SRML.Console.LogError($"Trying to set value for field '{field.Name}' from config, but config value can't be converted. Default value will be used, however this might mean a field is asking for a diferent type of data then it can contain"); UnityEngine.Debug.LogException(e); field.SetValue(null, configValue.defaultValue); continue; } } } } }