Sicherung

This commit is contained in:
Maier Stephan SI
2023-01-02 04:33:49 +01:00
parent bea46135fd
commit d01747f75a
284 changed files with 6106 additions and 65112 deletions

View File

@@ -0,0 +1,76 @@
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="Log">
<PropertyGroup>
<Copyright>Copyright (c) 2015-2022 by Ivan Gavryliuk</Copyright>
<AssemblyTitle>Config.Net</AssemblyTitle>
<Authors>Ivan Gavryliuk (@aloneguid)</Authors>
<AssemblyName>Config.Net</AssemblyName>
<PackageId>Config.Net</PackageId>
<AssemblyVersion>4.0.0.0</AssemblyVersion>
<FileVersion>4.7.3.0</FileVersion>
<Version>4.7.3</Version>
<Description>Super simple configuration framework for .NET focused on developer ergonomics and strong typing. Supports multiple configuration sources such as .ini, .json, .xml files, as well as external providers pluggable by other NuGet packages.</Description>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageIconUrl />
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<PackageProjectUrl>https://github.com/aloneguid/config</PackageProjectUrl>
<RepositoryUrl>https://github.com/aloneguid/config</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<Choose>
<When Condition="'$(OS)' == 'Windows_NT'">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<Target Name="Log">
<Message Text="HELLO $(OS)" Importance="high"/>
</Target>
<ItemGroup>
<PackageReference Include="Castle.Core" Version="5.0.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
</ItemGroup>
<!-- we need latest system.text.json in order to support writeable json in earlier targets -->
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0'">
<PackageReference Include="System.Text.Json" Version="6.0.5"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\docs\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Update="DotNet.ReproducibleBuilds" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Castle.DynamicProxy;
using Config.Net.Core;
namespace Config.Net
{
public class ConfigurationBuilder<T> where T : class
{
private readonly ProxyGenerator _generator = new ProxyGenerator();
private List<IConfigStore> _stores = new List<IConfigStore>();
private TimeSpan _cacheInterval = TimeSpan.Zero;
private readonly List<ITypeParser> _customParsers = new List<ITypeParser>();
public ConfigurationBuilder()
{
TypeInfo ti = typeof(T).GetTypeInfo();
if (!ti.IsInterface) throw new ArgumentException($"{ti.FullName} must be an interface", ti.FullName);
}
/// <summary>
/// Creates an instance of the configuration interface
/// </summary>
/// <returns></returns>
public T Build()
{
var valueHandler = new ValueHandler(_customParsers);
var ioHandler = new IoHandler(_stores, valueHandler, _cacheInterval);
T instance = _generator.CreateInterfaceProxyWithoutTarget<T>(new InterfaceInterceptor(typeof(T), ioHandler));
return instance;
}
/// <summary>
/// Set to anything different from <see cref="TimeSpan.Zero"/> to add caching for values. By default
/// Config.Net doesn't cache any values
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
public ConfigurationBuilder<T> CacheFor(TimeSpan time)
{
_cacheInterval = time;
return this;
}
public ConfigurationBuilder<T> UseConfigStore(IConfigStore store)
{
_stores.Add(store);
return this;
}
/// <summary>
/// Adds a custom type parser
/// </summary>
public ConfigurationBuilder<T> UseTypeParser(ITypeParser parser)
{
if (parser == null)
{
throw new ArgumentNullException(nameof(parser));
}
_customParsers.Add(parser);
return this;
}
}
}

View File

@@ -0,0 +1,146 @@
using System.Reflection;
using Config.Net.Stores;
using System.Collections.Generic;
using Config.Net.Stores.Impl.CommandLine;
namespace Config.Net
{
/// <summary>
/// Configuration extensions
/// </summary>
public static class ConfigurationExtensions
{
/// <summary>
/// In-memory dictionary. Optionally you can pass pre-created dictionary, otherwise it will be created internally as empty.
/// </summary>
public static ConfigurationBuilder<TInterface> UseInMemoryDictionary<TInterface>(
this ConfigurationBuilder<TInterface> builder,
IDictionary<string, string>? container = null) where TInterface : class
{
builder.UseConfigStore(new DictionaryConfigStore(container));
return builder;
}
/// <summary>
/// Standard app.config (web.config) builder store. Read-only.
/// </summary>
public static ConfigurationBuilder<TInterface> UseAppConfig<TInterface>(this ConfigurationBuilder<TInterface> builder) where TInterface : class
{
builder.UseConfigStore(new AppConfigStore());
return builder;
}
/// <summary>
/// Reads builder from the .dll.config or .exe.config file.
/// </summary>
/// <param name="builder"></param>
/// <param name="assembly">Reference to the assembly to look for</param>
/// <returns></returns>
public static ConfigurationBuilder<TInterface> UseAssemblyConfig<TInterface>(this ConfigurationBuilder<TInterface> builder, Assembly assembly) where TInterface : class
{
builder.UseConfigStore(new AssemblyConfigStore(assembly));
return builder;
}
/// <summary>
/// Uses system environment variables
/// </summary>
public static ConfigurationBuilder<TInterface> UseEnvironmentVariables<TInterface>(this ConfigurationBuilder<TInterface> builder) where TInterface : class
{
builder.UseConfigStore(new EnvironmentVariablesStore());
return builder;
}
/// <summary>
/// Simple INI storage.
/// </summary>
/// <param name="builder"></param>
/// <param name="iniFilePath">File does not have to exist, however it will be created as soon as you try to write to it.</param>
/// <param name="parseInlineComments">When true, inline comments are parsed. It is set to false by default so inline comments are considered a part of the value.</param>
/// <returns></returns>
public static ConfigurationBuilder<TInterface> UseIniFile<TInterface>(this ConfigurationBuilder<TInterface> builder,
string iniFilePath,
bool parseInlineComments = false) where TInterface : class
{
builder.UseConfigStore(new IniFileConfigStore(iniFilePath, true, parseInlineComments));
return builder;
}
/// <summary>
/// Simple INI storage.
/// </summary>
/// <param name="builder"></param>
/// <param name="iniString">File contents</param>
/// <param name="parseInlineComments">When true, inline comments are parsed. It is set to false by default so inline comments are considered a part of the value</param>
/// <returns></returns>
public static ConfigurationBuilder<TInterface> UseIniString<TInterface>(this ConfigurationBuilder<TInterface> builder,
string iniString,
bool parseInlineComments = false) where TInterface : class
{
builder.UseConfigStore(new IniFileConfigStore(iniString, false, parseInlineComments));
return builder;
}
/// <summary>
/// Accepts builder from the command line arguments. This is not intended to replace a command line parsing framework but rather
/// complement it in a builder like way. Uses current process' command line parameters automatically
/// </summary>
/// <param name="builder">Configuration object</param>
/// <param name="isCaseSensitive">When true argument names are case sensitive, false by default</param>
/// <returns>Changed builder</returns>
public static ConfigurationBuilder<TInterface> UseCommandLineArgs<TInterface>(this ConfigurationBuilder<TInterface> builder,
bool isCaseSensitive = false,
params KeyValuePair<string, int>[] parameterNameToPosition)
where TInterface : class
{
builder.UseConfigStore(new CommandLineConfigStore(null, isCaseSensitive, parameterNameToPosition));
return builder;
}
public static ConfigurationBuilder<TInterface> UseCommandLineArgs<TInterface>(this ConfigurationBuilder<TInterface> builder,
bool isCaseSensitive = false,
string[]? args = null,
params KeyValuePair<string, int>[] parameterNameToPosition)
where TInterface : class
{
builder.UseConfigStore(new CommandLineConfigStore(args, isCaseSensitive, parameterNameToPosition));
return builder;
}
public static ConfigurationBuilder<TInterface> UseCommandLineArgs<TInterface>(this ConfigurationBuilder<TInterface> builder,
params KeyValuePair<string, int>[] parameterNameToPosition)
where TInterface : class
{
builder.UseConfigStore(new CommandLineConfigStore(null, false, parameterNameToPosition));
return builder;
}
/// <summary>
/// Uses JSON file as a builder storage.
/// </summary>
/// <param name="builder">Configuration object.</param>
/// <param name="jsonFilePath">Full path to json storage file.</param>
/// <returns>Changed builder.</returns>
/// <remarks>Storage file does not have to exist, however it will be created as soon as first write performed.</remarks>
public static ConfigurationBuilder<TInterface> UseJsonFile<TInterface>(this ConfigurationBuilder<TInterface> builder, string jsonFilePath) where TInterface : class
{
builder.UseConfigStore(new JsonConfigStore(jsonFilePath, true));
return builder;
}
/// <summary>
/// Uses JSON file as a builder storage.
/// </summary>
/// <param name="builder">Configuration object.</param>
/// <param name="jsonString">Json document.</param>
/// <returns>Changed builder.</returns>
/// <remarks>Storage file does not have to exist, however it will be created as soon as first write performed.</remarks>
public static ConfigurationBuilder<TInterface> UseJsonString<TInterface>(this ConfigurationBuilder<TInterface> builder, string jsonString) where TInterface : class
{
builder.UseConfigStore(new JsonConfigStore(jsonString, false));
return builder;
}
}
}

View File

