2025-10-19 11:51:28 +02:00
using System.Globalization ;
using System.Linq.Expressions ;
using AIStudio.Settings.DataModel ;
using Lua ;
namespace AIStudio.Settings ;
public static partial class ManagedConfiguration
{
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
/// <param name="_">An unused parameter to help with type inference for enum types. You might ignore it when calling the method.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TValue">The type of the property within the configuration class.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass , TValue >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , TValue >> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun ,
TValue ? _ = default )
where TValue : Enum
{
//
// Handle configured enum values
//
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
var successful = false ;
var configuredValue = configMeta . Default ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 1 -- try to read the Lua value out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredEnumValue ))
{
// Step 2 -- try to read the Lua value as a string:
if ( configuredEnumValue . TryRead < string >( out var configuredEnumText ))
{
// Step 3 -- try to parse the string as the enum type:
2026-09-27 16:26:52 +02:00
if ( EnumNames . TryParse ( typeof ( TValue ), configuredEnumText , out var configuredEnum ))
2025-10-19 11:51:28 +02:00
{
configuredValue = ( TValue ) configuredEnum ;
successful = true ;
}
}
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
if ( dryRun )
return successful ;
2026-03-21 18:05:06 +01:00
var settingName = SettingName ( propertyExpression );
var managedMode = ReadManagedConfigurationMode ( propertyExpression , settings );
return HandleParsedScalarValue ( configPluginId , dryRun , successful , configMeta , configuredValue , managedMode , settingName );
2025-10-19 11:51:28 +02:00
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for ISpanParsable types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
/// <param name="_">An unused parameter to help with type inference. You might ignore it when calling the method.</param>
2026-08-11 20:14:21 +02:00
/// <param name="validator">An optional validator for rejecting parsed values outside the setting's supported range.</param>
2025-10-19 11:51:28 +02:00
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TValue">The type of the property within the configuration class.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass , TValue >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , TValue >> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun ,
2026-08-11 20:14:21 +02:00
ISpanParsable < TValue >? _ = null ,
Func < TValue , bool >? validator = null )
2025-10-19 11:51:28 +02:00
where TValue : struct , ISpanParsable < TValue >
{
//
// Handle configured ISpanParsable values
//
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
var successful = false ;
var configuredValue = configMeta . Default ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 1 -- try to read the Lua value out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaValue ))
{
// Step 2a -- try to read the Lua value as a string:
if ( configuredLuaValue . Type is LuaValueType . String && configuredLuaValue . TryRead < string >( out var configuredLuaValueText ))
{
// Step 3 -- try to parse the string as the target type:
2026-08-11 20:14:21 +02:00
if ( TValue . TryParse ( configuredLuaValueText , CultureInfo . InvariantCulture , out var configuredParsedValue )
&& ( validator ?. Invoke ( configuredParsedValue ) ?? true ))
2025-10-19 11:51:28 +02:00
{
configuredValue = configuredParsedValue ;
successful = true ;
}
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 2b -- try to read the Lua value:
2026-08-11 20:14:21 +02:00
if ( configuredLuaValue . TryRead < TValue >( out var configuredLuaValueInstance )
&& ( validator ?. Invoke ( configuredLuaValueInstance ) ?? true ))
2025-10-19 11:51:28 +02:00
{
configuredValue = configuredLuaValueInstance ;
successful = true ;
}
}
if ( dryRun )
return successful ;
2026-03-21 18:05:06 +01:00
var settingName = SettingName ( propertyExpression );
var managedMode = ReadManagedConfigurationMode ( propertyExpression , settings );
return HandleParsedScalarValue ( configPluginId , dryRun , successful , configMeta , configuredValue , managedMode , settingName );
2025-10-19 11:51:28 +02:00
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string values.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
2026-10-25 18:52:39 +01:00
/// <param name="convert">Turns the configured text into the value to store, e.g., a path relative to the plugin into a full one; null rejects the text. Without it, the text is stored as it is.</param>
2025-10-19 11:51:28 +02:00
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , string >> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
2026-10-25 18:52:39 +01:00
bool dryRun ,
Func < string , string? >? convert = null )
2025-12-04 15:37:13 +01:00
{
2026-10-25 18:52:39 +01:00
return TryProcessConfiguration ( configSelection , propertyExpression , string . Empty , configPluginId , settings , dryRun , convert );
2025-12-04 15:37:13 +01:00
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string values.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configuredType">Parameter type of the configuration entry.</param>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
2026-10-25 18:52:39 +01:00
/// <param name="convert">For plain strings: turns the configured text into the value to store; null rejects the text. Without it, the text is stored as it is.</param>
2025-12-04 15:37:13 +01:00
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TDataType">The data type of the configured value.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass , TDataType >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , string >> propertyExpression ,
TDataType configuredType ,
Guid configPluginId ,
LuaTable settings ,
2026-10-25 18:52:39 +01:00
bool dryRun ,
Func < string , string? >? convert = null )
2025-10-19 11:51:28 +02:00
{
//
// Handle configured string values
//
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
var successful = false ;
var configuredValue = configMeta . Default ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 1 -- try to read the Lua value out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredTextValue ))
{
// Step 2 -- try to read the Lua value as a string:
if ( configuredTextValue . TryRead < string >( out var configuredText ))
{
2025-12-04 15:37:13 +01:00
switch ( configuredType )
{
// Case: the read string is a Guid:
case Guid :
successful = Guid . TryParse ( configuredText , out var id );
configuredValue = successful ? id . ToString (). ToLowerInvariant () : configuredText ;
break ;
2026-06-21 11:52:02 +02:00
2025-12-04 15:37:13 +01:00
// Case: the read string is just a string:
case string :
2026-10-25 18:52:39 +01:00
var convertedText = convert is null ? configuredText : convert ( configuredText );
successful = convertedText is not null ;
configuredValue = convertedText ?? configuredText ;
2025-12-04 15:37:13 +01:00
break ;
}
2025-10-19 11:51:28 +02:00
}
}
2026-06-21 11:52:02 +02:00
2026-03-21 18:05:06 +01:00
var settingName = SettingName ( propertyExpression );
var managedMode = ReadManagedConfigurationMode ( propertyExpression , settings );
return HandleParsedScalarValue ( configPluginId , dryRun , successful , configMeta , configuredValue , managedMode , settingName );
2025-10-19 11:51:28 +02:00
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for ISpanParsable list types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <param name="_">An unused parameter to help with type inference for ISpanParsable types. You might ignore it when calling the method.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the list
/// elements, which must implement ISpanParsable.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
// ReSharper disable MethodOverloadWithOptionalParameter
public static bool TryProcessConfiguration < TClass , TValue >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , IList < TValue >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun ,
ISpanParsable < TValue >? _ = null )
where TValue : ISpanParsable < TValue >
{
//
// Handle configured ISpanParsable lists
//
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
var successful = false ;
var configuredValue = configMeta . Default ;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
// Determine the length of the Lua table and prepare a list to hold the parsed values:
var len = valueTable . ArrayLength ;
var list = new List < TValue >( len );
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Iterate over each entry in the Lua table:
for ( var index = 1 ; index <= len ; index ++)
{
// Retrieve the Lua value at the current index:
var value = valueTable [ index ];
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 2a -- try to read the Lua value as a string:
if ( value . Type is LuaValueType . String && value . TryRead < string >( out var configuredLuaValueText ))
{
// Step 3 -- try to parse the string as the target type:
if ( TValue . TryParse ( configuredLuaValueText , CultureInfo . InvariantCulture , out var configuredParsedValue ))
list . Add ( configuredParsedValue );
}
// Step 2b -- try to read the Lua value:
if ( value . TryRead < TValue >( out var configuredLuaValueInstance ))
list . Add ( configuredLuaValueInstance );
}
configuredValue = list ;
successful = true ;
}
if ( dryRun )
return successful ;
return HandleParsedValue ( configPluginId , dryRun , successful , configMeta , configuredValue );
}
// ReSharper restore MethodOverloadWithOptionalParameter
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum list types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the list
/// elements, which must be an enum.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass , TValue >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , IList < TValue >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun )
where TValue : Enum
{
//
// Handle configured enum lists
//
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
var successful = false ;
var configuredValue = configMeta . Default ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
// Determine the length of the Lua table and prepare a list to hold the parsed values:
var len = valueTable . ArrayLength ;
var list = new List < TValue >( len );
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Iterate over each entry in the Lua table:
for ( var index = 1 ; index <= len ; index ++)
{
// Retrieve the Lua value at the current index:
var value = valueTable [ index ];
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 2 -- try to read the Lua value as a string:
if ( value . Type is LuaValueType . String && value . TryRead < string >( out var configuredLuaValueText ))
{
// Step 3 -- try to parse the string as the target type:
2026-09-27 16:26:52 +02:00
if ( EnumNames . TryParse ( typeof ( TValue ), configuredLuaValueText , out var configuredEnum ))
2025-10-19 11:51:28 +02:00
list . Add (( TValue ) configuredEnum );
}
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
configuredValue = list ;
successful = true ;
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
if ( dryRun )
return successful ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
return HandleParsedValue ( configPluginId , dryRun , successful , configMeta , configuredValue );
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string list types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , IList < string >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun )
{
//
// Handle configured string lists
//
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
var successful = false ;
var configuredValue = configMeta . Default ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
// Determine the length of the Lua table and prepare a list to hold the parsed values:
var len = valueTable . ArrayLength ;
var list = new List < string >( len );
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Iterate over each entry in the Lua table:
for ( var index = 1 ; index <= len ; index ++)
{
// Retrieve the Lua value at the current index:
var value = valueTable [ index ];
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 2 -- try to read the Lua value as a string:
if ( value . Type is LuaValueType . String && value . TryRead < string >( out var configuredLuaValueText ))
list . Add ( configuredLuaValueText );
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
configuredValue = list ;
successful = true ;
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
if ( dryRun )
return successful ;
2026-06-21 11:52:02 +02:00
2026-07-05 14:46:31 +02:00
var settingName = SettingName ( propertyExpression );
var managedMode = ReadManagedConfigurationMode ( propertyExpression , settings );
return HandleParsedScalarValue ( configPluginId , dryRun , successful , configMeta , configuredValue , managedMode , settingName );
2025-10-19 11:51:28 +02:00
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for ISpanParsable set types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <param name="_">An unused parameter to help with type inference for ISpanParsable types. You might ignore it when calling the method.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the set
/// elements, which must implement ISpanParsable.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
// ReSharper disable MethodOverloadWithOptionalParameter
public static bool TryProcessConfiguration < TClass , TValue >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , ISet < TValue >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun ,
ISpanParsable < TValue >? _ = null )
where TValue : ISpanParsable < TValue >
{
//
// Handle configured ISpanParsable sets
//
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
var successful = false ;
var configuredValue = configMeta . Default ;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable . ArrayLength ;
var set = new HashSet < TValue >( len );
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Iterate over each entry in the Lua table:
for ( var index = 1 ; index <= len ; index ++)
{
// Retrieve the Lua value at the current index:
var value = valueTable [ index ];
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 2a -- try to read the Lua value as a string:
if ( value . Type is LuaValueType . String && value . TryRead < string >( out var configuredLuaValueText ))
{
// Step 3 -- try to parse the string as the target type:
if ( TValue . TryParse ( configuredLuaValueText , CultureInfo . InvariantCulture , out var configuredParsedValue ))
set . Add ( configuredParsedValue );
}
// Step 2b -- try to read the Lua value:
if ( value . TryRead < TValue >( out var configuredLuaValueInstance ))
set . Add ( configuredLuaValueInstance );
}
configuredValue = set ;
successful = true ;
}
if ( dryRun )
return successful ;
return HandleParsedValue ( configPluginId , dryRun , successful , configMeta , configuredValue );
}
// ReSharper restore MethodOverloadWithOptionalParameter
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum set types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the set
/// elements, which must be an enum.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass , TValue >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , ISet < TValue >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun )
where TValue : Enum
{
//
// Handle configured enum sets
//
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
var successful = false ;
var configuredValue = configMeta . Default ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable . ArrayLength ;
var set = new HashSet < TValue >( len );
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Iterate over each entry in the Lua table:
for ( var index = 1 ; index <= len ; index ++)
{
// Retrieve the Lua value at the current index:
var value = valueTable [ index ];
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 2 -- try to read the Lua value as a string:
if ( value . Type is LuaValueType . String && value . TryRead < string >( out var configuredLuaValueText ))
{
// Step 3 -- try to parse the string as the target type:
2026-09-27 16:26:52 +02:00
if ( EnumNames . TryParse ( typeof ( TValue ), configuredLuaValueText , out var configuredEnum ))
2025-10-19 11:51:28 +02:00
set . Add (( TValue ) configuredEnum );
2026-10-22 18:57:12 +02:00
//
// Dropped, but not quietly: such a set may prohibit something, and a typo would
// otherwise leave it allowed without anybody noticing. A dry run comes before
// the actual one, so only the actual one reports it:
//
else if (! dryRun )
Log . LogWarning ( $"The configuration plugin '{configPluginId}' names '{configuredLuaValueText}' in the setting '{configMeta.SettingName}', which is no valid value. Ignoring it." );
2025-10-19 11:51:28 +02:00
}
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
configuredValue = set ;
successful = true ;
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
if ( dryRun )
return successful ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
return HandleParsedValue ( configPluginId , dryRun , successful , configMeta , configuredValue );
}
2026-02-20 12:40:38 +01:00
/// <summary>
/// Attempts to process additive plugin contributions for enum set settings from a Lua table.
/// The contributed values are merged into the existing set, and the setting remains unlocked
/// so users can add additional values.
/// </summary>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the set
/// elements, which must be an enum.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfigurationWithPluginContribution < TClass , TValue >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , ISet < TValue >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun )
where TValue : Enum
{
//
// Handle configured enum sets (additive merge)
//
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
var successful = false ;
var configuredValue = new HashSet < TValue >();
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable . ArrayLength ;
var set = new HashSet < TValue >( len );
2026-06-21 11:52:02 +02:00
2026-02-20 12:40:38 +01:00
// Iterate over each entry in the Lua table:
for ( var index = 1 ; index <= len ; index ++)
{
// Retrieve the Lua value at the current index:
var value = valueTable [ index ];
2026-06-21 11:52:02 +02:00
2026-02-20 12:40:38 +01:00
// Step 2 -- try to read the Lua value as a string:
if ( value . Type is LuaValueType . String && value . TryRead < string >( out var configuredLuaValueText ))
{
// Step 3 -- try to parse the string as the target type:
2026-09-27 16:26:52 +02:00
if ( EnumNames . TryParse ( typeof ( TValue ), configuredLuaValueText , out var configuredEnum ))
2026-02-20 12:40:38 +01:00
set . Add (( TValue ) configuredEnum );
}
}
configuredValue = set ;
successful = true ;
}
if ( dryRun )
return successful ;
2026-08-08 18:35:46 +02:00
//
// Contributions need no protection against a takeover: every configuration plugin has its
// own contribution, so no plugin can replace or drop the contribution of another one. This
// is also why a local configuration plugin may contribute next to one of an organization.
//
2026-02-20 12:40:38 +01:00
if ( successful )
{
2026-06-10 21:01:27 +02:00
var configInstance = configSelection . Compile (). Invoke ( SettingsManagerAccess . ConfigurationData );
2026-02-20 12:40:38 +01:00
var currentValue = propertyExpression . Compile (). Invoke ( configInstance );
var merged = new HashSet < TValue >( currentValue );
merged . UnionWith ( configuredValue );
configMeta . SetValue ( merged );
configMeta . SetPluginContribution ( new HashSet < TValue >( configuredValue ), configPluginId );
}
2026-08-08 18:35:46 +02:00
else
configMeta . RemovePluginContribution ( configPluginId );
2026-02-20 12:40:38 +01:00
if ( configMeta . IsLocked && configMeta . LockedByConfigPluginId == configPluginId )
configMeta . UnlockConfiguration ();
return successful ;
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string set types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , ISet < string >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun )
{
//
// Handle configured string sets
//
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
var successful = false ;
var configuredValue = configMeta . Default ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable . ArrayLength ;
var set = new HashSet < string >( len );
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Iterate over each entry in the Lua table:
for ( var index = 1 ; index <= len ; index ++)
{
// Retrieve the Lua value at the current index:
var value = valueTable [ index ];
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 2 -- try to read the Lua value as a string:
if ( value . Type is LuaValueType . String && value . TryRead < string >( out var configuredLuaValueText ))
set . Add ( configuredLuaValueText );
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
configuredValue = set ;
successful = true ;
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
if ( dryRun )
return successful ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
return HandleParsedValue ( configPluginId , dryRun , successful , configMeta , configuredValue );
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string dictionary types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
/// The setting's value is set to the configured value.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , IDictionary < string , string >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun )
{
//
// Handle configured string dictionaries (both keys and values are strings)
//
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
var successful = false ;
2026-09-04 15:48:07 +02:00
var configuredValue = CloneStringDictionary ( configMeta . Default );
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
// Determine the length of the Lua table and prepare a dictionary to hold the parsed key-value pairs.
// Instead of using ArrayLength, we use HashMapCount to get the number of key-value pairs:
var len = valueTable . HashMapCount ;
if ( len > 0 )
configuredValue . Clear ();
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
// Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
// there are no more pairs:
var previousKey = LuaValue . Nil ;
while ( valueTable . TryGetNext ( previousKey , out var pair ))
{
// Update the previous key for the next iteration:
previousKey = pair . Key ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// Try to read both the key and the value as strings:
var hadKey = pair . Key . TryRead < string >( out var key );
var hadValue = pair . Value . TryRead < string >( out var value );
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
// If both key and value were read successfully, add them to the dictionary:
if ( hadKey && hadValue )
configuredValue [ key ] = value ;
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
successful = true ;
}
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
if ( dryRun )
return successful ;
2026-09-04 15:48:07 +02:00
var settingName = SettingName ( propertyExpression );
var managedMode = ReadManagedConfigurationMode ( propertyExpression , settings );
return HandleParsedDictionaryValue ( configPluginId , dryRun , successful , configMeta , configuredValue , managedMode , settingName );
2025-10-19 11:51:28 +02:00
}
2026-06-21 11:52:02 +02:00
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum dictionary types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it applies the configured managed state to the provided configuration plugin ID.
/// The setting's value is set to the configured value when locked or when the editable default should apply.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TKey">The enum type of the dictionary keys.</typeparam>
/// <typeparam name="TValue">The enum type of the dictionary values.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass , TKey , TValue >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , Dictionary < TKey , TValue >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun )
where TKey : struct , Enum
where TValue : struct , Enum
{
//
// Handle configured enum dictionaries (Lua keys and values are strings)
//
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
var successful = false ;
var configuredValue = new Dictionary < TKey , TValue >( configMeta . Default );
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
configuredValue . Clear ();
// In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
// Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
// there are no more pairs:
var previousKey = LuaValue . Nil ;
while ( valueTable . TryGetNext ( previousKey , out var pair ))
{
// Update the previous key for the next iteration:
previousKey = pair . Key ;
// Try to read both the key and the value as strings:
var hadKey = pair . Key . TryRead < string >( out var keyText );
var hadValue = pair . Value . TryRead < string >( out var valueText );
// If both key and value were read successfully, parse and add them to the dictionary:
if ( hadKey
&& hadValue
2026-09-27 16:26:52 +02:00
&& EnumNames . TryParse < TKey >( keyText , out var key )
&& EnumNames . TryParse < TValue >( valueText , out var value ))
2026-06-21 11:52:02 +02:00
configuredValue [ key ] = value ;
}
successful = true ;
}
if ( dryRun )
return successful ;
var settingName = SettingName ( propertyExpression );
var managedMode = ReadManagedConfigurationMode ( propertyExpression , settings );
return HandleParsedScalarValue ( configPluginId , dryRun , successful , configMeta , configuredValue , managedMode , settingName );
}
2026-10-25 18:52:39 +01:00
/// <summary>
/// Attempts to process the configuration settings from a Lua table for dictionaries which map enum keys to texts.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it applies the configured managed state to the provided configuration plugin ID.
/// The setting's value is set to the configured value when locked or when the editable default should apply.
/// A key which names no member of the enum is passed over, as with the enum dictionaries.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TKey">The enum type of the dictionary keys.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass , TKey >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , Dictionary < TKey , string >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun )
where TKey : struct , Enum
{
//
// Handle configured dictionaries of enum keys and texts (Lua keys and values are strings)
//
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
var successful = false ;
var configuredValue = new Dictionary < TKey , string >( configMeta . Default );
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
configuredValue . Clear ();
// In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
// Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
// there are no more pairs:
var previousKey = LuaValue . Nil ;
while ( valueTable . TryGetNext ( previousKey , out var pair ))
{
// Update the previous key for the next iteration:
previousKey = pair . Key ;
// Try to read both the key and the value as strings:
var hadKey = pair . Key . TryRead < string >( out var keyText );
var hadValue = pair . Value . TryRead < string >( out var valueText );
// If both were read successfully and the key names a member of the enum, add them to the dictionary:
if ( hadKey && hadValue && EnumNames . TryParse < TKey >( keyText , out var key ))
configuredValue [ key ] = valueText ;
}
successful = true ;
}
if ( dryRun )
return successful ;
var settingName = SettingName ( propertyExpression );
var managedMode = ReadManagedConfigurationMode ( propertyExpression , settings );
return HandleParsedScalarValue ( configPluginId , dryRun , successful , configMeta , configuredValue , managedMode , settingName );
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for dictionaries which map numbers to enum values.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it applies the configured managed state to the provided configuration plugin ID.
/// The setting's value is set to the configured value when locked or when the editable default should apply.
/// The numbers count from 1, like the slides of a presentation. An entry whose key is no such number or
/// whose value names no member of the enum is passed over with a warning, since the plugin's author
/// would otherwise never learn why the entry has no effect.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TValue">The enum type of the dictionary values.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration < TClass , TValue >(
Expression < Func < Data , TClass >> configSelection ,
Expression < Func < TClass , Dictionary < int , TValue >>> propertyExpression ,
Guid configPluginId ,
LuaTable settings ,
bool dryRun )
where TValue : struct , Enum
{
//
// Handle configured dictionaries of numbers and enum values (Lua keys are numbers, values are strings)
//
// Check if that configuration was registered:
if (! TryGet ( configSelection , propertyExpression , out var configMeta ))
return false ;
var successful = false ;
var configuredValue = new Dictionary < int , TValue >( configMeta . Default );
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if ( settings . TryGetValue ( SettingsManager . ToSettingName ( propertyExpression ), out var configuredLuaList ) &&
configuredLuaList . Type is LuaValueType . Table &&
configuredLuaList . TryRead < LuaTable >( out var valueTable ))
{
configuredValue . Clear ();
// In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
// Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
// there are no more pairs:
var previousKey = LuaValue . Nil ;
while ( valueTable . TryGetNext ( previousKey , out var pair ))
{
// Update the previous key for the next iteration:
previousKey = pair . Key ;
// Try to read the key as a number and the value as a string:
var hadKey = pair . Key . TryRead < int >( out var key ) && pair . Key . Type is LuaValueType . Number && key > 0 ;
var hadValue = pair . Value . TryRead < string >( out var valueText );
// If both were read successfully and the value names a member of the enum, add them to the dictionary:
if ( hadKey && hadValue && EnumNames . TryParse < TValue >( valueText , out var value ))
configuredValue [ key ] = value ;
// A dry run comes before the actual one, so only the actual one reports the entry:
else if (! dryRun )
Log . LogWarning ( $"The configuration plugin '{configPluginId}' has the entry '{pair.Key}' = '{pair.Value}' in the setting '{configMeta.SettingName}', which is no valid entry. The keys are numbers from 1, and the values are names of options. Ignoring it." );
}
successful = true ;
}
if ( dryRun )
return successful ;
var settingName = SettingName ( propertyExpression );
var managedMode = ReadManagedConfigurationMode ( propertyExpression , settings );
return HandleParsedScalarValue ( configPluginId , dryRun , successful , configMeta , configuredValue , managedMode , settingName );
}
2025-10-19 11:51:28 +02:00
/// <summary>
/// Handles the parsed configuration value based on whether the parsing was successful and whether it's a dry run.
/// </summary>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="dryRun">When true, no changes will be applied.</param>
/// <param name="successful">Indicates whether the configuration value was successfully parsed.</param>
/// <param name="configMeta">The configuration metadata.</param>
/// <param name="configuredValue">>The parsed configuration value.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TValue">The type of the configuration property value.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
private static bool HandleParsedValue < TClass , TValue >(
Guid configPluginId ,
bool dryRun ,
bool successful ,
ConfigMeta < TClass , TValue > configMeta ,
TValue configuredValue )
{
if ( dryRun )
return successful ;
2026-06-21 11:52:02 +02:00
2026-08-08 18:35:46 +02:00
// The setting might belong to the IT department of an organization. In that case, no local
// configuration plugin may touch it, no matter what it declares:
if (! MayManageSetting ( configPluginId , configMeta ))
return false ;
2026-08-09 20:47:30 +02:00
//
// Remember the value the user had chosen before any configuration plugin took this setting
// over. Once no plugin manages it anymore, we hand that value back to the user:
//
if ( successful )
configMeta . CaptureUserValueSnapshot ();
2025-10-19 11:51:28 +02:00
switch ( successful )
{
case true :
//
// Case: the setting was configured, and we could read the value successfully.
//
2026-06-21 11:52:02 +02:00
2026-02-20 12:40:38 +01:00
// Set the configured value and lock the configuration:
2025-10-19 11:51:28 +02:00
configMeta . SetValue ( configuredValue );
2026-02-20 12:40:38 +01:00
configMeta . LockConfiguration ( configPluginId );
2025-10-19 11:51:28 +02:00
break ;
2026-02-20 12:40:38 +01:00
case false when configMeta . IsLocked && configMeta . LockedByConfigPluginId == configPluginId :
2025-10-19 11:51:28 +02:00
//
// Case: the setting was configured previously, but we could not read the value successfully.
// This happens when the setting was removed from the configuration plugin. We handle that
// case only when the setting was locked and managed by the same configuration plugin.
//
// The other case, when the setting was locked and managed by a different configuration plugin,
2026-08-06 20:59:53 +02:00
// is handled by the CleanupLeftOverManagedConfigurations method, which checks if the configuration
// plugin is still available. If it is not available, it resets the locked state of the
2025-10-19 11:51:28 +02:00
// configuration setting, allowing it to be reconfigured by a different plugin or left unchanged.
//
2026-02-20 12:40:38 +01:00
configMeta . ResetLockedConfiguration ();
2025-10-19 11:51:28 +02:00
break ;
2026-06-21 11:52:02 +02:00
2025-10-19 11:51:28 +02:00
case false :
//
// Case: the setting was not configured, or we could not read the value successfully.
// We do not change the setting, and it remains at whatever value it had before.
//
break ;
}
return successful ;
}
2026-03-21 18:05:06 +01:00
private static bool HandleParsedScalarValue < TClass , TValue >(
Guid configPluginId ,
bool dryRun ,
bool successful ,
ConfigMeta < TClass , TValue > configMeta ,
TValue configuredValue ,
ManagedConfigurationMode managedMode ,
string settingName )
{
if ( dryRun )
return successful ;
2026-08-08 18:35:46 +02:00
// The setting might belong to the IT department of an organization. In that case, no local
// configuration plugin may touch it, no matter what it declares:
if (! MayManageSetting ( configPluginId , configMeta ))
return false ;
2026-08-09 20:47:30 +02:00
//
// Remember the value the user had chosen before any configuration plugin took this setting
// over. Once no plugin manages it anymore, we hand that value back to the user. This has to
// happen before the managed state below changes, because only an unmanaged setting holds a
// value which belongs to the user:
//
if ( successful )
configMeta . CaptureUserValueSnapshot ();
2026-03-21 18:05:06 +01:00
switch ( successful )
{
case true when managedMode is ManagedConfigurationMode . LOCKED :
ClearEditableDefaultState ( settingName );
configMeta . ClearEditableDefaultConfiguration ();
configMeta . SetValue ( configuredValue );
configMeta . LockConfiguration ( configPluginId );
break ;
case true when managedMode is ManagedConfigurationMode . EDITABLE_DEFAULT :
var currentValueSerialized = SerializeManagedScalarValue ( configMeta . GetValue ());
var configuredValueSerialized = SerializeManagedScalarValue ( configuredValue );
string lastAppliedValue ;
if (! TryGetEditableDefaultState ( settingName , out var editableDefaultState ))
{
configMeta . SetValue ( configuredValue );
lastAppliedValue = configuredValueSerialized ;
}
else
{
lastAppliedValue = editableDefaultState . LastAppliedValue ;
if ( string . Equals ( currentValueSerialized , lastAppliedValue , StringComparison . Ordinal ))
{
configMeta . SetValue ( configuredValue );
lastAppliedValue = configuredValueSerialized ;
}
}
SetEditableDefaultState ( settingName , configPluginId , lastAppliedValue );
configMeta . UnlockConfiguration ();
configMeta . SetEditableDefaultConfiguration ( configPluginId );
break ;
case false when configMeta . IsLocked && configMeta . LockedByConfigPluginId == configPluginId :
configMeta . ResetLockedConfiguration ();
break ;
case false when configMeta . ManagedMode is ManagedConfigurationMode . EDITABLE_DEFAULT
&& TryGetEditableDefaultState ( settingName , out var editableDefaultStateToRemove )
&& editableDefaultStateToRemove . ConfigPluginId == configPluginId :
2026-08-09 20:47:30 +02:00
configMeta . ResetEditableDefaultConfiguration ( HasUserChangedEditableDefault ( configMeta , editableDefaultStateToRemove ));
2026-03-21 18:05:06 +01:00
ClearEditableDefaultState ( settingName );
break ;
}
return successful ;
}
2026-09-04 15:48:07 +02:00
private static bool HandleParsedDictionaryValue < TClass >(
Guid configPluginId ,
bool dryRun ,
bool successful ,
ConfigMeta < TClass , IDictionary < string , string >> configMeta ,
IDictionary < string , string > configuredValue ,
ManagedConfigurationMode managedMode ,
string settingName )
{
if ( dryRun )
return successful ;
switch ( successful )
{
case true when managedMode is ManagedConfigurationMode . LOCKED :
ClearEditableDefaultState ( settingName );
configMeta . ClearEditableDefaultConfiguration ();
configMeta . SetValue ( CloneStringDictionary ( configuredValue ));
configMeta . LockConfiguration ( configPluginId );
break ;
case true when managedMode is ManagedConfigurationMode . EDITABLE_DEFAULT :
var currentValueSerialized = SerializeManagedStringDictionaryValue ( configMeta . GetValue ());
var configuredValueSerialized = SerializeManagedStringDictionaryValue ( configuredValue );
string lastAppliedValue ;
if (! TryGetEditableDefaultState ( settingName , out var editableDefaultState ))
{
configMeta . SetValue ( CloneStringDictionary ( configuredValue ));
lastAppliedValue = configuredValueSerialized ;
}
else
{
lastAppliedValue = editableDefaultState . LastAppliedValue ;
if ( string . Equals ( currentValueSerialized , lastAppliedValue , StringComparison . Ordinal ))
{
configMeta . SetValue ( CloneStringDictionary ( configuredValue ));
lastAppliedValue = configuredValueSerialized ;
}
}
SetEditableDefaultState ( settingName , configPluginId , lastAppliedValue );
configMeta . UnlockConfiguration ();
configMeta . SetEditableDefaultConfiguration ( configPluginId );
break ;
case false when configMeta . IsLocked && configMeta . LockedByConfigPluginId == configPluginId :
configMeta . ResetLockedConfiguration ();
break ;
case false when configMeta . ManagedMode is ManagedConfigurationMode . EDITABLE_DEFAULT
&& TryGetEditableDefaultState ( settingName , out var editableDefaultStateToRemove )
&& editableDefaultStateToRemove . ConfigPluginId == configPluginId :
configMeta . ClearEditableDefaultConfiguration ();
ClearEditableDefaultState ( settingName );
break ;
}
return successful ;
}
2026-03-21 18:05:06 +01:00
private static ManagedConfigurationMode ReadManagedConfigurationMode < TClass , TValue >(
Expression < Func < TClass , TValue >> propertyExpression ,
LuaTable settings )
{
var allowUserOverrideSettingName = $"{SettingsManager.ToSettingName(propertyExpression)}.AllowUserOverride" ;
if (! settings . TryGetValue ( allowUserOverrideSettingName , out var allowUserOverrideValue ))
return ManagedConfigurationMode . LOCKED ;
if ( allowUserOverrideValue . TryRead < bool >( out var allowUserOverride ))
return allowUserOverride ? ManagedConfigurationMode . EDITABLE_DEFAULT : ManagedConfigurationMode . LOCKED ;
if ( allowUserOverrideValue . TryRead < string >( out var allowUserOverrideText ) && bool . TryParse ( allowUserOverrideText , out allowUserOverride ))
return allowUserOverride ? ManagedConfigurationMode . EDITABLE_DEFAULT : ManagedConfigurationMode . LOCKED ;
return ManagedConfigurationMode . LOCKED ;
}
2026-08-09 20:47:30 +02:00
internal static string SerializeManagedScalarValue < TValue >( TValue value ) => value switch
2026-03-21 18:05:06 +01:00
{
null => string . Empty ,
string text => text ,
2026-06-21 11:52:02 +02:00
System . Collections . IDictionary dictionary => string . Join ( ";" , dictionary . Keys
. Cast < object >()
. OrderBy ( key => key . ToString (), StringComparer . Ordinal )
. Select ( key => $"{key}:{dictionary[key]}" )),
2026-07-05 14:46:31 +02:00
System . Collections . IEnumerable enumerable => string . Join ( ";" , enumerable
. Cast < object >()
. Select ( item => item . ToString () ?? string . Empty )
. Order ( StringComparer . Ordinal )),
2026-03-21 18:05:06 +01:00
IFormattable formattable => formattable . ToString ( null , CultureInfo . InvariantCulture ),
2026-06-21 11:52:02 +02:00
2026-03-21 18:05:06 +01:00
_ => value . ToString () ?? string . Empty ,
};
2026-09-04 15:48:07 +02:00
private static Dictionary < string , string > CloneStringDictionary ( IDictionary < string , string > values ) => new ( values , StringComparer . Ordinal );
private static string SerializeManagedStringDictionaryValue ( IDictionary < string , string > values ) => string . Join (
"\n" ,
values
. OrderBy ( pair => pair . Key , StringComparer . Ordinal )
. Select ( pair => $"{pair.Key}={pair.Value}" ));
}