mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-19 15:51:45 +00:00
Start work on config provider type conversion
This commit is contained in:
1 parent
441f302bf9
commit
e90304073c
6 files changed
+107
-43
No files matched your search
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace BepInEx.Configuration
|
||||
{
|
||||
@@ -11,7 +12,7 @@ namespace BepInEx.Configuration
|
||||
/// <inheritdoc />
|
||||
public class ConfigDefinition : IEquatable<ConfigDefinition>
|
||||
{
|
||||
private static readonly char[] _invalidConfigChars = { '=', '\n', '\t', '\\', '"', '\'', '[', ']' };
|
||||
private static readonly char[] _invalidConfigChars = { '=', '\n', '\t', '\\', '"', '\'', '[', ']', '.' };
|
||||
|
||||
/// <summary>
|
||||
/// Create a new definition. Definitions with same section and key are equal.
|
||||
@@ -35,6 +36,8 @@ namespace BepInEx.Configuration
|
||||
/// Name of the setting.
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
|
||||
internal string[] ConfigPath => Section.Split('.').AddItem(Key).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Check if the definitions are the same.
|
||||
@@ -50,12 +53,9 @@ namespace BepInEx.Configuration
|
||||
private static void CheckInvalidConfigChars(string val, string name)
|
||||
{
|
||||
if (val == null) throw new ArgumentNullException(name);
|
||||
if (val != val.Trim())
|
||||
throw new ArgumentException("Cannot use whitespace characters at start or end of section and key names",
|
||||
name);
|
||||
if (val.Any(c => _invalidConfigChars.Contains(c)))
|
||||
throw new
|
||||
ArgumentException(@"Cannot use any of the following characters in section and key names: = \n \t \ "" ' [ ]",
|
||||
ArgumentException(@"Cannot use any of the following characters in section and key names: = \n \t \ "" ' [ ] .",
|
||||
name);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace BepInEx.Configuration
|
||||
{
|
||||
@@ -120,7 +123,7 @@ namespace BepInEx.Configuration
|
||||
/// <summary>
|
||||
/// Get the serialized representation of the value.
|
||||
/// </summary>
|
||||
public string GetSerializedValue() => TomlTypeConverter.ConvertToString(BoxedValue, SettingType);
|
||||
public string GetSerializedValue() => ConfigTypeConverter.ConvertToString(BoxedValue, SettingType);
|
||||
|
||||
/// <summary>
|
||||
/// Set the value by using its serialized form.
|
||||
@@ -129,7 +132,7 @@ namespace BepInEx.Configuration
|
||||
{
|
||||
try
|
||||
{
|
||||
var newValue = TomlTypeConverter.ConvertToValue(value, SettingType);
|
||||
var newValue = ConfigTypeConverter.ConvertToValue(value, SettingType);
|
||||
BoxedValue = newValue;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -164,7 +167,7 @@ namespace BepInEx.Configuration
|
||||
|
||||
writer.WriteLine("# Setting type: " + SettingType.Name);
|
||||
|
||||
writer.WriteLine("# Default value: " + TomlTypeConverter.ConvertToString(DefaultValue, SettingType));
|
||||
writer.WriteLine("# Default value: " + ConfigTypeConverter.ConvertToString(DefaultValue, SettingType));
|
||||
|
||||
if (Description.AcceptableValues != null)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
|
||||
// TODO: Check if IDictionary implementation is reasonable
|
||||
|
||||
@@ -37,7 +39,7 @@ namespace BepInEx.Configuration
|
||||
|
||||
public static ConfigFile CoreConfig { get; } = new(Paths.BepInExConfigPath, true);
|
||||
|
||||
public IConfigurationProvider ConfigurationProvider { get; set; }
|
||||
public IConfigurationProvider ConfigurationProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All config entries inside
|
||||
@@ -313,24 +315,15 @@ namespace BepInEx.Configuration
|
||||
T defaultValue,
|
||||
ConfigDescription configDescription = null)
|
||||
{
|
||||
if (!TomlTypeConverter.CanConvert(typeof(T)))
|
||||
throw new
|
||||
ArgumentException($"Type {typeof(T)} is not supported by the config system. Supported types: {string.Join(", ", TomlTypeConverter.GetSupportedTypes().Select(x => x.Name).ToArray())}");
|
||||
|
||||
lock (_ioLock)
|
||||
{
|
||||
if (Entries.TryGetValue(configDefinition, out var rawEntry))
|
||||
return (ConfigEntry<T>) rawEntry;
|
||||
|
||||
var entry = new ConfigEntry<T>(this, configDefinition, defaultValue, configDescription);
|
||||
|
||||
Entries[configDefinition] = entry;
|
||||
|
||||
if (OrphanedEntries.TryGetValue(configDefinition, out var homelessValue))
|
||||
{
|
||||
entry.SetSerializedValue(homelessValue);
|
||||
OrphanedEntries.Remove(configDefinition);
|
||||
}
|
||||
entry.BoxedValue = GetValue(configDefinition.ConfigPath, typeof(T)) ?? defaultValue;
|
||||
|
||||
if (SaveOnConfigSet)
|
||||
Save();
|
||||
@@ -338,6 +331,64 @@ namespace BepInEx.Configuration
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
private object GetValue(string[] path, Type type)
|
||||
{
|
||||
// We have direct value; simply resolve it normally
|
||||
if (ConfigTypeConverter.CanConvertDirectly(type))
|
||||
{
|
||||
var val = ConfigurationProvider.Get(path);
|
||||
if (val != null)
|
||||
return ConfigTypeConverter.ConvertToValue(val.Value, type);
|
||||
return null;
|
||||
}
|
||||
else if (type.IsArray)
|
||||
{
|
||||
var elType = type.GetElementType();
|
||||
var indexPath = path.AddItem("0").ToArray();
|
||||
var values = new List<object>();
|
||||
for (var i = 0;; i++)
|
||||
{
|
||||
indexPath[indexPath.Length - 1] = i.ToString(CultureInfo.InvariantCulture);
|
||||
var val = GetValue(indexPath, elType);
|
||||
if (val == null)
|
||||
break;
|
||||
values.Add(val);
|
||||
}
|
||||
var arr = Array.CreateInstance(elType, values.Count);
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
arr.SetValue(values[i], i);
|
||||
return arr;
|
||||
}
|
||||
else if (typeof(IList<>).IsAssignableFrom(type))
|
||||
{
|
||||
var res = Activator.CreateInstance(type);
|
||||
var elType = type.GetGenericArguments()[0];
|
||||
var add = MethodInvoker.GetHandler(AccessTools.Method(type, "Add", new[] { elType }));
|
||||
var indexPath = path.AddItem("0").ToArray();
|
||||
for (var i = 0;; i++)
|
||||
{
|
||||
indexPath[indexPath.Length - 1] = i.ToString(CultureInfo.InvariantCulture);
|
||||
var val = GetValue(indexPath, elType);
|
||||
if (val == null)
|
||||
break;
|
||||
add(val);
|
||||
}
|
||||
|
||||
return res;
|
||||
} else if (typeof(IDictionary<,>).IsAssignableFrom(type))
|
||||
{
|
||||
var res = Activator.CreateInstance(type);
|
||||
var gArgs = type.GetGenericArguments();
|
||||
if (gArgs[0] != typeof(string))
|
||||
throw new Exception("Only dictionaries with string keys are supported");
|
||||
var elType = gArgs[1];
|
||||
var add = MethodInvoker.GetHandler(AccessTools.Method(type, "Add", new[] { typeof(string), elType }));
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new setting. The setting is saved to drive and loaded automatically.
|
||||
|
||||
+28
-18
@@ -10,21 +10,21 @@ namespace BepInEx.Configuration
|
||||
/// <summary>
|
||||
/// Serializer/deserializer used by the config system.
|
||||
/// </summary>
|
||||
public static class TomlTypeConverter
|
||||
public static class ConfigTypeConverter
|
||||
{
|
||||
// Don't put anything from UnityEngine here or it will break preloader, use LazyTomlConverterLoader instead
|
||||
private static Dictionary<Type, TypeConverter> TypeConverters { get; } = new()
|
||||
private static Dictionary<Type, TypeConverter> DirectConverters { get; } = new()
|
||||
{
|
||||
[typeof(string)] = new TypeConverter
|
||||
{
|
||||
ConvertToString = (obj, type) => Escape((string) obj),
|
||||
ConvertToObject = (str, type) =>
|
||||
{
|
||||
// Check if the string is a file path with unescaped \ path separators (e.g. D:\test and not D:\\test)
|
||||
if (Regex.IsMatch(str, @"^""?\w:\\(?!\\)(?!.+\\\\)"))
|
||||
return str;
|
||||
return Unescape(str);
|
||||
}
|
||||
ConvertToString = (obj, type) => obj.ToString(), //Escape((string) obj),
|
||||
ConvertToObject = (str, type) => str,
|
||||
// {
|
||||
// // Check if the string is a file path with unescaped \ path separators (e.g. D:\test and not D:\\test)
|
||||
// if (Regex.IsMatch(str, @"^""?\w:\\(?!\\)(?!.+\\\\)"))
|
||||
// return str;
|
||||
// return Unescape(str);
|
||||
// }
|
||||
},
|
||||
[typeof(bool)] = new TypeConverter
|
||||
{
|
||||
@@ -98,15 +98,25 @@ namespace BepInEx.Configuration
|
||||
ConvertToObject = (str, type) => decimal.Parse(str, NumberFormatInfo.InvariantInfo)
|
||||
},
|
||||
|
||||
//enums are special
|
||||
// Special direct converters
|
||||
|
||||
[typeof(Enum)] = new TypeConverter
|
||||
{
|
||||
ConvertToString = (obj, type) => obj.ToString(),
|
||||
ConvertToObject = (str, type) => Enum.Parse(type, str, true)
|
||||
},
|
||||
[typeof(DateTime)] = new TypeConverter
|
||||
{
|
||||
ConvertToString = (obj, type) => ((DateTime) obj).ToString("O"),
|
||||
ConvertToObject = (str, type) => DateTime.ParseExact(str, "O", CultureInfo.InvariantCulture)
|
||||
},
|
||||
[typeof(DateTimeOffset)] = new TypeConverter
|
||||
{
|
||||
ConvertToString = (obj, type) => ((DateTimeOffset) obj).ToString("O"),
|
||||
ConvertToObject = (str, type) => DateTimeOffset.ParseExact(str, "O", CultureInfo.InvariantCulture)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert object of a given type to a string using available converters.
|
||||
/// </summary>
|
||||
@@ -145,9 +155,9 @@ namespace BepInEx.Configuration
|
||||
throw new ArgumentNullException(nameof(valueType));
|
||||
|
||||
if (valueType.IsEnum)
|
||||
return TypeConverters[typeof(Enum)];
|
||||
return DirectConverters[typeof(Enum)];
|
||||
|
||||
TypeConverters.TryGetValue(valueType, out var result);
|
||||
DirectConverters.TryGetValue(valueType, out var result);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -160,25 +170,25 @@ namespace BepInEx.Configuration
|
||||
{
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
if (converter == null) throw new ArgumentNullException(nameof(converter));
|
||||
if (CanConvert(type))
|
||||
if (CanConvertDirectly(type))
|
||||
{
|
||||
Logger.LogWarning("Tried to add a TomlConverter when one already exists for type " + type.FullName);
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeConverters.Add(type, converter);
|
||||
DirectConverters.Add(type, converter);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given type can be converted to and from string.
|
||||
/// </summary>
|
||||
public static bool CanConvert(Type type) => GetConverter(type) != null;
|
||||
public static bool CanConvertDirectly(Type type) => GetConverter(type) != null;
|
||||
|
||||
/// <summary>
|
||||
/// Give a list of types with registered converters.
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> GetSupportedTypes() => TypeConverters.Keys;
|
||||
public static IEnumerable<Type> GetSupportedTypes() => DirectConverters.Keys;
|
||||
|
||||
private static string Escape(this string txt)
|
||||
{
|
||||
@@ -20,7 +20,7 @@ namespace BepInEx.Configuration
|
||||
{
|
||||
static KeyboardShortcut()
|
||||
{
|
||||
TomlTypeConverter.AddConverter(
|
||||
ConfigTypeConverter.AddConverter(
|
||||
typeof(KeyboardShortcut),
|
||||
new TypeConverter
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace BepInEx.Unity
|
||||
}
|
||||
};
|
||||
|
||||
TomlTypeConverter.AddConverter(typeof(Color), colorConverter);
|
||||
ConfigTypeConverter.AddConverter(typeof(Color), colorConverter);
|
||||
|
||||
var jsonConverter = new TypeConverter
|
||||
{
|
||||
@@ -32,10 +32,10 @@ namespace BepInEx.Unity
|
||||
ConvertToObject = (str, type) => JsonUtility.FromJson(type: type, json: str)
|
||||
};
|
||||
|
||||
TomlTypeConverter.AddConverter(typeof(Vector2), jsonConverter);
|
||||
TomlTypeConverter.AddConverter(typeof(Vector3), jsonConverter);
|
||||
TomlTypeConverter.AddConverter(typeof(Vector4), jsonConverter);
|
||||
TomlTypeConverter.AddConverter(typeof(Quaternion), jsonConverter);
|
||||
ConfigTypeConverter.AddConverter(typeof(Vector2), jsonConverter);
|
||||
ConfigTypeConverter.AddConverter(typeof(Vector3), jsonConverter);
|
||||
ConfigTypeConverter.AddConverter(typeof(Vector4), jsonConverter);
|
||||
ConfigTypeConverter.AddConverter(typeof(Quaternion), jsonConverter);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user