@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace Config.Net.Core.Box
{
static class BoxFactory
{
public static Dictionary<string, ResultBox> Discover(Type t, ValueHandler valueHandler, string? basePath)
{
var result = new Dictionary<string, ResultBox>();
DiscoverProperties(t, valueHandler, result, basePath);
DiscoverMethods(t, valueHandler, result);
return result;
}
private static void DiscoverProperties(Type t, ValueHandler valueHandler, Dictionary<string, ResultBox> result, string? basePath)
{
IEnumerable<PropertyInfo> properties = GetHierarchyPublicProperties(t);
foreach (PropertyInfo pi in properties)
{
Type propertyType = pi.PropertyType;
ResultBox rbox;
bool isCollection = false;
if(ResultBox.TryGetCollection(propertyType, out propertyType))
{
if(pi.SetMethod != null)
{
throw new NotSupportedException($"Collection properties cannot have a setter. Detected at '{OptionPath.Combine(basePath, pi.Name)}'");
}
isCollection = true;
}
if(propertyType.GetTypeInfo().IsInterface)
{
rbox = new ProxyResultBox(pi.Name, propertyType);
}
else
{
rbox = new PropertyResultBox(pi.Name, propertyType);
}
ValidateSupportedType(rbox, valueHandler);
AddAttributes(rbox, pi, valueHandler);
//adjust to collection
if(isCollection)
{
rbox = new CollectionResultBox(pi.Name, rbox);
AddAttributes(rbox, pi, valueHandler);
}
result[pi.Name] = rbox;
}
}
private static void DiscoverMethods(Type t, ValueHandler valueHandler, Dictionary<string, ResultBox> result)
{
TypeInfo ti = t.GetTypeInfo();
IEnumerable<MethodInfo> methods = ti.DeclaredMethods.Where(m => !m.IsSpecialName);
foreach (MethodInfo method in methods)
{
var mbox = new MethodResultBox(method);
AddAttributes(mbox, method, valueHandler);
result[mbox.Name] = mbox;
}
}
private static void ValidateSupportedType(ResultBox rb, ValueHandler valueHandler)
{
Type? t = null;
if (rb is PropertyResultBox pbox)
t = rb.ResultBaseType;
if (t != null && !valueHandler.IsSupported(t))
{
throw new NotSupportedException($"type {t} on object '{rb.Name}' is not supported.");
}
}
private static object? GetDefaultValue(Type t)
{
if (t.GetTypeInfo().IsValueType) return Activator.CreateInstance(t);
return null;
}
private static void AddAttributes(ResultBox box, PropertyInfo pi, ValueHandler valueHandler)
{
AddAttributes(box, valueHandler,
pi.GetCustomAttribute<OptionAttribute>(),
pi.GetCustomAttribute<DefaultValueAttribute>());
}
private static void AddAttributes(ResultBox box, MethodInfo mi, ValueHandler valueHandler)
{
AddAttributes(box, valueHandler, mi.GetCustomAttribute<OptionAttribute>(), mi.GetCustomAttribute<DefaultValueAttribute>());
}
private static void AddAttributes(ResultBox box, ValueHandler valueHandler, params Attribute?[] attributes)
{
OptionAttribute? optionAttribute = attributes.OfType<OptionAttribute>().FirstOrDefault();
DefaultValueAttribute? defaultValueAttribute = attributes.OfType<DefaultValueAttribute>().FirstOrDefault();
if (optionAttribute?.Alias != null)
{
box.StoreByName = optionAttribute.Alias;
}
box.DefaultResult = GetDefaultValue(optionAttribute?.DefaultValue, box, valueHandler) ??
GetDefaultValue(defaultValueAttribute?.Value, box, valueHandler) ??
GetDefaultValue(box.ResultType);
}
private static object? GetDefaultValue(object? defaultValue, ResultBox box, ValueHandler valueHandler)
{
object? result = null;
if (defaultValue != null)
{
//validate that types for default value match
Type dvt = defaultValue.GetType();
if (dvt != box.ResultType && dvt != typeof(string))
{
throw new InvalidCastException($"Default value for option {box.Name} is of type {dvt.FullName} whereas the property has type {box.ResultType.FullName}. To fix this, either set default value to type {box.ResultType.FullName} or a string parseable to the target type.");
}
if (box.ResultType != typeof(string) && dvt == typeof(string))
{
valueHandler.TryParse(box.ResultType, (string?)defaultValue, out result);
}
}
if (result == null)
{
result = defaultValue;
}
return result;
}
private static PropertyInfo[] GetHierarchyPublicProperties(Type type)
{
var propertyInfos = new List<PropertyInfo>();
var considered = new List<TypeInfo>();
var queue = new Queue<TypeInfo>();
considered.Add(type.GetTypeInfo());
queue.Enqueue(type.GetTypeInfo());
while (queue.Count > 0)
{
TypeInfo typeInfo = queue.Dequeue();
//add base interfaces to the queue
foreach (Type subInterface in typeInfo.ImplementedInterfaces)
{
TypeInfo subInterfaceTypeInfo = subInterface.GetTypeInfo();
if (considered.Contains(subInterfaceTypeInfo)) continue;
considered.Add(subInterfaceTypeInfo);
queue.Enqueue(subInterfaceTypeInfo);
}
//add base classes to the queue
if (typeInfo.BaseType != null)
{
TypeInfo baseType = typeInfo.BaseType.GetTypeInfo();
if (!considered.Contains(baseType))
{
considered.Add(baseType);
queue.Enqueue(baseType);
}
}
//get properties from the current type
IEnumerable<PropertyInfo> newProperties = typeInfo.DeclaredProperties.Where(p => !propertyInfos.Contains(p));
propertyInfos.InsertRange(0, newProperties);
}
return propertyInfos.ToArray();
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Config.Net.Core.Box
{
class CollectionResultBox : ResultBox
{
private readonly ResultBox _elementResultBox;
private string? _basePath;
private DynamicReader? _reader;
public CollectionResultBox(string name, ResultBox elementBox) : base(name, elementBox.ResultType, null)
{
_elementResultBox = elementBox;
}
public ResultBox ElementResultBox => _elementResultBox;
public bool IsInitialised { get; private set; }
public IEnumerable? CollectionInstance { get; private set; }
public void Initialise(string? basePath, int length, DynamicReader reader)
{
_basePath = basePath;
_reader = reader;
CollectionInstance = CreateGenericEnumerable(length);
IsInitialised = true;
}
private IEnumerable? CreateGenericEnumerable(int count)
{
Type t = typeof(DynamicEnumerable<>);
t = t.MakeGenericType(ResultType);
IEnumerable? instance = (IEnumerable?)Activator.CreateInstance(t, count, this);
return instance;
}
private object? ReadAt(int index)
{
return _reader?.Read(ElementResultBox, index);
}
private class DynamicEnumerable<T> : IEnumerable<T>
{
private readonly int _count;
private readonly CollectionResultBox _parent;
public DynamicEnumerable(int count, CollectionResultBox parent)
{
_count = count;
_parent = parent;
}
public IEnumerator<T> GetEnumerator()
{
return new DynamicEnumerator<T>(_count, _parent);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new DynamicEnumerator<T>(_count, _parent);
}
}
private class DynamicEnumerator<T> : IEnumerator<T>
{
private int _index = -1;
private readonly int _count;
private readonly CollectionResultBox _parent;
private T? _current;
public DynamicEnumerator(int count, CollectionResultBox parent)
{
_count = count;
_parent = parent;
}
#pragma warning disable CS8603 // Possible null reference return.
public T Current => _current ?? default(T);
object IEnumerator.Current => _current;
#pragma warning restore CS8603 // Possible null reference return.
public void Dispose()
{
}
public bool MoveNext()
{
_index += 1;
_current = (T?)_parent.ReadAt(_index);
return _index < _count;
}
public void Reset()
{
_index = -1;
}
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Config.Net.Core.Box
{
class MethodResultBox : ResultBox
{
public MethodResultBox(MethodInfo methodInfo) : base(GetName(methodInfo), GetReturnType(methodInfo), null)
{
StoreByName = GetStoreName(methodInfo);
IsGettter = IsGet(methodInfo);
}
public bool IsGettter { get; private set; }
/// <summary>
/// Composes a uniqueue method name using method name itself and parameter type names, separated by underscore
/// </summary>
public static string GetName(MethodInfo mi)
{
ParameterInfo[] parameters = mi.GetParameters();
var sb = new StringBuilder();
sb.Append(mi.Name);
foreach (ParameterInfo pi in parameters)
{
sb.Append("-");
sb.Append(pi.ParameterType.ToString());
}
return sb.ToString();
}
public string GetValuePath(object[] arguments)
{
var sb = new StringBuilder();
sb.Append(StoreByName);
bool ignoreLast = !IsGettter;
for (int i = 0; i < arguments.Length - (ignoreLast ? 1 : 0); i++)
{
object value = arguments[i];
if (value == null) continue;
if (sb.Length > 0)
{
sb.Append(OptionPath.Separator);
}
sb.Append(value.ToString());
}
return sb.ToString();
}
private static string GetStoreName(MethodInfo mi)
{
string name = mi.Name;
if (name.StartsWith("get", StringComparison.OrdinalIgnoreCase) ||
name.StartsWith("set", StringComparison.OrdinalIgnoreCase))
{
name = name.Substring(3);
}
return name;
}
private static bool IsGet(MethodInfo mi)
{
return mi.ReturnType != typeof(void);
}
private static Type GetReturnType(MethodInfo mi)
{
ParameterInfo[] parameters = mi.GetParameters();
if (parameters == null || parameters.Length == 0)
{
throw new InvalidOperationException($"method {mi.Name} must have at least one parameter");
}
Type returnType = IsGet(mi) ? mi.ReturnType : parameters[parameters.Length - 1].ParameterType;
return returnType;
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Reflection;
namespace Config.Net.Core.Box
{
class PropertyResultBox : ResultBox
{
public PropertyResultBox(string name, Type resultType) : base(name, resultType, null)
{
}
public static bool IsProperty(MethodInfo mi, out bool isGetter, out string? name)
{
if (mi.Name.StartsWith("get_"))
{
isGetter = true;
name = mi.Name.Substring(4);
return true;
}
if (mi.Name.StartsWith("set_"))
{
isGetter = false;
name = mi.Name.Substring(4);
return true;
}
isGetter = false;
name = null;
return false;
}
public static bool IsProperty(MethodInfo mi, out string? name)
{
if (mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_"))
{
name = mi.Name.Substring(4);
return true;
}
name = null;
return false;
}
public static bool IsGetProperty(MethodInfo mi)
{
return mi.Name.StartsWith("get_");
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using Castle.DynamicProxy;
namespace Config.Net.Core.Box
{
class ProxyResultBox : ResultBox
{
private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();
private readonly Dictionary<int, object> _indexToProxyInstance = new Dictionary<int, object>();
public ProxyResultBox(string name, Type interfaceType) : base(name, interfaceType, null)
{
}
public bool IsInitialisedAt(int index)
{
return _indexToProxyInstance.ContainsKey(index);
}
public object GetInstanceAt(int index)
{
return _indexToProxyInstance[index];
}
public void InitialiseAt(int index, IoHandler ioHandler, string prefix)
{
object instance = ProxyGenerator.CreateInterfaceProxyWithoutTarget(ResultBaseType,
new InterfaceInterceptor(ResultBaseType, ioHandler, prefix));
_indexToProxyInstance[index] = instance;
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Config.Net.Core.Box
{
abstract class ResultBox
{
private string? _storeByName;
protected ResultBox(string name, Type resultType, object? defaultResult)
{
Name = name;
ResultType = resultType;
ResultBaseType = GetBaseType(resultType);
DefaultResult = defaultResult;
}
public string Name { get; }
public string StoreByName
{
get => _storeByName ?? Name;
set => _storeByName = value;
}
public Type ResultType { get; }
public Type ResultBaseType { get; }
public object? DefaultResult { get; set; }
#region [ Utility Methods ]
private static Type GetBaseType(Type t)
{
TypeInfo ti = t.GetTypeInfo();
if (ti.IsClass)
{
return t;
}
else
{
if (ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return ti.GenericTypeArguments[0];
}
else
{
return t;
}
}
}
internal static bool TryGetCollection(Type t, out Type elementType)
{
TypeInfo ti = t.GetTypeInfo();
if(ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
elementType = ti.GenericTypeArguments[0];
return true;
}
elementType = t;
return false;
}
#endregion
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Text;
using Config.Net.Core.Box;
namespace Config.Net.Core
{
class DynamicReader
{
private readonly string? _basePath;
private readonly IoHandler _ioHandler;
public DynamicReader(string? basePath, IoHandler ioHandler)
{
_basePath = basePath;
_ioHandler = ioHandler;
}
public object? Read(ResultBox rbox, int index = -1, params object[] arguments)
{
if (rbox is PropertyResultBox pbox) return ReadProperty(pbox, index);
if (rbox is ProxyResultBox xbox) return ReadProxy(xbox, index);
if (rbox is CollectionResultBox cbox) return ReadCollection(cbox, index);
if (rbox is MethodResultBox mbox) return ReadMethod(mbox, arguments);
throw new NotImplementedException($"don't know how to read {rbox.GetType()}");
}
private object? ReadProperty(PropertyResultBox pbox, int index)
{
string path = OptionPath.Combine(index, _basePath, pbox.StoreByName);
return _ioHandler.Read(pbox.ResultBaseType, path, pbox.DefaultResult);
}
private object ReadProxy(ProxyResultBox xbox, int index)
{
if (!xbox.IsInitialisedAt(index))
{
string prefix = OptionPath.Combine(index, _basePath, xbox.StoreByName);
xbox.InitialiseAt(index, _ioHandler, prefix);
}
return xbox.GetInstanceAt(index);
}
private object? ReadCollection(CollectionResultBox cbox, int index)
{
string lengthPath = OptionPath.Combine(index, _basePath, cbox.StoreByName);
lengthPath = OptionPath.AddLength(lengthPath);
if (!cbox.IsInitialised)
{
int length = (int?)_ioHandler.Read(typeof(int), lengthPath, 0) ?? 0;
cbox.Initialise(_basePath, length, this);
}
return cbox.CollectionInstance;
}
private object? ReadMethod(MethodResultBox mbox, object[] arguments)
{
string path = mbox.GetValuePath(arguments);
path = OptionPath.Combine(_basePath, path);
return _ioHandler.Read(mbox.ResultBaseType, path, mbox.DefaultResult);
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using Config.Net.Core.Box;
namespace Config.Net.Core
{
class DynamicWriter
{
private readonly string? _basePath;
private readonly IoHandler _ioHandler;
public DynamicWriter(string? basePath, IoHandler ioHandler)
{
_basePath = basePath;
_ioHandler = ioHandler;
}
public void Write(ResultBox rbox, object[] arguments)
{
if (rbox is PropertyResultBox pbox) WriteProperty(pbox, arguments);
else if (rbox is MethodResultBox mbox) WriteMethod(mbox, arguments);
else if (rbox is ProxyResultBox xbox) WriteProxy(xbox, arguments);
else throw new NotImplementedException($"don't know how to write {rbox.GetType()}");
}
private void WriteProperty(PropertyResultBox pbox, object[] arguments)
{
string path = OptionPath.Combine(_basePath, pbox.StoreByName);
_ioHandler.Write(pbox.ResultBaseType, path, arguments[0]);
}
private void WriteMethod(MethodResultBox mbox, object[] arguments)
{
object value = arguments[arguments.Length - 1];
string path = mbox.GetValuePath(arguments);
_ioHandler.Write(mbox.ResultBaseType, path, value);
}
private void WriteProxy(ProxyResultBox xbox, object[] arguments)
{
throw new NotSupportedException("cannot assign values to interface properties");
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Config.Net.Core
{
static class Extensions
{
public static TValue? GetValueOrDefaultInternal<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
where TKey: notnull
where TValue: class
{
if (!dictionary.TryGetValue(key, out TValue? value)) return default(TValue);
return value;
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Text;
using Config.Net.Core;
using Config.Net.TypeParsers;
namespace Config.Net.Core
{
/// <summary>
/// Helper class to implement flat arrays
/// </summary>
public static class FlatArrays
{
public static bool IsArrayLength(string? key, Func<string, string?> getValue, out int length)
{
if (!OptionPath.TryStripLength(key, out key))
{
length = 0;
return false;
}
string? value = key == null ? null : getValue(key);
if (value == null)
{
length = 0;
return false;
}
if (!StringArrayParser.TryParse(value, out string[]? ar))
{
length = 0;
return false;
}
length = ar?.Length ?? 0;
return true;
}
public static bool IsArrayElement(string? key, Func<string, string?> getValue, out string? value)
{
if(!OptionPath.TryStripIndex(key, out key, out int index))
{
value = null;
return false;
}
string? arrayString = key == null ? null : getValue(key);
if (!StringArrayParser.TryParse(arrayString, out string[]? array) || index >= array?.Length)
{
value = null;
return false;
}
value = array?[index] ?? null;
return true;
}
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using Castle.DynamicProxy;
using Config.Net.Core.Box;
namespace Config.Net.Core
{
class InterfaceInterceptor : IInterceptor
{
private readonly Dictionary<string, ResultBox> _boxes;
private IoHandler _ioHandler;
private readonly string? _prefix;
private readonly DynamicReader _reader;
private readonly DynamicWriter _writer;
private readonly bool _isInpc;
private PropertyChangedEventHandler? _inpcHandler;
public InterfaceInterceptor(Type interfaceType, IoHandler ioHandler, string? prefix = null)
{
_boxes = BoxFactory.Discover(interfaceType, ioHandler.ValueHandler, prefix);
_ioHandler = ioHandler;
_prefix = prefix;
_reader = new DynamicReader(prefix, ioHandler);
_writer = new DynamicWriter(prefix, ioHandler);
_isInpc = interfaceType.GetInterface(nameof(INotifyPropertyChanged)) != null;
}
private ResultBox FindBox(IInvocation invocation)
{
if (PropertyResultBox.IsProperty(invocation.Method, out string? propertyName) && propertyName != null)
{
return _boxes[propertyName];
}
else //method
{
string name = MethodResultBox.GetName(invocation.Method);
return _boxes[name];
}
}
public void Intercept(IInvocation invocation)
{
if (TryInterceptInpc(invocation)) return;
ResultBox rbox = FindBox(invocation);
bool isRead =
(rbox is PropertyResultBox && PropertyResultBox.IsGetProperty(invocation.Method)) ||
(rbox is ProxyResultBox && PropertyResultBox.IsGetProperty(invocation.Method)) ||
(rbox is MethodResultBox mbox && mbox.IsGettter) ||
(rbox is CollectionResultBox);
if(isRead)
{
invocation.ReturnValue = _reader.Read(rbox, -1, invocation.Arguments);
return;
}
else
{
_writer.Write(rbox, invocation.Arguments);
TryNotifyInpc(invocation, rbox);
}
}
private bool TryInterceptInpc(IInvocation invocation)
{
if (!_isInpc) return false;
if (invocation.Method.Name == "add_PropertyChanged")
{
invocation.ReturnValue =
_inpcHandler =
(PropertyChangedEventHandler)Delegate.Combine(_inpcHandler, (Delegate)invocation.Arguments[0]);
return true;
}
else if(invocation.Method.Name == "remove_PropertyChanged")
{
invocation.ReturnValue =
_inpcHandler =
(PropertyChangedEventHandler?)Delegate.Remove(_inpcHandler, (Delegate)invocation.Arguments[0]);
return true;
}
return false;
}
private void TryNotifyInpc(IInvocation invocation, ResultBox rbox)
{
if (_inpcHandler == null || rbox is MethodResultBox) return;
_inpcHandler.Invoke(invocation.InvocationTarget, new PropertyChangedEventArgs(rbox.Name));
if(rbox.Name != rbox.StoreByName)
{
//notify on StoreByName as well
_inpcHandler.Invoke(invocation.InvocationTarget, new PropertyChangedEventArgs(rbox.StoreByName));
}
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Config.Net.Core
{
class IoHandler
{
private readonly IEnumerable<IConfigStore> _stores;
private readonly ValueHandler _valueHandler;
private readonly TimeSpan _cacheInterval;
private readonly ConcurrentDictionary<string, LazyVar<object>> _keyToValue = new ConcurrentDictionary<string, LazyVar<object>>();
public IoHandler(IEnumerable<IConfigStore> stores, ValueHandler valueHandler, TimeSpan cacheInterval)
{
_stores = stores ?? throw new ArgumentNullException(nameof(stores));
_valueHandler = valueHandler ?? throw new ArgumentNullException(nameof(valueHandler));
_cacheInterval = cacheInterval;
}
public ValueHandler ValueHandler => _valueHandler;
public object? Read(Type baseType, string path, object? defaultValue)
{
if(!_keyToValue.TryGetValue(path, out _))
{
var v = new LazyVar<object>(_cacheInterval, () => ReadNonCached(baseType, path, defaultValue));
_keyToValue[path] = v;
return v.GetValue();
}
return _keyToValue[path].GetValue();
}
public void Write(Type baseType, string path, object? value)
{
string? valueToWrite = _valueHandler.ConvertValue(baseType, value);
foreach (IConfigStore store in _stores.Where(s => s.CanWrite))
{
store.Write(path, valueToWrite);
}
}
private object? ReadNonCached(Type baseType, string path, object? defaultValue)
{
string? rawValue = ReadFirstValue(path);
return _valueHandler.ParseValue(baseType, rawValue, defaultValue);
}
private string? ReadFirstValue(string key)
{
foreach (IConfigStore store in _stores)
{
if (store.CanRead)
{
string? value = store.Read(key);
if (value != null) return value;
}
}
return null;
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
namespace Config.Net.Core
{
/// <summary>
/// Implements a lazy value i.e. that can expire in future
/// </summary>
/// <typeparam name="T"></typeparam>
class LazyVar<T> where T : class
{
private readonly Func<Task<T?>>? _renewFuncAsync;
private readonly Func<T?>? _renewFunc;
private DateTime _lastRenewed = DateTime.MinValue;
private readonly TimeSpan _timeToLive;
private T? _value;
/// <summary>
/// Creates an instance of a lazy variable with time-to-live value
/// </summary>
/// <param name="timeToLive">Time to live. Setting to <see cref="TimeSpan.Zero"/> disables caching completely</param>
/// <param name="renewFunc"></param>
public LazyVar(TimeSpan timeToLive, Func<Task<T?>> renewFunc)
{
_timeToLive = timeToLive;
_renewFuncAsync = renewFunc ?? throw new ArgumentNullException(nameof(renewFunc));
_renewFunc = null;
}
/// <summary>
/// Creates an instance of a lazy variable with time-to-live value
/// </summary>
/// <param name="timeToLive">Time to live. Setting to <see cref="TimeSpan.Zero"/> disables caching completely</param>
/// <param name="renewFunc"></param>
public LazyVar(TimeSpan timeToLive, Func<T?> renewFunc)
{
_timeToLive = timeToLive;
_renewFuncAsync = null;
_renewFunc = renewFunc ?? throw new ArgumentNullException(nameof(renewFunc));
}
/// <summary>
/// Gets the values, renewing it if necessary
/// </summary>
/// <returns>Value</returns>
public async Task<T?> GetValueAsync()
{
if (_renewFuncAsync == null)
{
throw new InvalidOperationException("cannot renew value, async delegate is not specified");
}
if (_timeToLive == TimeSpan.Zero)
{
return await _renewFuncAsync();
}
bool expired = (DateTime.UtcNow - _lastRenewed) > _timeToLive;
if (expired)
{
_value = await _renewFuncAsync();
_lastRenewed = DateTime.UtcNow;
}
return _value;
}
/// <summary>
/// Gets the values, renewing it if necessary
/// </summary>
/// <returns>Value</returns>
public T? GetValue()
{
if (_renewFunc == null)
{
throw new InvalidOperationException("cannot renew value, synchronous delegate is not specified");
}
if (_timeToLive == TimeSpan.Zero)
{
return _renewFunc();
}
bool expired = (DateTime.UtcNow - _lastRenewed) > _timeToLive;
if (expired)
{
_value = _renewFunc();
_lastRenewed = DateTime.UtcNow;
}
return _value;
}
}
}

View File

@@ -0,0 +1,88 @@
namespace Config.Net.Core
{
public static class OptionPath
{
public const string Separator = ".";
private const string IndexOpen = "[";
private const string IndexClose = "]";
public const string LengthFunction = ".$l";
public static string Combine(params string?[] parts)
{
return Combine(-1, parts);
}
public static string AddLength(string path)
{
return path + LengthFunction;
}
public static bool TryStripLength(string? path, out string? noLengthPath)
{
if (path == null)
{
noLengthPath = path;
return false;
}
if (!path.EndsWith(LengthFunction))
{
noLengthPath = path;
return false;
}
noLengthPath = path.Substring(0, path.Length - LengthFunction.Length);
return true;
}
/// <summary>
/// For indexed paths like "creds[1]" strips index part so it becomes:
/// - noIndexPath: "creds"
/// - index: 1
///
/// If path is not indexed returns false and noIndexPath is equal to path itself
/// </summary>
public static bool TryStripIndex(string? path, out string? noIndexPath, out int index)
{
if (path == null)
{
index = 0;
noIndexPath = path;
return false;
}
int openIdx = path.IndexOf(IndexOpen);
int closeIdx = path.IndexOf(IndexClose);
if (openIdx == -1 || closeIdx == -1 || openIdx > closeIdx || closeIdx != path.Length - 1)
{
noIndexPath = path;
index = 0;
return false;
}
noIndexPath = path.Substring(0, openIdx);
int.TryParse(path.Substring(openIdx + 1, closeIdx - openIdx - 1), out index);
return true;
}
public static string Combine(int index, params string?[] parts)
{
string s = string.Empty;
for (int i = 0; i < parts.Length; i++)
{
if (s.Length > 0) s += Separator;
if (!string.IsNullOrEmpty(parts[i])) s += parts[i];
}
if (index != -1)
{
s = $"{s}{IndexOpen}{index}{IndexClose}";
}
return s;
}
}
}

View File

@@ -0,0 +1,157 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Config.Net.TypeParsers;
namespace Config.Net.Core
{
class ValueHandler
{
private readonly DefaultParser _defaultParser = new DefaultParser();
private readonly ConcurrentDictionary<Type, ITypeParser> _allParsers = new ConcurrentDictionary<Type, ITypeParser>();
private readonly HashSet<Type> _supportedTypes = new HashSet<Type>();
private static readonly ValueHandler _default = new ValueHandler();
public ValueHandler(IEnumerable<ITypeParser>? customParsers = null)
{
foreach (ITypeParser pc in GetBuiltInParsers())
{
AddParser(pc);
}
if(customParsers != null)
{
foreach(ITypeParser pc in customParsers)
{
AddParser(pc);
}
}
}
public void AddParser(ITypeParser parser)
{
if (parser == null)
{
throw new ArgumentNullException(nameof(parser));
}
foreach (Type t in parser.SupportedTypes)
{
_allParsers[t] = parser;
_supportedTypes.Add(t);
}
}
public bool IsSupported(Type t)
{
return _supportedTypes.Contains(t) || _defaultParser.IsSupported(t);
}
public object? ParseValue(Type baseType, string? rawValue, object? defaultValue)
{
object? result;
if (rawValue == null)
{
result = defaultValue;
}
else
{
if(!TryParse(baseType, rawValue, out result))
{
result = defaultValue;
}
}
return result;
}
public bool TryParse(Type propertyType, string? rawValue, out object? result)
{
if (_defaultParser.IsSupported(propertyType)) //type here must be a non-nullable one
{
if (!_defaultParser.TryParse(rawValue, propertyType, out result))
{
return false;
}
}
else
{
ITypeParser? typeParser = GetParser(propertyType);
if (typeParser == null)
{
result = null;
return false;
}
if (!typeParser.TryParse(rawValue, propertyType, out result))
{
return false;
}
}
return true;
}
public string? ConvertValue(Type baseType, object? value)
{
string? str;
if (value == null)
{
str = null;
}
else
{
if (_defaultParser.IsSupported(baseType))
{
str = _defaultParser.ToRawString(value);
}
else
{
ITypeParser? parser = GetParser(value.GetType());
str = parser?.ToRawString(value);
}
}
return str;
}
private ITypeParser? GetParser(Type t)
{
ITypeParser? result;
_allParsers.TryGetValue(t, out result);
return result;
}
/// <summary>
/// Scans assembly for types implementing <see cref="ITypeParser"/> and builds Type => instance dictionary.
/// Not sure if I should use reflection here, however the assembly is small and this shouldn't cause any
/// performance issues
/// </summary>
/// <returns></returns>
private static IEnumerable<ITypeParser> GetBuiltInParsers()
{
return new ITypeParser[]
{
new FloatParser(),
new DoubleParser(),
new DecimalParser(),
new SByteParser(),
new ShortParser(),
new IntParser(),
new LongParser(),
new ByteParser(),
new UShortParser(),
new UIntParser(),
new ULongParser(),
new StringArrayParser(),
new StringParser(),
new TimeSpanParser(),
new CoreParsers(),
new NetworkCredentialParser()
};
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Config.Net
{
/// <summary>
/// Configuration store interface
/// </summary>
public interface IConfigStore : IDisposable
{
/// <summary>
/// Returns true if store supports read operation.
/// </summary>
bool CanRead { get; }
/// <summary>
/// Returns true if store supports write operation.
/// </summary>
bool CanWrite { get; }
/// <summary>
/// Reads a key from the store.
/// </summary>
/// <param name="key">Key name.</param>
/// <returns>If key exists in the store returns the value, othwise returns null.</returns>
string? Read(string key);
/// <summary>
/// Writes a key to the store.
/// </summary>
/// <param name="key">Key name</param>
/// <param name="value">Key value. Value of NULL usually means the key will be deleted, at least
/// this is the recomendation for the custom store implementers.</param>
void Write(string key, string? value);
}
}

32
Config.Net/ITypeParser.cs Normal file
View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
namespace Config.Net
{
/// <summary>
/// Type parser interface
/// </summary>
public interface ITypeParser
{
/// <summary>
/// Returns the list of supported types this type parser handles
/// </summary>
IEnumerable<Type> SupportedTypes { get; }
/// <summary>
/// Tries to parse a value from string
/// </summary>
/// <param name="value"></param>
/// <param name="t"></param>
/// <param name="result"></param>
/// <returns></returns>
bool TryParse(string? value, Type t, out object? result);
/// <summary>
/// Converts value to a string to store in a backed store
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
string? ToRawString(object? value);
}
}

View File

@@ -0,0 +1,19 @@
using System;
namespace Config.Net
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false)]
public class OptionAttribute : Attribute
{
/// <summary>
/// Alias is used to override option name if it's stored by a different name in external stores
/// </summary>
public string? Alias { get; set; }
/// <summary>
/// Set to override the default value if option is not found in any stores
/// </summary>
public object? DefaultValue { get; set; }
}
}

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Config.Net.Tests")]

View File

@@ -0,0 +1,313 @@
/*using System;
using System.Reflection;
using System.Collections.Concurrent;
using Config.Net.TypeParsers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace Config.Net
{
/// <summary>
/// Generic container for test settings
/// </summary>
public abstract class SettingsContainer
{
private readonly IConfigConfiguration _config = new ContainerConfiguration();
private readonly ConcurrentDictionary<string, Option> _nameToOption =
new ConcurrentDictionary<string, Option>();
private readonly ConcurrentDictionary<string, OptionValue> _nameToOptionValue =
new ConcurrentDictionary<string, OptionValue>();
private static readonly DefaultParser DefaultParser = new DefaultParser();
private readonly string _namespace;
private bool _isConfigured;
/// <summary>
/// Constructs the container in default namespace
/// </summary>
protected SettingsContainer() : this(null)
{
}
/// <summary>
/// Constructs the container allowing to specify a custom namespace
/// </summary>
/// <param name="namespaceName"></param>
protected SettingsContainer(string namespaceName)
{
_namespace = namespaceName;
DiscoverProperties();
}
/// <summary>
/// Reads the option value
/// </summary>
/// <typeparam name="T">Option type</typeparam>
/// <param name="option">Option reference</param>
/// <returns>Option value</returns>
public T Read<T>(Option<T> option)
{
CheckConfigured();
CheckCanParse(option.NonNullableType);
OptionValue optionValue;
_nameToOptionValue.TryGetValue(option.Name, out optionValue);
if (!optionValue.IsExpired(_config.CacheTimeout))
{
return (T) optionValue.RawValue;
}
string value = ReadFirstValue(option.Name);
if (value == null)
{
optionValue.RawValue = option.DefaultValue;
}
else if (DefaultParser.IsSupported(option.NonNullableType))
{
object resultObject;
if (DefaultParser.TryParse(value, option.NonNullableType, out resultObject))
{
optionValue.Update<T>((T) resultObject);
}
else
{
optionValue.Update(option.DefaultValue);
}
}
else
{
ITypeParser typeParser = _config.GetParser(option.NonNullableType);
object result;
typeParser.TryParse(value, option.NonNullableType, out result);
optionValue.Update<T>((T) result);
}
OnReadOption(option, optionValue.RawValue);
return (T) optionValue.RawValue;
}
/// <summary>
/// Writes a new value to the option
/// </summary>
/// <typeparam name="T">Option type</typeparam>
/// <param name="option">Option reference</param>
/// <param name="value">New value</param>
public void Write<T>(Option<T> option, T value)
{
CheckConfigured();
CheckCanParse(option.NonNullableType);
OptionValue optionValue;
_nameToOptionValue.TryGetValue(option.Name, out optionValue);
foreach (IConfigStore store in _config.Stores)
{
if (store.CanWrite)
{
string rawValue = AreEqual(value, option.DefaultValue) ? null : GetRawStringValue(option, value);
store.Write(option.Name, rawValue);
break;
}
}
optionValue.Update(value);
OnWriteOption(option, value);
}
/// <summary>
/// This method is called internally before containers is ready for use. You can specify
/// configuration stores or any other options here.
/// </summary>
/// <param name="configuration"></param>
protected abstract void OnConfigure(IConfigConfiguration configuration);
/// <summary>
/// Called after any value is read
/// </summary>
/// <param name="option">Optiond that is read</param>
/// <param name="value">Option value read from a store</param>
protected virtual void OnReadOption(Option option, object value)
{
}
/// <summary>
/// Called before any value is written
/// </summary>
/// <param name="option">Option that is written</param>
/// <param name="value">Option value to write</param>
protected virtual void OnWriteOption(Option option, object value)
{
}
private void CheckConfigured()
{
if (_isConfigured) return;
OnConfigure(_config);
_isConfigured = true;
}
[Ignore]
private void DiscoverProperties()
{
Type t = this.GetType();
Type optionType = typeof(Option);
IEnumerable<PropertyInfo> properties = t.GetRuntimeProperties()
.Where(f => f.PropertyType.GetTypeInfo().IsSubclassOf(optionType) && f.GetCustomAttribute<IgnoreAttribute>() == null).ToList();
// Only include fields that have not already been added as properties
IEnumerable<FieldInfo> fields = t.GetRuntimeFields()
.Where(f => f.IsPublic && f.FieldType.GetTypeInfo().IsSubclassOf(optionType)).ToList();
foreach (PropertyInfo pi in properties)
{
AssignOption(pi.GetValue(this), pi, pi.PropertyType.GetTypeInfo(), pi.CanWrite, v => pi.SetValue(this, v));
}
foreach (FieldInfo fi in fields)
{
if (properties.Any(p => p.Name == fi.Name))
throw new ArgumentException(
$"Field '{fi.Name}' has already been defined as a property.");
var methInfo = fi.FieldType.GetTypeInfo();
if (!methInfo.IsSubclassOf(optionType)) continue;
AssignOption(fi.GetValue(this), fi, methInfo, true, v => fi.SetValue(this, v));
}
}
private void AssignOption(object objValue, MemberInfo pi, TypeInfo propInfo, bool writeable,
Action<object> setter)
{
{
//check if it has the value
if (objValue == null)
{
// Throw an exception if it's impossible to assign a default value to a read-only property with no default object assigned
if (!writeable)
throw new ArgumentException(
$"Property/Field '{pi.Name}' must either be settable or be pre-initialised with an Option<> object as a property, or marked as readonly if a field");
//create default instance if it doesn't exist
var nt = typeof(Option<>);
Type[] ntArgs = propInfo.GetGenericArguments();
Type ntGen = nt.MakeGenericType(ntArgs);
objValue = Activator.CreateInstance(ntGen);
//set the instance value back to the container
setter(objValue);
}
Option value = (Option) objValue;
if (string.IsNullOrEmpty(value.Name)) value.Name = pi.Name;
value.Name = GetFullKeyName(value.Name);
value._parent = this;
value.NonNullableType = Nullable.GetUnderlyingType(value.ValueType);
value.IsNullable = value.NonNullableType != null;
if (value.NonNullableType == null) value.NonNullableType = value.ValueType;
_nameToOption[value.Name] = value;
_nameToOptionValue[value.Name] = new OptionValue();
}
}
private string GetFullKeyName(string name)
{
if (string.IsNullOrEmpty(_namespace)) return name;
return _namespace + "." + name;
}
private bool CanParse(Type t)
{
return _config.HasParser(t) || DefaultParser.IsSupported(t);
}
private string ReadFirstValue(string key)
{
foreach (IConfigStore store in _config.Stores)
{
if (store.CanRead)
{
string value = store.Read(key);
if (value != null) return value;
}
}
return null;
}
private void CheckCanParse(Type t)
{
if (!CanParse(t))
{
throw new ArgumentException("value parser for " + t.FullName +
" is not registered and not supported by default parser");
}
}
private bool AreEqual(object value1, object value2)
{
if (value1 == null && value2 == null) return true;
if (value1 != null && value2 != null)
{
Type t1 = value1.GetType();
Type t2 = value2.GetType();
if (t1.IsArray && t2.IsArray)
{
return AreEqual((Array) value1, (Array) value2);
}
}
return value1 != null && value1.Equals(value2);
}
private bool AreEqual(Array a, Array b)
{
if (a == null && b == null) return true;
if (a == null || b == null) return false;
if (a.Length != b.Length) return false;
for (int i = 0; i < a.Length; i++)
{
object obj1 = a.GetValue(i);
object obj2 = b.GetValue(i);
if (!AreEqual(obj1, obj2)) return false;
}
return true;
}
private string GetRawStringValue<T>(Option<T> option, T value)
{
string stringValue = null;
ITypeParser typeParser = _config.GetParser(option.NonNullableType);
if (typeParser != null)
{
stringValue = typeParser.ToRawString(value);
}
else
{
if (DefaultParser.IsSupported(typeof(T)))
{
stringValue = DefaultParser.ToRawString(value);
}
}
return stringValue;
}
}
}*/

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Specialized;
using System.Configuration;
namespace Config.Net.Stores
{
/// <summary>
/// Standard app.config (web.config) configuration store. Read-only.
/// </summary>
class AppConfigStore : IConfigStore
{
public string Name => "App.config";
public bool CanRead => true;
public bool CanWrite => false;
public string? Read(string key)
{
if(key == null) return null;
//first, look at appsettings and connection strings
string? value = ConfigurationManager.AppSettings[key] ?? ConfigurationManager.ConnectionStrings[key]?.ConnectionString;
if(value == null)
{
int idx = key.IndexOf('.');
if(idx != -1)
{
string sectionName = key.Substring(0, idx);
if(ConfigurationManager.GetSection(sectionName) is NameValueCollection nvsc)
{
string keyName = key.Substring(idx + 1);
value = nvsc[keyName];
}
}
}
return value;
}
public void Write(string key, string? value) => throw new NotSupportedException();
public void Dispose()
{
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Configuration;
using System.IO;
using System.Reflection;
namespace Config.Net.Stores
{
/// <summary>
/// Reads configuration from the .dll.config or .exe.config file.
/// </summary>
class AssemblyConfigStore : IConfigStore
{
private readonly Configuration _configuration;
/// <summary>
/// Creates a new instance of assembly configuration store (.dll.config files)
/// </summary>
/// <param name="assembly">reference to the assembly to look for</param>
public AssemblyConfigStore(Assembly assembly)
{
_configuration = ConfigurationManager.OpenExeConfiguration(assembly.Location);
}
/// <summary>
/// Store name
/// </summary>
public string Name => Path.GetFileName(_configuration.FilePath);
/// <summary>
/// Store is readable
/// </summary>
public bool CanRead => true;
/// <summary>
/// Store is not writeable
/// </summary>
public bool CanWrite => false;
/// <summary>
/// Reads the value by key
/// </summary>
public string? Read(string key)
{
KeyValueConfigurationElement element = _configuration.AppSettings.Settings[key];
return element?.Value;
}
/// <summary>
/// Writing is not supported
/// </summary>
public void Write(string key, string? value) => throw new NotSupportedException();
/// <summary>
/// Nothing to dispose
/// </summary>
public void Dispose()
{
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using Config.Net.Core;
namespace Config.Net.Stores
{
class DictionaryConfigStore : IConfigStore
{
private readonly IDictionary<string, string> _container;
public DictionaryConfigStore(IDictionary<string, string>? container = null)
{
_container = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
if(container != null)
{
foreach(KeyValuePair<string, string> item in container)
{
_container[item.Key] = item.Value;
}
}
}
public bool CanRead => true;
public bool CanWrite => true;
public void Dispose()
{
}
public string? Read(string key)
{
if (key == null) return null;
if (FlatArrays.IsArrayLength(key, k => _container.GetValueOrDefaultInternal(k), out int length))
{
return length.ToString();
}
if (FlatArrays.IsArrayElement(key, k => _container.GetValueOrDefaultInternal(k), out string? element))
{
return element;
}
return _container.GetValueOrDefaultInternal(key);
}
public void Write(string key, string? value)
{
if (key == null) return;
if (value == null)
{
_container.Remove(key);
}
else
{
_container[key] = value;
}
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using Config.Net.Core;
namespace Config.Net.Stores
{
/// <summary>
/// Uses system environment variables
/// </summary>
class EnvironmentVariablesStore : IConfigStore
{
/// <summary>
/// Readable
/// </summary>
public bool CanRead => true;
/// <summary>
/// Writeable
/// </summary>
public bool CanWrite => true;
/// <summary>
/// Store name
/// </summary>
public string Name => "System Environment";
/// <summary>
/// Reads value by key
/// </summary>
public string? Read(string key)
{
if (key == null) return null;
foreach(string variant in GetAllKeyVariants(key))
{
if (FlatArrays.IsArrayLength(variant, k => Environment.GetEnvironmentVariable(k), out int length))
{
return length.ToString();
}
if (FlatArrays.IsArrayElement(variant, k => Environment.GetEnvironmentVariable(k), out string? element))
{
return element;
}
string? value = Environment.GetEnvironmentVariable(variant);
if (value != null) return value;
}
return null;
}
/// <summary>
/// Writes value by key
/// </summary>
public void Write(string key, string? value)
{
Environment.SetEnvironmentVariable(key, value);
}
private IEnumerable<string> GetAllKeyVariants(string key)
{
var result = new List<string>();
result.Add(key);
result.Add(key.ToUpper().Replace(".", "_"));
return result;
}
/// <summary>
/// Nothing to dispose
/// </summary>
public void Dispose()
{
}
}
}

View File

@@ -0,0 +1,21 @@
namespace Config.Net.Stores.Formats.Ini
{
class IniComment : IniEntity
{
public const string CommentSeparator = ";";
public IniComment(string value)
{
Value = value;
}
public string Value { get; set; }
public string EscapedValue
{
get { return Value.Replace("\r", @"\r").Replace("\n", @"\n"); }
}
public override string ToString() => Value;
}
}

View File

@@ -0,0 +1,6 @@
namespace Config.Net.Stores.Formats.Ini
{
abstract class IniEntity
{
}
}

View File

@@ -0,0 +1,72 @@
using System;
namespace Config.Net.Stores.Formats.Ini
{
internal class IniKeyValue : IniEntity
{
public const string KeyValueSeparator = "=";
public IniKeyValue(string key, string value, string? comment)
{
if(key == null) throw new ArgumentNullException(nameof(key));
Key = key;
Value = value;
Comment = comment == null ? null : new IniComment(comment);
}
public string Key { get; }
public string Value { get; set; }
public string EscapedKey
{
get { return Key.Replace("\r", @"\r").Replace("\n", @"\n"); }
}
public string EscapedValue
{
get { return Value.Replace("\r", @"\r").Replace("\n", @"\n"); }
}
public IniComment? Comment { get; }
public static IniKeyValue? FromLine(string line, bool parseInlineComments, bool unescapeNewLines = false)
{
int idx = line.IndexOf(KeyValueSeparator, StringComparison.CurrentCulture);
if(idx == -1) return null;
string key = line.Substring(0, idx).Trim();
string value = line.Substring(idx + 1).Trim();
string? comment = null;
if (parseInlineComments)
{
idx = value.LastIndexOf(IniComment.CommentSeparator, StringComparison.CurrentCulture);
if (idx != -1)
{
comment = value.Substring(idx + 1).Trim();
value = value.Substring(0, idx).Trim();
}
}
if(unescapeNewLines)
{
key = UnescapeString(key);
value = UnescapeString(value);
comment = (comment != null) ? UnescapeString(comment) : null;
}
return new IniKeyValue(key, value, comment);
}
private static string UnescapeString(string key)
{
return key.Replace(@"\r", "\r").Replace(@"\n", "\n");
}
public override string ToString()
{
return $"{Value}";
}
}
}

View File

@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Config.Net.Stores.Formats.Ini
{
class IniSection
{
public const string SectionKeySeparator = ".";
private readonly List<IniEntity> _entities = new List<IniEntity>();
private readonly Dictionary<string, IniKeyValue> _keyToValue = new Dictionary<string, IniKeyValue>();
/// <summary>
/// Section name
/// </summary>
public string? Name { get; set; }
/// <summary>
///
/// </summary>
/// <param name="name">Pass null to work with global section</param>
public IniSection(string? name)
{
if(name != null)
{
if (name.StartsWith("[")) name = name.Substring(1);
if (name.EndsWith("]")) name = name.Substring(0, name.Length - 1);
}
Name = name;
}
public void Add(IniEntity entity)
{
_entities.Add(entity);
IniKeyValue? ikv = entity as IniKeyValue;
if(ikv != null)
{
_keyToValue[ikv.Key] = ikv;
}
}
public IniKeyValue? Set(string key, string? value)
{
if(value == null)
{
IniKeyValue? ikv;
if(_keyToValue.TryGetValue(key, out ikv))
{
_keyToValue.Remove(key);
return ikv;
}
return null;
}
else
{
IniKeyValue? ikv;
if(_keyToValue.TryGetValue(key, out ikv))
{
ikv.Value = value;
}
else
{
ikv = new IniKeyValue(key, value, null);
Add(ikv);
}
return ikv;
}
}
public static void SplitKey(string fullKey, out string? sectionName, out string keyName)
{
int idx = fullKey.IndexOf(SectionKeySeparator, StringComparison.CurrentCulture);
if(idx == -1)
{
sectionName = null;
keyName = fullKey;
}
else
{
sectionName = fullKey.Substring(0, idx);
keyName = fullKey.Substring(idx + 1);
}
}
public void WriteTo(StreamWriter writer)
{
foreach(IniEntity entity in _entities)
{
IniKeyValue? ikv = entity as IniKeyValue;
if(ikv != null)
{
writer.Write($"{ikv.EscapedKey}{IniKeyValue.KeyValueSeparator}{ikv.EscapedValue}");
if(ikv.Comment != null)
{
writer.Write(" ");
writer.Write(IniComment.CommentSeparator);
writer.Write(ikv.Comment.EscapedValue);
}
writer.WriteLine();
continue;
}
IniComment? comment = entity as IniComment;
if(comment != null)
{
writer.Write(IniComment.CommentSeparator);
writer.WriteLine(comment.Value);
}
}
}
public override string ToString()
{
return Name ?? string.Empty;
}
}
}

View File

@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Config.Net.Stores.Formats.Ini
{
class StructuredIniFile
{
private const string _sectionBegin = "[";
private const string _sectionEnd = "]";
private static readonly char[] _sectionTrims = {'[', ']'};
private readonly IniSection _globalSection;
private readonly List<IniSection> _sections = new List<IniSection>();
private readonly Dictionary<string, IniKeyValue> _fullKeyNameToValue = new Dictionary<string, IniKeyValue>(StringComparer.InvariantCultureIgnoreCase);
public StructuredIniFile()
{
_globalSection = new IniSection(null);
_sections.Add(_globalSection);
}
public string? this[string key]
{
get
{
if(key == null) return null;
IniKeyValue? value;
return !_fullKeyNameToValue.TryGetValue(key, out value) ? null : value.Value;
}
set
{
if(key == null) return;
IniSection.SplitKey(key, out string? sectionName, out string keyName);
IniSection? section = sectionName == null
? _globalSection
: _sections.FirstOrDefault(s => s.Name == sectionName);
if(section == null)
{
section = new IniSection(sectionName);
_sections.Add(section);
}
IniKeyValue? ikv = section.Set(keyName, value);
//update the local cache
if(ikv != null)
{
if(value == null)
{
_fullKeyNameToValue.Remove(key);
}
else
{
_fullKeyNameToValue[key] = ikv;
}
}
}
}
public static StructuredIniFile ReadFrom(Stream inputStream, bool parseInlineComments, bool unescapeNewLines = false)
{
if(inputStream == null) throw new ArgumentNullException(nameof(inputStream));
var file = new StructuredIniFile();
using(var reader = new StreamReader(inputStream))
{
IniSection section = file._globalSection;
string? line;
while((line = reader.ReadLine()) != null)
{
line = line.Trim();
if(line.StartsWith(_sectionBegin))
{
//start new section
line = line.Trim();
section = new IniSection(line);
file._sections.Add(section);
}
else if(line.StartsWith(IniComment.CommentSeparator))
{
//whole line is a comment
string comment = line.Substring(1).Trim();
section.Add(new IniComment(comment));
}
else
{
IniKeyValue? ikv = IniKeyValue.FromLine(line, parseInlineComments, unescapeNewLines);
if(ikv == null) continue;
section.Add(ikv);
string fullKey = section.Name == null
? ikv.Key
: $"{section.Name}{IniSection.SectionKeySeparator}{ikv.Key}";
file._fullKeyNameToValue[fullKey] = ikv;
}
}
}
return file;
}
public void WriteTo(Stream outputStream)
{
if(outputStream == null) throw new ArgumentNullException(nameof(outputStream));
using(var writer = new StreamWriter(outputStream))
{
foreach(IniSection section in _sections)
{
if(section.Name != null)
{
writer.WriteLine();
writer.WriteLine($"{_sectionBegin}{section.Name}{_sectionEnd}");
}
section.WriteTo(writer);
}
}
}
//private static
}
}

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using Config.Net.Core;
using Config.Net.TypeParsers;
namespace Config.Net.Stores.Impl.CommandLine
{
class CommandLineConfigStore : IConfigStore
{
private readonly Dictionary<string, string> _nameToValue;
private static readonly char[] ArgPrefixes = new[] { '-', '/' };
private static readonly string[] ArgDelimiters = new[] { "=", ":" };
private readonly bool _isCaseSensitive;
public bool CanRead => true;
public bool CanWrite => false;
public CommandLineConfigStore(string[]? args = null, bool isCaseSensitive = false, IEnumerable<KeyValuePair<string, int>>? nameToPosition = null)
{
_isCaseSensitive = isCaseSensitive;
_nameToValue = new Dictionary<string, string>(_isCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase);
Parse(args ?? Environment.GetCommandLineArgs(), nameToPosition);
}
public void Dispose()
{
}
public string? Read(string key)
{
if (key == null) return null;
if(FlatArrays.IsArrayLength(key, k => _nameToValue.GetValueOrDefaultInternal(k), out int length))
{
return length.ToString();
}
if(FlatArrays.IsArrayElement(key, k => _nameToValue.GetValueOrDefaultInternal(k), out string? element))
{
return element;
}
string? value;
_nameToValue.TryGetValue(key, out value);
return value;
}
private string[]? GetAsArray(string key)
{
if (!_nameToValue.TryGetValue(key, out string? allString)) return null;
if (!StringArrayParser.TryParse(allString, out string[]? ar)) return null;
return ar;
}
public void Write(string key, string? value)
{
throw new NotSupportedException("command line cannot be written to");
}
private void Parse(string[] args, IEnumerable<KeyValuePair<string, int>>? nameToPosition)
{
_nameToValue.Clear();
var posToName = new Dictionary<int, string>();
if (nameToPosition != null)
{
foreach(KeyValuePair<string, int> p in nameToPosition)
{
if (p.Key != null)
{
posToName[p.Value] = p.Key;
}
}
}
if (args == null) return;
for (int i = 0; i < args.Length; i++)
{
string? name;
string? value;
Tuple<string, string?>? nameValue = Utils.SplitByDelimiter(args[i], ArgDelimiters);
name = nameValue?.Item1.TrimStart(ArgPrefixes);
value = nameValue?.Item2;
if (name != null && value != null)
{
_nameToValue[name] = value;
}
else if(name != null && posToName.TryGetValue(i, out string? ptnName))
{
_nameToValue[ptnName] = args[i];
}
}
}
}
}

View File

@@ -0,0 +1,120 @@
using System;
using System.IO;
using System.Text;
using Config.Net.Core;
using Config.Net.Stores.Formats.Ini;
namespace Config.Net.Stores
{
/// <summary>
/// Simple INI storage.
/// </summary>
class IniFileConfigStore : IConfigStore
{
private readonly string? _fullName;
private readonly StructuredIniFile _iniFile;
/// <summary>
///
/// </summary>r
/// <param name="name">File does not have to exist, however it will be created as soon as you
/// try to write to it</param>
public IniFileConfigStore(string name, bool isFilePath, bool parseInlineComments, bool unescapeNewLines = false)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (isFilePath)
{
_fullName = Path.GetFullPath(name); // Allow relative path to INI file
string? parentDirPath = Path.GetDirectoryName(_fullName);
if (string.IsNullOrEmpty(parentDirPath)) throw new IOException("the provided directory path is not valid");
if (!Directory.Exists(parentDirPath))
{
Directory.CreateDirectory(parentDirPath);
}
_iniFile = ReadIniFile(_fullName, parseInlineComments, unescapeNewLines);
CanWrite = true;
}
else
{
_iniFile = ReadIniContent(name, parseInlineComments, unescapeNewLines);
CanWrite = false;
}
CanRead = true;
}
public string Name => ".ini";
public bool CanRead { get; }
public bool CanWrite { get; }
public string? Read(string key)
{
if (FlatArrays.IsArrayLength(key, k => _iniFile[k], out int length))
{
return length.ToString();
}
if (FlatArrays.IsArrayElement(key, k => _iniFile[k], out string? element))
{
return element;
}
return _iniFile[key];
}
public void Write(string key, string? value)
{
if (!CanWrite) return;
_iniFile[key] = value;
WriteIniFile();
}
private static StructuredIniFile ReadIniFile(string fullName, bool parseInlineComments, bool unescapeNewLines = false)
{
FileInfo iniFile = new FileInfo(fullName);
if(iniFile.Exists)
{
using(FileStream stream = iniFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
return StructuredIniFile.ReadFrom(stream, parseInlineComments, unescapeNewLines);
}
}
else
{
return new StructuredIniFile();
}
}
private static StructuredIniFile ReadIniContent(string content, bool parseInlineComments, bool unescapeNewLines = false)
{
using (Stream input = new MemoryStream(Encoding.UTF8.GetBytes(content)))
{
return StructuredIniFile.ReadFrom(input, parseInlineComments, unescapeNewLines);
}
}
private void WriteIniFile()
{
if (_fullName == null) return;
using(FileStream stream = File.Create(_fullName))
{
_iniFile.WriteTo(stream);
}
}
public void Dispose()
{
//nothing to dispose
}
}
}

View File

@@ -0,0 +1,165 @@
using System;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
using System.Text.Json;
using System.Text.Json.Nodes;
using Config.Net.Core;
namespace Config.Net.Stores
{
/// <summary>
/// Simple JSON storage using System.Text.Json
/// </summary>
public class JsonConfigStore : IConfigStore
{
private readonly string? _pathName;
private JsonNode? _j;
/// <summary>
/// Create JSON storage in the file specified in <paramref name="name"/>.
/// </summary>
/// <param name="name">Name of the file, either path to JSON storage file, or json file content.</param>
/// <param name="isFilePath">Set to true if <paramref name="name"/> specifies file name, otherwise false. </param>
/// <exception cref="ArgumentNullException"><paramref name="name"/> is null.</exception>
/// <exception cref="IOException">Provided path is not valid.</exception>
/// <remarks>Storage file does not have to exist, however it will be created as soon as first write performed.</remarks>
public JsonConfigStore(string name, bool isFilePath)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (isFilePath)
{
_pathName = Path.GetFullPath(name); // Allow relative path to JSON file
_j = ReadJsonFile(_pathName);
}
else
{
_j = ReadJsonString(name);
}
}
public void Dispose()
{
// nothing to dispose.
}
public string Name => "json";
public bool CanRead => true;
public bool CanWrite => _pathName != null;
public string? Read(string rawKey)
{
if (string.IsNullOrEmpty(rawKey) || _j == null) return null;
bool isLength = OptionPath.TryStripLength(rawKey, out string? key);
if (key == null) return null;
string[] parts = key.Split('.');
if (parts.Length == 0) return null;
JsonNode? node = _j;
foreach (string rawPart in parts)
{
bool isIndex = OptionPath.TryStripIndex(rawPart, out string? part, out int partIndex);
if (part == null) return null;
node = node![part];
if (node == null) return null;
if (isIndex)
{
if (!(node is JsonArray ja)) return null;
if (partIndex < ja.Count)
{
node = ja[partIndex];
}
else
return null;
}
}
if (isLength)
return node is JsonArray ja ? ja.Count.ToString() : null;
return node!.ToString();
}
public void Write(string key, string? value)
{
if (string.IsNullOrEmpty(_pathName))
throw new InvalidOperationException("please specify file name for writeable config");
if (_j == null) _j = new JsonObject();
// navigate to target element, create if needed
string[] parts = key.Split('.');
if (parts.Length == 0) return;
JsonNode? node = _j;
string? lastPart = null;
foreach (string rawPart in parts)
{
bool isIndex = OptionPath.TryStripIndex(rawPart, out string? part, out int partIndex);
if (part == null) return;
lastPart = part;
JsonNode? nextNode = node[part];
if (isIndex)
{
throw new NotImplementedException();
}
else
{
if (nextNode == null)
{
//create missing node
nextNode = new JsonObject();
node[part] = nextNode;
}
}
node = nextNode;
}
JsonObject? parent = node.Parent as JsonObject;
parent!.Remove(lastPart!);
parent![lastPart!] = JsonValue.Create(value);
string js = _j.ToJsonString(new JsonSerializerOptions { WriteIndented = true });
FileInfo file = new FileInfo(_pathName);
if (file is not null)
{
if (file.Directory is not null)
{
file.Directory.Create();
}
}
File.WriteAllText(_pathName, js);
}
private static JsonNode? ReadJsonFile(string fileName)
{
if (File.Exists(fileName))
{
string json = File.ReadAllText(fileName);
return ReadJsonString(json);
}
return null;
}
private static JsonNode? ReadJsonString(string jsonString)
{
return JsonNode.Parse(jsonString);
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Config.Net.TypeParsers
{
class ByteParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(byte) };
public bool TryParse(string? value, Type t, out object? result)
{
if(value == null)
{
result = false;
return false;
}
byte ir;
bool parsed;
if (value.StartsWith("0x"))
{
parsed = byte.TryParse(value.Substring(2), NumberStyles.HexNumber, TypeParserSettings.DefaultCulture, out ir);
}
else
{
parsed = byte.TryParse(value, NumberStyles.Integer, TypeParserSettings.DefaultCulture, out ir);
}
result = ir;
return parsed;
}
public string? ToRawString(object? value)
{
return ((byte?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Config.Net.TypeParsers
{
/// <summary>
/// Container for core types. Eventually all simple parsers will merge into this class.
/// </summary>
class CoreParsers : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(Uri), typeof(bool), typeof(Guid), typeof(DateTime) };
private static readonly Dictionary<string, bool> BooleanTrueValues =
new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase)
{
{"true", true},
{"yes", true},
{"1", true},
};
public string? ToRawString(object? value)
{
if (value == null) return null;
Type t = value.GetType();
if(t == typeof(Uri))
return value.ToString();
if(t == typeof(bool))
return value?.ToString()?.ToLowerInvariant();
if (t == typeof(Guid))
return value.ToString();
if(t == typeof(DateTime))
return ((DateTime)value).ToUniversalTime().ToString("u");
return null;
}
public bool TryParse(string? value, Type t, out object? result)
{
if(value == null)
{
result = null;
return false;
}
if(t == typeof(Uri))
{
Uri uri = new Uri(value);
result = uri;
return true;
}
if(t == typeof(bool))
{
if(BooleanTrueValues.ContainsKey(value))
{
result = true;
return true;
}
result = false;
return true;
}
if(t == typeof(Guid))
{
if(Guid.TryParse(value, out Guid tg))
{
result = tg;
return true;
}
result = null;
return false;
}
if(t == typeof(DateTime))
{
DateTime dateResult;
bool parsed = DateTime.TryParse(value, TypeParserSettings.DefaultCulture, DateTimeStyles.None, out dateResult);
result = dateResult;
return parsed;
}
result = null;
return false;
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Config.Net.TypeParsers
{
class DecimalParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(decimal) };
public bool TryParse(string? value, Type t, out object? result)
{
decimal dr;
bool parsed = decimal.TryParse(value, NumberStyles.Float, TypeParserSettings.DefaultCulture, out dr);
result = dr;
return parsed;
}
public string? ToRawString(object? value)
{
return ((decimal?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Reflection;
namespace Config.Net.TypeParsers
{
class DefaultParser
{
public bool TryParse(string? value, Type t, out object? result)
{
if(IsEnum(t))
{
if(value == null)
{
result = null;
return false;
}
try
{
result = Enum.Parse(t, value, true);
return true;
}
catch(ArgumentException)
{
}
catch(OverflowException)
{
}
result = null;
return false;
}
throw new NotSupportedException();
}
public bool IsSupported(Type t)
{
return IsEnum(t);
}
public string? ToRawString(object? value)
{
if(value == null) return null;
Type t = value.GetType();
if(IsEnum(t))
{
return value.ToString();
}
throw new NotSupportedException();
}
static bool IsEnum(Type t)
{
if(t == null) return false;
//try to get the underlying type if this is a nullable type
Type? nullable = Nullable.GetUnderlyingType(t);
if(nullable != null) t = nullable;
return t.GetTypeInfo().IsEnum;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Config.Net.TypeParsers
{
class DoubleParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(double) };
public bool TryParse(string? value, Type t, out object? result)
{
double dr;
bool parsed = double.TryParse(value, NumberStyles.Float, TypeParserSettings.DefaultCulture, out dr);
result = dr;
return parsed;
}
public string? ToRawString(object? value)
{
return ((double?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Config.Net.TypeParsers
{
class FloatParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(float) };
public bool TryParse(string? value, Type t, out object? result)
{
float dr;
bool parsed = float.TryParse(value, NumberStyles.Float, TypeParserSettings.DefaultCulture, out dr);
result = dr;
return parsed;
}
public string? ToRawString(object? value)
{
return ((float?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Config.Net.TypeParsers
{
class IntParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(int) };
public bool TryParse(string? value, Type t, out object? result)
{
int ir;
bool parsed = int.TryParse(value, NumberStyles.Integer, TypeParserSettings.DefaultCulture, out ir);
result = ir;
return parsed;
}
public string? ToRawString(object? value)
{
return ((int?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Config.Net.TypeParsers
{
class LongParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(long) };
public bool TryParse(string? value, Type t, out object? result)
{
long lr;
bool parsed = long.TryParse(value, NumberStyles.Integer, TypeParserSettings.DefaultCulture, out lr);
result = lr;
return parsed;
}
public string? ToRawString(object? value)
{
return ((long?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace Config.Net.TypeParsers
{
class NetworkCredentialParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(NetworkCredential) };
public string? ToRawString(object? value)
{
NetworkCredential? nc = value as NetworkCredential;
return Utils.ToFriendlyString(nc);
}
public bool TryParse(string? value, Type t, out object? result)
{
NetworkCredential? nc = value.ToNetworkCredential();
result = nc;
return true;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Config.Net.TypeParsers
{
class SByteParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(sbyte) };
public bool TryParse(string? value, Type t, out object? result)
{
sbyte ir;
bool parsed = sbyte.TryParse(value, NumberStyles.Integer, TypeParserSettings.DefaultCulture, out ir);
result = ir;
return parsed;
}
public string? ToRawString(object? value)
{
return ((sbyte?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Config.Net.TypeParsers
{
class ShortParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(short) };
public bool TryParse(string? value, Type t, out object? result)
{
short ir;
bool parsed = short.TryParse(value, NumberStyles.Integer, TypeParserSettings.DefaultCulture, out ir);
result = ir;
return parsed;
}
public string? ToRawString(object? value)
{
return ((short?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Config.Net.TypeParsers
{
class StringArrayParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(string[]) };
public bool TryParse(string? value, Type t, out object? result)
{
if (value == null)
{
result = null;
return false;
}
result = ParseAsArray(value);
return true;
}
public static bool TryParse(string? value, out string[]? result)
{
if(value == null)
{
result = null;
return false;
}
result = ParseAsArray(value);
return true;
}
public string? ToRawString(object? value)
{
string[]? arv = (string[]?)value;
if (arv == null || arv.Length == 0) return null;
return string.Join(" ", arv.Select(Escape));
}
private static string Escape(string s)
{
string s1 = s.Replace("\"", "\"\"");
return (s == s1 && !s.Contains(" "))
? s
: $"\"{s1}\"";
}
private static string[] ParseAsArray(string s)
{
var a = new List<string>();
string v = string.Empty;
int state = 0;
for(int i = 0; i < s.Length;)
{
char ch = s[i];
switch(state)
{
case 0: //default
if (ch == '\"')
{
state = 2;
}
else if(ch == ' ')
{
//skip spaces in default mode
}
else
{
v += ch;
state = 1;
}
i++;
break;
case 1: //reading unquoted value
if (ch == ' ')
{
a.Add(v);
v = string.Empty;
state = 0;
}
else
{
v += ch;
}
i++;
break;
case 2: //reading quoted value
if(ch == '\"')
{
state = 3;
}
else
{
v += ch;
}
i++;
break;
case 3: //after quote in quoted mode
if (ch == '\"')
{
v += ch;
state = 2;
}
else
{
a.Add(v);
v = string.Empty;
state = 0;
}
i++;
break;
}
}
if(!string.IsNullOrEmpty(v))
{
a.Add(v);
}
return a.ToArray();
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace Config.Net.TypeParsers
{
class StringParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(string) };
public bool TryParse(string? value, Type t, out object? result)
{
result = value;
return value != null;
}
public string? ToRawString(object? value)
{
return value as string;
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace Config.Net.TypeParsers
{
class TimeSpanParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(TimeSpan) };
public bool TryParse(string? value, Type t, out object? result)
{
TimeSpan ts;
bool parsed = TimeSpan.TryParse(value, TypeParserSettings.DefaultCulture, out ts);
result = ts;
return parsed;
}
public string? ToRawString(object? value)
{
return ((TimeSpan?)value)?.ToString(null, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,11 @@
using System.Globalization;
namespace Config.Net.TypeParsers
{
internal static class TypeParserSettings
{
public const string DefaultNumericFormat = "G";
public static readonly CultureInfo DefaultCulture = CultureInfo.InvariantCulture;
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Config.Net.TypeParsers
{
class UIntParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(uint) };
public bool TryParse(string? value, Type t, out object? result)
{
uint ir;
bool parsed = uint.TryParse(value, NumberStyles.Integer, TypeParserSettings.DefaultCulture, out ir);
result = ir;
return parsed;
}
public string? ToRawString(object? value)
{
return ((uint?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Config.Net.TypeParsers
{
class ULongParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(ulong) };
public bool TryParse(string? value, Type t, out object? result)
{
ulong ir;
bool parsed = ulong.TryParse(value, NumberStyles.Integer, TypeParserSettings.DefaultCulture, out ir);
result = ir;
return parsed;
}
public string? ToRawString(object? value)
{
return ((ulong?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Config.Net.TypeParsers
{
class UShortParser : ITypeParser
{
public IEnumerable<Type> SupportedTypes => new[] { typeof(ushort) };
public bool TryParse(string? value, Type t, out object? result)
{
ushort ir;
bool parsed = ushort.TryParse(value, NumberStyles.Integer, TypeParserSettings.DefaultCulture, out ir);
result = ir;
return parsed;
}
public string? ToRawString(object? value)
{
return ((ushort?)value)?.ToString(TypeParserSettings.DefaultNumericFormat, TypeParserSettings.DefaultCulture);
}
}
}

87
Config.Net/Utils.cs Normal file
View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
namespace Config.Net
{
static class Utils
{
public static NetworkCredential? ToNetworkCredential(this string? s)
{
if (s == null) return null;
Tuple<string, string?>? credsAndDomain = SplitByDelimiter(s, "@");
string? creds = credsAndDomain?.Item1;
string? domain = credsAndDomain?.Item2;
Tuple<string, string?>? usernameAndPassword = SplitByDelimiter(creds, ":");
string? username = usernameAndPassword?.Item1;
string? password = usernameAndPassword?.Item2;
return new NetworkCredential(username, password, domain);
}
public static string? ToFriendlyString(NetworkCredential? credential)
{
if (credential == null) return null;
string usernameAndPassword;
if (string.IsNullOrEmpty(credential.UserName) && string.IsNullOrEmpty(credential.Password))
{
usernameAndPassword = string.Empty;
}
else if (string.IsNullOrEmpty(credential.UserName))
{
usernameAndPassword = $":{credential.Password}";
}
else if (string.IsNullOrEmpty(credential.Password))
{
usernameAndPassword = credential.UserName;
}
else
{
usernameAndPassword = $"{credential.UserName}:{credential.Password}";
}
return string.IsNullOrEmpty(credential.Domain)
? usernameAndPassword
: $"{usernameAndPassword}@{credential.Domain}";
}
public static Tuple<string, string?>? SplitByDelimiter(string? s, params string[] delimiter)
{
if (s == null) return null;
string key;
string? value;
if (delimiter == null || delimiter.Length == 0)
{
key = s.Trim();
value = null;
}
else
{
List<int> indexes = delimiter.Where(d => d != null).Select(d => s.IndexOf(d)).Where(d => d != -1).ToList();
if (indexes.Count == 0)
{
key = s.Trim();
value = null;
}
else
{
int idx = indexes.OrderBy(i => i).First();
key = s.Substring(0, idx);
value = s.Substring(idx + 1);
}
}
return new Tuple<string, string?>(key, value);
}
}
}