Skip to content

Commit

Permalink
add ini parser
Browse files Browse the repository at this point in the history
  • Loading branch information
CypherPotato committed Aug 8, 2024
1 parent 5e269d3 commit 1a69e86
Show file tree
Hide file tree
Showing 5 changed files with 613 additions and 0 deletions.
111 changes: 111 additions & 0 deletions extensions/Sisk.IniConfiguration/IniConfigurationPipeline.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using Sisk.Core.Http;
using Sisk.Core.Http.Hosting;
using Sisk.IniConfiguration.Parser;
using System.Text;

namespace Sisk.IniConfiguration;

/// <summary>
/// Provides an INI-Document based configuration-reader pipeline.
/// </summary>
public sealed class IniConfigurationPipeline : IConfigurationReader
{
/// <inheritdoc/>
public void ReadConfiguration(ConfigurationContext context)
{
IniDocument document = IniDocument.FromFile(context.ConfigurationFile);

string parsingNode = "";
try
{
var serverSection = document.GetSection("Server");
if (serverSection is not null)
{
parsingNode = "Server.Listen";
string[] listeningPorts = serverSection.GetMany("Listen");
context.TargetListeningHost.Ports = listeningPorts.Select(n => ListeningPort.Parse(n, null)).ToArray();

parsingNode = "Server.Encoding";
if (serverSection.GetOne("Encoding") is { } encoding)
context.Host.ServerConfiguration.DefaultEncoding = Encoding.GetEncoding(encoding);

parsingNode = "Server.MaximumContentLength";
if (serverSection.GetOne("MaximumContentLength") is { } MaximumContentLength)
context.Host.ServerConfiguration.MaximumContentLength = Int32.Parse(MaximumContentLength);

parsingNode = "Server.IncludeRequestIdHeader";
if (serverSection.GetOne("IncludeRequestIdHeader") is { } IncludeRequestIdHeader)
context.Host.ServerConfiguration.IncludeRequestIdHeader = IniParser.IniNamingComparer.Compare(IncludeRequestIdHeader, "true") == 0;

parsingNode = "Server.ThrowExceptions";
if (serverSection.GetOne("ThrowExceptions") is { } ThrowExceptions)
context.Host.ServerConfiguration.ThrowExceptions = IniParser.IniNamingComparer.Compare(ThrowExceptions, "true") == 0;

parsingNode = "Server.AccessLogsStream";
if (serverSection.GetOne("AccessLogsStream") is { } AccessLogsStream)
context.Host.ServerConfiguration.AccessLogsStream = string.Compare(AccessLogsStream, "console", true) == 0 ?
LogStream.ConsoleOutput : new LogStream(AccessLogsStream);

parsingNode = "Server.ErrorsLogsStream";
if (serverSection.GetOne("ErrorsLogsStream") is { } ErrorsLogsStream)
context.Host.ServerConfiguration.ErrorsLogsStream = string.Compare(ErrorsLogsStream, "console", true) == 0 ?
LogStream.ConsoleOutput : new LogStream(ErrorsLogsStream);
}

var paramsSection = document.GetSection("Parameters");
if (paramsSection is not null)
{
foreach (string key in paramsSection.Keys)
{
string? value = paramsSection.GetOne(key);
if (value is not null)
context.Parameters.Add(key, value);
}
}

parsingNode = "Cors";
var corsSection = document.GetSection("CORS");
if (corsSection is not null)
{
parsingNode = "Cors.AllowMethods";
if (corsSection.GetOne("AllowMethods") is { } AllowMethods)
context.TargetListeningHost.CrossOriginResourceSharingPolicy.AllowMethods
= AllowMethods.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);

parsingNode = "Cors.AllowHeaders";
if (corsSection.GetOne("AllowHeaders") is { } AllowHeaders)
context.TargetListeningHost.CrossOriginResourceSharingPolicy.AllowHeaders
= AllowHeaders.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);

parsingNode = "Cors.AllowOrigins";
if (corsSection.GetOne("AllowOrigins") is { } AllowOrigins)
context.TargetListeningHost.CrossOriginResourceSharingPolicy.AllowOrigins
= AllowOrigins.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);

parsingNode = "Cors.AllowOrigin";
if (corsSection.GetOne("AllowOrigin") is { } AllowOrigin)
context.TargetListeningHost.CrossOriginResourceSharingPolicy.AllowOrigin
= AllowOrigin;

parsingNode = "Cors.ExposeHeaders";
if (corsSection.GetOne("ExposeHeaders") is { } ExposeHeaders)
context.TargetListeningHost.CrossOriginResourceSharingPolicy.ExposeHeaders
= ExposeHeaders.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);

