Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #2663 - No more nested classes #2664

Merged
merged 11 commits into from
May 23, 2023
58 changes: 28 additions & 30 deletions Terminal.Gui/Configuration/AppScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,33 @@

namespace Terminal.Gui {

public static partial class ConfigurationManager {
/// <summary>
/// The <see cref="Scope{T}"/> class for application-defined configuration settings.
/// </summary>
/// <remarks>
/// </remarks>
/// <example>
/// <para>
/// Use the <see cref="SerializableConfigurationProperty"/> attribute to mark properties that should be serialized as part
/// of application-defined configuration settings.
/// </para>
/// <code>
/// public class MyAppSettings {
/// [SerializableConfigurationProperty (Scope = typeof (AppScope))]
/// public static bool? MyProperty { get; set; } = true;
/// }
/// </code>
/// <para>
/// THe resultant Json will look like this:
/// </para>
/// <code>
/// "AppSettings": {
/// "MyAppSettings.MyProperty": true,
/// "UICatalog.ShowStatusBar": true
/// },
/// </code>
/// </example>
[JsonConverter (typeof (ScopeJsonConverter<AppScope>))]
public class AppScope : Scope<AppScope> {
}
/// <summary>
/// The <see cref="Scope{T}"/> class for application-defined configuration settings.
/// </summary>
/// <remarks>
/// </remarks>
/// <example>
/// <para>
/// Use the <see cref="SerializableConfigurationProperty"/> attribute to mark properties that should be serialized as part
/// of application-defined configuration settings.
/// </para>
/// <code>
/// public class MyAppSettings {
/// [SerializableConfigurationProperty (Scope = typeof (AppScope))]
/// public static bool? MyProperty { get; set; } = true;
/// }
/// </code>
/// <para>
/// THe resultant Json will look like this:
/// </para>
/// <code>
/// "AppSettings": {
/// "MyAppSettings.MyProperty": true,
/// "UICatalog.ShowStatusBar": true
/// },
/// </code>
/// </example>
[JsonConverter (typeof (ScopeJsonConverter<AppScope>))]
public class AppScope : Scope<AppScope> {
}
}
97 changes: 97 additions & 0 deletions Terminal.Gui/Configuration/ConfigProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Reflection;
using System.Text.Json.Serialization;

#nullable enable

namespace Terminal.Gui;

/// <summary>
/// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/>
/// to get and set the property's value.
/// </summary>
/// <remarks>
/// Configuration properties must be <see langword="public"/> and <see langword="static"/>
/// and have the <see cref="SerializableConfigurationProperty"/>
/// attribute. If the type of the property requires specialized JSON serialization,
/// a <see cref="JsonConverter"/> must be provided using
/// the <see cref="JsonConverterAttribute"/> attribute.
/// </remarks>
public class ConfigProperty {
private object? propertyValue;

/// <summary>
/// Describes the property.
/// </summary>
public PropertyInfo? PropertyInfo { get; set; }

/// <summary>
/// Helper to get either the Json property named (specified by [JsonPropertyName(name)]
/// or the actual property name.
/// </summary>
/// <param name="pi"></param>
/// <returns></returns>
public static string GetJsonPropertyName (PropertyInfo pi)
{
var jpna = pi.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
return jpna?.Name ?? pi.Name;
}

/// <summary>
/// Holds the property's value as it was either read from the class's implementation or from a config file.
/// If the property has not been set (e.g. because no configuration file specified a value),
/// this will be <see langword="null"/>.
/// </summary>
/// <remarks>
/// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements of
/// the object that are non-null).
/// </remarks>
public object? PropertyValue {
get => propertyValue;
set {
propertyValue = value;
}
}

internal object? UpdateValueFrom (object source)
{
if (source == null) {
return PropertyValue;
}

var ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
if (source.GetType () != PropertyInfo!.PropertyType && (ut != null && source.GetType () != ut)) {
throw new ArgumentException ($"The source object ({PropertyInfo!.DeclaringType}.{PropertyInfo!.Name}) is not of type {PropertyInfo!.PropertyType}.");
}
if (PropertyValue != null && source != null) {
PropertyValue = ConfigurationManager.DeepMemberwiseCopy (source, PropertyValue);
} else {
PropertyValue = source;
}

return PropertyValue;
}

/// <summary>
/// Retrieves (using reflection) the value of the static property described in <see cref="PropertyInfo"/>
/// into <see cref="PropertyValue"/>.
/// </summary>
/// <returns></returns>
public object? RetrieveValue ()
{
return PropertyValue = PropertyInfo!.GetValue (null);
}

/// <summary>
/// Applies the <see cref="PropertyValue"/> to the property described by <see cref="PropertyInfo"/>.
/// </summary>
/// <returns></returns>
public bool Apply ()
{
if (PropertyValue != null) {
PropertyInfo?.SetValue (null, ConfigurationManager.DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
}
return PropertyValue != null;
}

}
116 changes: 2 additions & 114 deletions Terminal.Gui/Configuration/ConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,118 +69,6 @@ public static partial class ConfigurationManager {
},
};

/// <summary>
/// An attribute that can be applied to a property to indicate that it should included in the configuration file.
/// </summary>
/// <example>
/// [SerializableConfigurationProperty(Scope = typeof(Configuration.ThemeManager.ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
/// public static LineStyle DefaultBorderStyle {
/// ...
/// </example>
[AttributeUsage (AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class SerializableConfigurationProperty : System.Attribute {
/// <summary>
/// Specifies the scope of the property.
/// </summary>
public Type? Scope { get; set; }

/// <summary>
/// If <see langword="true"/>, the property will be serialized to the configuration file using only the property name
/// as the key. If <see langword="false"/>, the property will be serialized to the configuration file using the
/// property name pre-pended with the classname (e.g. <c>Application.UseSystemConsole</c>).
/// </summary>
public bool OmitClassName { get; set; }
}

/// <summary>
/// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/>
/// to get and set the property's value.
/// </summary>
/// <remarks>
/// Configuration properties must be <see langword="public"/> and <see langword="static"/>
/// and have the <see cref="SerializableConfigurationProperty"/>
/// attribute. If the type of the property requires specialized JSON serialization,
/// a <see cref="JsonConverter"/> must be provided using
/// the <see cref="JsonConverterAttribute"/> attribute.
/// </remarks>
public class ConfigProperty {
private object? propertyValue;

/// <summary>
/// Describes the property.
/// </summary>
public PropertyInfo? PropertyInfo { get; set; }

/// <summary>
/// Helper to get either the Json property named (specified by [JsonPropertyName(name)]
/// or the actual property name.
/// </summary>
/// <param name="pi"></param>
/// <returns></returns>
public static string GetJsonPropertyName (PropertyInfo pi)
{
var jpna = pi.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
return jpna?.Name ?? pi.Name;
}

/// <summary>
/// Holds the property's value as it was either read from the class's implementation or from a config file.
/// If the property has not been set (e.g. because no configuration file specified a value),
/// this will be <see langword="null"/>.
/// </summary>
/// <remarks>
/// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements of
/// the object that are non-null).
/// </remarks>
public object? PropertyValue {
get => propertyValue;
set {
propertyValue = value;
}
}

internal object? UpdateValueFrom (object source)
{
if (source == null) {
return PropertyValue;
}

var ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
if (source.GetType () != PropertyInfo!.PropertyType && (ut != null && source.GetType () != ut)) {
throw new ArgumentException ($"The source object ({PropertyInfo!.DeclaringType}.{PropertyInfo!.Name}) is not of type {PropertyInfo!.PropertyType}.");
}
if (PropertyValue != null && source != null) {
PropertyValue = DeepMemberwiseCopy (source, PropertyValue);
} else {
PropertyValue = source;
}

return PropertyValue;
}

/// <summary>
/// Retrieves (using reflection) the value of the static property described in <see cref="PropertyInfo"/>
/// into <see cref="PropertyValue"/>.
/// </summary>
/// <returns></returns>
public object? RetrieveValue ()
{
return PropertyValue = PropertyInfo!.GetValue (null);
}

/// <summary>
/// Applies the <see cref="PropertyValue"/> to the property described by <see cref="PropertyInfo"/>.
/// </summary>
/// <returns></returns>
public bool Apply ()
{
if (PropertyValue != null) {
PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
}
return PropertyValue != null;
}
}

/// <summary>
/// A dictionary of all properties in the Terminal.Gui project that are decorated with the <see cref="SerializableConfigurationProperty"/> attribute.
/// The keys are the property names pre-pended with the class that implements the property (e.g. <c>Application.UseSystemConsole</c>).
Expand All @@ -190,7 +78,7 @@ public bool Apply ()
/// <remarks>
/// Is <see langword="null"/> until <see cref="Initialize"/> is called.
/// </remarks>
private static Dictionary<string, ConfigProperty>? _allConfigProperties;
internal static Dictionary<string, ConfigProperty>? _allConfigProperties;

/// <summary>
/// The backing property for <see cref="Settings"/>.
Expand Down Expand Up @@ -319,7 +207,7 @@ internal static Stream ToStream ()

internal static StringBuilder jsonErrors = new StringBuilder ();

private static void AddJsonError (string error)
internal static void AddJsonError (string error)
{
Debug.WriteLine ($"ConfigurationManager: {error}");
jsonErrors.AppendLine (error);
Expand Down
Loading
Loading