parsingNode = "Cors.AllowCredentials";
if (corsSection.GetOne("AllowCredentials") is { } AllowCredentials)
context.TargetListeningHost.CrossOriginResourceSharingPolicy.AllowCredentials
= string.Compare(AllowCredentials, "True", true) == 0;

parsingNode = "Cors.MaxAge";
if (corsSection.GetOne("MaxAge") is { } MaxAge)
context.TargetListeningHost.CrossOriginResourceSharingPolicy.MaxAge
= TimeSpan.FromSeconds(int.Parse(MaxAge));
}
}
catch (Exception ex)
{
throw new Exception($"Caught exception while trying to read the property {parsingNode}: {ex.Message}", ex);
}
}
}
90 changes: 90 additions & 0 deletions extensions/Sisk.IniConfiguration/IniDocument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using Sisk.IniConfiguration.Parser;
using System.Text;

namespace Sisk.IniConfiguration;

/// <summary>
/// Represents an INI configuration document.
/// </summary>
public sealed class IniDocument
{
/// <summary>
/// Gets all INI sections defined in this INI document.
/// </summary>
public IniSection[] Sections { get; }

/// <summary>
/// Gets the global INI section, which is the primary section in the document.
/// </summary>
public IniSection Global { get => Sections[0]; }

/// <summary>
/// Creates an new <see cref="IniDocument"/> document from the specified
/// string, reading it as an UTF-8 string.
/// </summary>
/// <param name="iniConfiguration">The UTF-8 string.</param>
public static IniDocument FromString(string iniConfiguration)
{
using TextReader reader = new StringReader(iniConfiguration);
using IniParser parser = new IniParser(reader);
return parser.Parse();
}

/// <summary>
/// Creates an new <see cref="IniDocument"/> document from the specified
/// file using the specified encoding.
/// </summary>
/// <param name="filePath">The absolute or relative file path to the INI document.</param>
/// <param name="encoding">Optional. The encoding used to read the file. Defaults to UTF-8.</param>
public static IniDocument FromFile(string filePath, Encoding? encoding = null)
{
using TextReader reader = new StreamReader(filePath, encoding ?? Encoding.UTF8);
using IniParser parser = new IniParser(reader);
return parser.Parse();
}

/// <summary>
/// Creates an new <see cref="IniDocument"/> document from the specified
/// stream using the specified encoding.
/// </summary>
/// <param name="stream">The input stream where the INI document is.</param>
/// <param name="encoding">Optional. The encoding used to read the stream. Defaults to UTF-8.</param>
public static IniDocument FromStream(Stream stream, Encoding? encoding = null)
{
using TextReader reader = new StreamReader(stream, encoding ?? Encoding.UTF8);
using IniParser parser = new IniParser(reader);
return parser.Parse();
}

/// <summary>
/// Creates an new <see cref="IniDocument"/> document from the specified
/// <see cref="TextReader"/>.
/// </summary>
/// <param name="reader">The <see cref="TextReader"/> instance.</param>
public static IniDocument FromStream(TextReader reader)
{
using IniParser parser = new IniParser(reader);
return parser.Parse();
}

internal IniDocument(IniSection[] sections)
{
Sections = sections;
}

/// <summary>
/// Gets an defined INI section from this document. The search is case-insensitive.
/// </summary>
/// <param name="sectionName">The section name.</param>
/// <returns>The <see cref="IniSection"/> object if found, or null if not defined.</returns>
public IniSection? GetSection(string sectionName)
{
for (int i = 0; i < Sections.Length; i++)
{
IniSection section = Sections[i];
if (IniParser.IniNamingComparer.Compare(section.Name, sectionName) == 0)
return section;
}
return null;
}
}
141 changes: 141 additions & 0 deletions extensions/Sisk.IniConfiguration/IniSection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using Sisk.IniConfiguration.Parser;
using System.Collections;
using System.Diagnostics.CodeAnalysis;

namespace Sisk.IniConfiguration;

/// <summary>
/// Represents an INI section, which contains it's own properties.
/// </summary>
public sealed class IniSection : IReadOnlyDictionary<string, string[]>
{
internal (string, string)[] items;

/// <summary>
/// Gets the INI section name.
/// </summary>
public string Name { get; }

internal IniSection(string name, (string, string)[] items)
{
this.items = items;
Name = name;
}

/// <summary>
/// Gets all values associated with the specified property name, performing an case-insensitive search.
/// </summary>
/// <param name="key">The property name.</param>
public string[] this[string key]
{
get
{
return items
.Where(k => IniParser.IniNamingComparer.Compare(key, k.Item1) == 0)
.Select(k => k.Item2)
.ToArray();
}
}

/// <summary>
/// Gets all keys defined in this INI section, without duplicates.
/// </summary>
public IEnumerable<string> Keys
{
get
{
return items.Select(i => i.Item1).Distinct().ToArray();
}
}

/// <summary>
/// Gets all values defined in this INI section.
/// </summary>
public IEnumerable<string[]> Values
{
get
{
using (var e = GetEnumerator())
{
while (e.MoveNext())
{
yield return e.Current.Value;
}
}
}
}

/// <summary>
/// Gets the number of properties in this INI section.
/// </summary>
public int Count => items.Length;

/// <summary>
/// Gets the last value defined in this INI section by their property name.
/// </summary>
/// <param name="key">The property name.</param>
/// <returns>The last value associated with the specified property name, or null if nothing is found.</returns>
public string? GetOne(string key)
{
return items
.Where(k => IniParser.IniNamingComparer.Compare(key, k.Item1) == 0)
.Select(k => k.Item2)
.LastOrDefault();
}

/// <summary>
/// Gets all values defined in this INI section by their property name.
/// </summary>
/// <param name="key">The property name.</param>
/// <returns>All values associated with the specified property name.</returns>
public string[] GetMany(string key)
{
return this[key];
}

/// <summary>
/// Gets an boolean indicating if the specified key/property name is
/// defined in this <see cref="IniSection"/>.
/// </summary>
/// <param name="key">The property name.</param>
/// <returns>An <see cref="bool"/> indicating if the specified property name is defined or not.</returns>
public bool ContainsKey(string key)
{
for (int i = 0; i < items.Length; i++)
{
(string, string?) item = items[i];

if (IniParser.IniNamingComparer.Compare(item.Item1, key) == 0)
return true;
}
return false;
}

/// <inheritdoc/>
public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator()
{
string[] keysDistinct = items.Select(i => i.Item1).Distinct().ToArray();

foreach (string key in keysDistinct)
{
string[] valuesByKey = items
.Where(i => i.Item1 == key)
.Select(i => i.Item2)
.ToArray();

yield return new KeyValuePair<string, string[]>(key, valuesByKey);
}
}

/// <inheritdoc/>
public bool TryGetValue(string key, [MaybeNullWhen(false)] out string[] value)
{
value = this[key];
return value.Length > 0;
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Loading

0 comments on commit 1a69e86

Please sign in to comment.