Compare commits
13 Commits
2b5d6b6a54
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d55c1c533 | |||
| e4ee03364c | |||
| c77b3e0742 | |||
| e391f0238a | |||
| 823169ca91 | |||
| ee20c9c5ec | |||
| af658b1656 | |||
| 26c118195c | |||
| c461418185 | |||
| 29cde170c6 | |||
| 448161239e | |||
| a4c2ae3e28 | |||
| 1eb208cba3 |
Submodule DTLib.Demystifier updated: bb96774c37...4eaade6e92
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<!--package info-->
|
||||
<PackageId>DTLib.Logging.Microsoft</PackageId>
|
||||
<Version>1.1.1</Version>
|
||||
<Version>1.1.3</Version>
|
||||
<Authors>Timerix</Authors>
|
||||
<Description>DTLib logger wrapper with dependency injection</Description>
|
||||
<RepositoryType>GIT</RepositoryType>
|
||||
@@ -11,7 +11,7 @@
|
||||
<Configuration>Release</Configuration>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<!--compilation properties-->
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<!--language features-->
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>disable</Nullable>
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<!--external dependencies-->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--DTLib dependencies-->
|
||||
@@ -28,6 +28,6 @@
|
||||
<ProjectReference Include="..\DTLib\DTLib.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
|
||||
<PackageReference Include="DTLib" Version="1.6.*" />
|
||||
<PackageReference Include="DTLib" Version="1.7.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<!--package info-->
|
||||
<PackageId>DTLib.Web</PackageId>
|
||||
<Version>1.1.2</Version>
|
||||
<Version>1.4.0</Version>
|
||||
<Authors>Timerix</Authors>
|
||||
<Description>HTTP Server with simple routing</Description>
|
||||
<RepositoryType>GIT</RepositoryType>
|
||||
@@ -11,7 +11,7 @@
|
||||
<Configuration>Release</Configuration>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<!--compilation properties-->
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
<!--language features-->
|
||||
<LangVersion>latest</LangVersion>
|
||||
@@ -25,6 +25,6 @@
|
||||
<ProjectReference Include="..\DTLib\DTLib.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
|
||||
<PackageReference Include="DTLib" Version="1.6.5" />
|
||||
<PackageReference Include="DTLib" Version="1.7.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
18
DTLib.Web/HttpMethod.cs
Normal file
18
DTLib.Web/HttpMethod.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace DTLib.Web;
|
||||
|
||||
/// <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods"/>
|
||||
[Flags]
|
||||
public enum HttpMethod : ushort
|
||||
{
|
||||
NONE = 0,
|
||||
GET = 1,
|
||||
POST = 2,
|
||||
PUT = 4,
|
||||
DELETE = 8,
|
||||
PATCH = 16,
|
||||
HEAD = 32,
|
||||
OPTIONS = 64,
|
||||
TRACE = 128,
|
||||
CONNECT = 256,
|
||||
ANY = 65535
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
namespace DTLib.Web.Routes;
|
||||
|
||||
public class DelegateRouteHandler(Func<HttpListenerContext, Task<HttpStatusCode>> routeHandler) : RouteHandler
|
||||
public class DelegateRouteHandler(
|
||||
Func<HttpListenerContext, ContextLogger, Task<HttpStatusCode>> routeHandler
|
||||
) : IRouteHandler
|
||||
{
|
||||
public override Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx) => routeHandler(ctx);
|
||||
public Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger)
|
||||
=> routeHandler(ctx, requestLogger);
|
||||
}
|
||||
6
DTLib.Web/Routes/IRouteHandler.cs
Normal file
6
DTLib.Web/Routes/IRouteHandler.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace DTLib.Web.Routes;
|
||||
|
||||
public interface IRouteHandler
|
||||
{
|
||||
Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger);
|
||||
}
|
||||
@@ -2,5 +2,5 @@ namespace DTLib.Web.Routes;
|
||||
|
||||
public interface IRouter
|
||||
{
|
||||
Task<HttpStatusCode> Resolve(HttpListenerContext ctx);
|
||||
Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace DTLib.Web.Routes;
|
||||
|
||||
public abstract class RouteHandler
|
||||
{
|
||||
public abstract Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace DTLib.Web.Routes;
|
||||
|
||||
public class ServeFilesRouteHandler(IOPath _publicDir, string _homePageUrl = "index.html") : RouteHandler
|
||||
public class ServeFilesRouteHandler(IOPath _publicDir, string _homePageUrl = "index.html") : IRouteHandler
|
||||
{
|
||||
public override async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx)
|
||||
public async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger)
|
||||
{
|
||||
if (ctx.Request.HttpMethod != "GET")
|
||||
return HttpStatusCode.BadRequest;
|
||||
|
||||
@@ -2,47 +2,64 @@ namespace DTLib.Web.Routes;
|
||||
|
||||
public class SimpleRouter : IRouter
|
||||
{
|
||||
/// route for base url
|
||||
public RouteHandler? HomePageRoute = null;
|
||||
/// route for any url that doesn't have its own handler
|
||||
public RouteHandler? DefaultRoute = null;
|
||||
public record RouteWithMethod(HttpMethod method, IRouteHandler routeHandler)
|
||||
{
|
||||
public bool CheckMethod(HttpMethod requestMethod) => (requestMethod & method) != 0;
|
||||
|
||||
public bool CheckMethod(string requestMethodStr)
|
||||
=> Enum.TryParse<HttpMethod>(requestMethodStr, out var requestMethod)
|
||||
&& CheckMethod(requestMethod);
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, RouteHandler> _routes = new();
|
||||
private readonly Dictionary<string, RouteWithMethod> _routes = new();
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RouteWithMethod? DefaultRoute { get; set; }
|
||||
|
||||
public SimpleRouter(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger = new ContextLogger(nameof(SimpleRouter), logger);
|
||||
}
|
||||
|
||||
public void MapRoute(string url, HttpMethod method, IRouteHandler route)
|
||||
=> _routes.Add(url, new RouteWithMethod(method, route));
|
||||
|
||||
public void MapRoute(string url, Func<HttpListenerContext, Task<HttpStatusCode>> route)
|
||||
=> MapRoute(url, new DelegateRouteHandler(route));
|
||||
public void MapRoute(string url, HttpMethod method,
|
||||
Func<HttpListenerContext, ContextLogger, Task<HttpStatusCode>> route)
|
||||
=> MapRoute(url, method, new DelegateRouteHandler(route));
|
||||
|
||||
public void MapRoute(string url, RouteHandler route) => _routes.Add(url, route);
|
||||
|
||||
public async Task<HttpStatusCode> Resolve(HttpListenerContext ctx)
|
||||
public async Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger)
|
||||
{
|
||||
string requestPath = ctx.Request.Url?.AbsolutePath ?? "/";
|
||||
RouteHandler? route;
|
||||
if(HomePageRoute != null && requestPath == "/")
|
||||
route = HomePageRoute;
|
||||
else if (_routes.TryGetValue(requestPath, out var routeDelegate))
|
||||
route = routeDelegate;
|
||||
else route = DefaultRoute;
|
||||
|
||||
HttpStatusCode status;
|
||||
if (route == null)
|
||||
HttpStatusCode status = HttpStatusCode.InternalServerError;
|
||||
try
|
||||
{
|
||||
_logger.LogWarn(nameof(SimpleRouter), $"couldn't resolve request path {requestPath}");
|
||||
status = HttpStatusCode.NotFound;
|
||||
}
|
||||
else status = await route.HandleRequest(ctx);
|
||||
string? requestPath = ctx.Request.Url?.AbsolutePath;
|
||||
if (string.IsNullOrEmpty(requestPath))
|
||||
requestPath = "/";
|
||||
|
||||
ctx.Response.StatusCode = (int)status;
|
||||
await ctx.Response.OutputStream.FlushAsync();
|
||||
ctx.Response.OutputStream.Close();
|
||||
return status;
|
||||
if(!_routes.TryGetValue(requestPath!, out var routeWithMethod))
|
||||
routeWithMethod = DefaultRoute;
|
||||
|
||||
if (routeWithMethod is null)
|
||||
{
|
||||
_logger.LogWarn(nameof(SimpleRouter),
|
||||
$"couldn't resolve request path {ctx.Request.HttpMethod} {requestPath}");
|
||||
status = HttpStatusCode.NotFound;
|
||||
}
|
||||
else if (!routeWithMethod.CheckMethod(ctx.Request.HttpMethod))
|
||||
{
|
||||
_logger.LogWarn(nameof(SimpleRouter),
|
||||
$"received request with invalid method {ctx.Request.HttpMethod} {requestPath}");
|
||||
status = HttpStatusCode.MethodNotAllowed;
|
||||
}
|
||||
else status = await routeWithMethod.routeHandler.HandleRequest(ctx, requestLogger);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.Response.StatusCode = (int)status;
|
||||
await ctx.Response.OutputStream.FlushAsync();
|
||||
ctx.Response.OutputStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ global using System.Threading.Tasks;
|
||||
global using DTLib.Filesystem;
|
||||
global using DTLib.Logging;
|
||||
global using System.Net;
|
||||
using System.Diagnostics;
|
||||
using DTLib.Extensions;
|
||||
using DTLib.Web.Routes;
|
||||
|
||||
@@ -28,37 +29,44 @@ public class WebApp
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
_logger.LogInfo($"starting webserver at {_baseUrl} ...");
|
||||
_logger.LogInfo($"starting server at '{_baseUrl}'...");
|
||||
HttpListener server = new HttpListener();
|
||||
server.Prefixes.Add(_baseUrl);
|
||||
server.Start();
|
||||
_logger.LogInfo("server started");
|
||||
long requestId = 1;
|
||||
while (!_stopToken.IsCancellationRequested)
|
||||
{
|
||||
var ctx = await server.GetContextAsync().AsCancellable(_stopToken);
|
||||
HandleRequestAsync(ctx, requestId);
|
||||
requestId++;
|
||||
}
|
||||
|
||||
// stop
|
||||
try
|
||||
{
|
||||
while (!_stopToken.IsCancellationRequested)
|
||||
{
|
||||
var ctx = await server.GetContextAsync().AsCancellable(_stopToken);
|
||||
HandleRequestAsync(ctx, new ContextLogger($"Request-{requestId++}", _logger));
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{}
|
||||
|
||||
server.Stop();
|
||||
_logger.LogInfo("server stopped");
|
||||
}
|
||||
|
||||
// ReSharper disable once AsyncVoidMethod
|
||||
private async void HandleRequestAsync(HttpListenerContext ctx, long requestId)
|
||||
private async void HandleRequestAsync(HttpListenerContext ctx, ContextLogger requestLogger)
|
||||
{
|
||||
string logContext = $"Request {requestId}";
|
||||
try
|
||||
{
|
||||
_logger.LogInfo(logContext, $"[{ctx.Request.HttpMethod}] {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint} ...");
|
||||
var status = await _router.Resolve(ctx);
|
||||
_logger.LogInfo(logContext, $"{(int)status} ({status})");
|
||||
requestLogger.LogInfo($"{ctx.Request.HttpMethod} {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint}...");
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
await _router.Resolve(ctx, requestLogger);
|
||||
stopwatch.Stop();
|
||||
requestLogger.LogInfo($"responded {ctx.Response.StatusCode}" +
|
||||
$" ({(HttpStatusCode)ctx.Response.StatusCode})" +
|
||||
$" in {stopwatch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarn(logContext, ex);
|
||||
requestLogger.LogWarn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Submodule DTLib.XXHash updated: 9360dfe305...3e1a2c00e6
@@ -14,6 +14,9 @@ public static class ColoredConsole
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ResetColor() => System.Console.ResetColor();
|
||||
|
||||
/// <summary>
|
||||
/// Clears console buffer and resets color
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Clear()
|
||||
{
|
||||
@@ -21,26 +24,15 @@ public static class ColoredConsole
|
||||
System.Console.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Write(char c) => System.Console.Write(c);
|
||||
|
||||
public static void Write(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
{
|
||||
if(fg != null) Fg(fg.Value);
|
||||
if(bg != null) Bg(bg.Value);
|
||||
#if NETSTANDARD2_0
|
||||
var chars = msg.ToCharArray();
|
||||
#else
|
||||
var chars = msg.AsSpan();
|
||||
#endif
|
||||
for (int i = 0; i < chars.Length; ++i)
|
||||
{
|
||||
Write(chars[i]);
|
||||
}
|
||||
System.Console.Write(msg);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteLine() => Write('\n');
|
||||
public static void WriteLine() => System.Console.Write('\n');
|
||||
|
||||
public static void WriteLine(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
{
|
||||
@@ -48,18 +40,15 @@ public static class ColoredConsole
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
public static string? ReadLine(string query, ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
public static string ReadLine(ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
{
|
||||
Write(query, fg, bg);
|
||||
Write(':');
|
||||
Write(' ');
|
||||
return System.Console.ReadLine();
|
||||
if(fg != null) Fg(fg.Value);
|
||||
if(bg != null) Bg(bg.Value);
|
||||
return System.Console.ReadLine() ?? string.Empty;
|
||||
}
|
||||
|
||||
public static void WriteHLine(char c, ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
{
|
||||
public static void WriteHLine(char c, ConsoleColor? fg = null, ConsoleColor? bg = null) =>
|
||||
WriteLine(c.Multiply(Width - 1), fg, bg);
|
||||
}
|
||||
|
||||
public static void WriteTitle(string title, char spacing = '-',
|
||||
string left_framing = "[", string right_framing = "]",
|
||||
@@ -83,7 +72,5 @@ public static class ColoredConsole
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteCentred(string title, ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
{
|
||||
WriteTitle(title, ' ', "", "", fg, bg);
|
||||
}
|
||||
=> WriteTitle(title, ' ', "", "", fg, bg);
|
||||
}
|
||||
|
||||
@@ -1,64 +1,96 @@
|
||||
namespace DTLib.Console;
|
||||
|
||||
#nullable enable
|
||||
public class LaunchArgument
|
||||
public record LaunchArgument
|
||||
{
|
||||
public string[] Aliases;
|
||||
public string Description;
|
||||
protected string? ParamName1;
|
||||
protected string? ParamName2;
|
||||
public Action? Handler;
|
||||
public Action<string>? HandlerWithArg1;
|
||||
public Action<string, string>? HandlerWithArg2;
|
||||
public int RequiredArgsCount;
|
||||
public int Priority;
|
||||
public struct Param
|
||||
{
|
||||
public readonly string Name;
|
||||
public string? Value { get; internal set; } = null;
|
||||
|
||||
private LaunchArgument(string[] aliases, string description, int priority)
|
||||
public Param(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly string[] Aliases;
|
||||
public readonly string Description;
|
||||
public readonly int Priority;
|
||||
public readonly Param[] Params;
|
||||
|
||||
private readonly Action? Handler;
|
||||
private readonly Action<string>? Handler1Param;
|
||||
private readonly Action<string, string>? Handler2Params;
|
||||
|
||||
public LaunchArgument(string[] aliases, string description,
|
||||
Action handler, int priority = 0)
|
||||
{
|
||||
Aliases = aliases;
|
||||
Description = description;
|
||||
Priority = priority;
|
||||
}
|
||||
|
||||
public LaunchArgument(string[] aliases, string description,
|
||||
Action handler, int priority = 0)
|
||||
: this(aliases, description, priority)
|
||||
{
|
||||
Handler = handler;
|
||||
RequiredArgsCount = 0;
|
||||
Params = [];
|
||||
}
|
||||
|
||||
public LaunchArgument(string[] aliases, string description,
|
||||
Action<string> handler, string paramName1, int priority=0)
|
||||
: this(aliases, description, priority)
|
||||
{
|
||||
HandlerWithArg1 = handler;
|
||||
ParamName1 = paramName1;
|
||||
RequiredArgsCount = 1;
|
||||
Aliases = aliases;
|
||||
Description = description;
|
||||
Priority = priority;
|
||||
|
||||
Handler1Param = handler;
|
||||
Params = [
|
||||
new Param(paramName1)
|
||||
];
|
||||
}
|
||||
public LaunchArgument(string[] aliases, string description,
|
||||
Action<string, string> handler, string paramName1, string paramName2, int priority=0)
|
||||
: this(aliases, description, priority)
|
||||
{
|
||||
HandlerWithArg2 = handler;
|
||||
ParamName1 = paramName1;
|
||||
ParamName2 = paramName2;
|
||||
RequiredArgsCount = 2;
|
||||
Aliases = aliases;
|
||||
Description = description;
|
||||
Priority = priority;
|
||||
|
||||
Handler2Params = handler;
|
||||
Params = [
|
||||
new Param(paramName1),
|
||||
new Param(paramName2),
|
||||
];
|
||||
}
|
||||
|
||||
public StringBuilder AppendHelpInfo(StringBuilder b)
|
||||
internal StringBuilder AppendHelpInfo(StringBuilder b)
|
||||
{
|
||||
b.Append(Aliases[0]);
|
||||
for (int i = 1; i < Aliases.Length; i++)
|
||||
b.Append(", ").Append(Aliases[i]);
|
||||
if (!string.IsNullOrEmpty(ParamName1))
|
||||
b.Append(" [").Append(ParamName1).Append("] ");
|
||||
if (!string.IsNullOrEmpty(ParamName2))
|
||||
b.Append(" [").Append(ParamName2).Append("] ");
|
||||
b.Append("- ").Append(Description);
|
||||
foreach (var param in Params)
|
||||
b.Append(" [").Append(param.Name).Append("]");
|
||||
b.Append(" - ").Append(Description);
|
||||
return b;
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"{{{{{Aliases.MergeToString(", ")}}}, Handler: {Handler is null}, HandlerWithArg: {HandlerWithArg1 is null}}}";
|
||||
internal void Handle()
|
||||
{
|
||||
switch (Params.Length)
|
||||
{
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(Params.Length.ToString());
|
||||
case 0:
|
||||
Handler!.Invoke();
|
||||
break;
|
||||
case 1:
|
||||
if (Params[0].Value is null)
|
||||
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[0] value");
|
||||
Handler1Param!.Invoke(Params[0].Value!);
|
||||
break;
|
||||
case 2:
|
||||
if (Params[0].Value is null)
|
||||
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[0] value");
|
||||
if (Params[1].Value is null)
|
||||
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[1] value");
|
||||
Handler2Params!.Invoke(Params[0].Value!, Params[1].Value!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,135 @@ namespace DTLib.Console;
|
||||
|
||||
public class LaunchArgumentParser
|
||||
{
|
||||
private Dictionary<string, LaunchArgument> argDict = new();
|
||||
private List<LaunchArgument> argList = new();
|
||||
public bool ExitIfNoArgs = true;
|
||||
public string HelpMessageHeader = "USAGE:";
|
||||
public bool AllowedNoArguments;
|
||||
public bool AllowedUnknownArguments;
|
||||
// ReSharper disable once CollectionNeverQueried.Global
|
||||
public readonly List<string> UnknownArguments = new();
|
||||
|
||||
public class ExitAfterHelpException : Exception
|
||||
private readonly Dictionary<string, LaunchArgument> argDict = new();
|
||||
private readonly List<LaunchArgument> argList = new();
|
||||
|
||||
public LaunchArgumentParser()
|
||||
{
|
||||
internal ExitAfterHelpException() : base("your program can use this exception to exit after displaying help message")
|
||||
{ }
|
||||
var help = new LaunchArgument(new[] { "h", "help" },
|
||||
"shows help message", HelpHandler);
|
||||
Add(help);
|
||||
var helpArg = new LaunchArgument(new[] { "ha", "helparg" },
|
||||
"shows help message for specific argument",
|
||||
HelpArgHandler, "argument");
|
||||
Add(helpArg);
|
||||
}
|
||||
public LaunchArgumentParser(ICollection<LaunchArgument> arguments) : this() => WithArgs(arguments);
|
||||
public LaunchArgumentParser(params LaunchArgument[] arguments) : this() => WithArgs(arguments);
|
||||
|
||||
public LaunchArgumentParser WithArgs(IEnumerable<LaunchArgument> args)
|
||||
{
|
||||
foreach (var arg in args)
|
||||
Add(arg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser WithArgs(params LaunchArgument[] args)
|
||||
{
|
||||
foreach (var arg in args)
|
||||
Add(arg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser WithHelpMessageHeader(string header)
|
||||
{
|
||||
HelpMessageHeader = header;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser AllowNoArguments()
|
||||
{
|
||||
AllowedNoArguments = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser AllowUnknownArguments()
|
||||
{
|
||||
AllowedUnknownArguments = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public string CreateHelpMessage()
|
||||
{
|
||||
StringBuilder b = new();
|
||||
StringBuilder b = new(HelpMessageHeader);
|
||||
foreach (var arg in argList)
|
||||
arg.AppendHelpInfo(b).Append('\n');
|
||||
b.Remove(b.Length-1, 1);
|
||||
{
|
||||
b.Append('\n');
|
||||
arg.AppendHelpInfo(b);
|
||||
}
|
||||
return b.ToString();
|
||||
}
|
||||
|
||||
public string CreateHelpArgMessage(string argAlias)
|
||||
{
|
||||
StringBuilder b = new();
|
||||
var arg = Parse(argAlias);
|
||||
if(!TryParseArg(argAlias, out var arg))
|
||||
throw new Exception($"unknown argument '{argAlias}'");
|
||||
arg.AppendHelpInfo(b);
|
||||
return b.ToString();
|
||||
}
|
||||
|
||||
public void Add(LaunchArgument arg)
|
||||
{
|
||||
argList.Add(arg);
|
||||
foreach (string alias in arg.Aliases)
|
||||
argDict.Add(alias, arg);
|
||||
}
|
||||
|
||||
public bool TryParseArg(string argAlias, out LaunchArgument arg)
|
||||
{
|
||||
// different argument providing patterns
|
||||
arg = null!;
|
||||
return argAlias.StartsWith("--") && argDict.TryGetValue(argAlias.Substring(2), out arg) || // --arg
|
||||
argAlias.StartsWith('-') && argDict.TryGetValue(argAlias.Substring(1), out arg) || // -arg
|
||||
argAlias.StartsWith('/') && argDict.TryGetValue(argAlias.Substring(1), out arg); // /arg
|
||||
}
|
||||
|
||||
/// <param name="args">program launch args</param>
|
||||
/// <exception cref="Exception">argument {args[i]} should have a parameter after it</exception>
|
||||
/// <exception cref="NullReferenceException">argument hasn't got any handlers</exception>
|
||||
/// <exception cref="ExitAfterHelpException">happens after help message is displayed</exception>
|
||||
public void ParseAndHandle(string[] args)
|
||||
{
|
||||
// show help message and throw ExitAfterHelpException
|
||||
if (args.Length == 0 && !AllowedNoArguments)
|
||||
HelpHandler();
|
||||
|
||||
List<LaunchArgument> execQueue = new();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (!TryParseArg(args[i], out var arg))
|
||||
{
|
||||
if (!AllowedUnknownArguments)
|
||||
throw new Exception($"unknown argument '{args[i]}'");
|
||||
UnknownArguments.Add(args[i]);
|
||||
}
|
||||
for (int j = 0; j < arg.Params.Length; j++)
|
||||
{
|
||||
if (++i >= args.Length)
|
||||
throw new Exception(
|
||||
$"argument '{arg.Aliases[0]}' should have parameter '{arg.Params[j]}' after it");
|
||||
arg.Params[j].Value = args[i];
|
||||
}
|
||||
|
||||
execQueue.Add(arg);
|
||||
}
|
||||
|
||||
// ascending sort by priority
|
||||
execQueue.Sort((a0, a1) => a0.Priority - a1.Priority);
|
||||
// finally executing handlers
|
||||
foreach (var a in execQueue)
|
||||
a.Handle();
|
||||
}
|
||||
|
||||
private void HelpHandler()
|
||||
{
|
||||
System.Console.WriteLine(CreateHelpMessage());
|
||||
@@ -39,104 +143,11 @@ public class LaunchArgumentParser
|
||||
throw new ExitAfterHelpException();
|
||||
}
|
||||
|
||||
|
||||
public LaunchArgumentParser()
|
||||
public class ExitAfterHelpException : Exception
|
||||
{
|
||||
var help = new LaunchArgument(new[] { "h", "help" },
|
||||
"shows help message", HelpHandler);
|
||||
Add(help);
|
||||
var helpArg = new LaunchArgument( new[]{ "ha", "helparg" },
|
||||
"shows help message for particular argument",
|
||||
HelpArgHandler, "argAlias");
|
||||
Add(helpArg);
|
||||
}
|
||||
|
||||
public LaunchArgumentParser WithNoExit()
|
||||
{
|
||||
ExitIfNoArgs = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser(ICollection<LaunchArgument> arguments) : this()
|
||||
{
|
||||
foreach (var arg in arguments)
|
||||
Add(arg);
|
||||
}
|
||||
public LaunchArgumentParser(params LaunchArgument[] arguments) : this()
|
||||
{
|
||||
for (var i = 0; i < arguments.Length; i++)
|
||||
Add(arguments[i]);
|
||||
}
|
||||
|
||||
public void Add(LaunchArgument arg)
|
||||
{
|
||||
argList.Add(arg);
|
||||
for(int a=0; a<arg.Aliases.Length; a++)
|
||||
argDict.Add(arg.Aliases[a], arg);
|
||||
}
|
||||
|
||||
public LaunchArgument Parse(string argAlias)
|
||||
{
|
||||
// different argument providing patterns
|
||||
if (!argDict.TryGetValue(argAlias, out var arg) && // arg
|
||||
!(argAlias.StartsWith("--") && argDict.TryGetValue(argAlias.Substring(2), out arg)) && // --arg
|
||||
!(argAlias.StartsWith('-') && argDict.TryGetValue(argAlias.Substring(1), out arg)) && // -arg
|
||||
!(argAlias.StartsWith('/') && argDict.TryGetValue(argAlias.Substring(1), out arg))) // /arg
|
||||
throw new Exception($"invalid argument: {argAlias}\n{CreateHelpMessage()}");
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
/// <param name="args">program launch args</param>
|
||||
/// <exception cref="Exception">argument {args[i]} should have a parameter after it</exception>
|
||||
/// <exception cref="NullReferenceException">argument hasn't got any handlers</exception>
|
||||
/// <exception cref="ExitAfterHelpException">happens after help message is displayed</exception>
|
||||
public void ParseAndHandle(string[] args)
|
||||
{
|
||||
// show help and throw
|
||||
if (args.Length == 0 && ExitIfNoArgs)
|
||||
HelpHandler();
|
||||
|
||||
List<LaunchArgument> execQueue = new();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
public ExitAfterHelpException()
|
||||
: base("your program can use this exception to exit after displaying help message")
|
||||
{
|
||||
LaunchArgument arg = Parse(args[i]);
|
||||
|
||||
switch (arg.RequiredArgsCount)
|
||||
{
|
||||
case 0:
|
||||
if (arg.Handler is null)
|
||||
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
if (arg.HandlerWithArg1 is null)
|
||||
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
|
||||
if (i + 1 >= args.Length)
|
||||
throw new Exception($"argument <{args[i]}> should have a parameter after it");
|
||||
string arg1 = args[++i];
|
||||
arg.Handler = () => arg.HandlerWithArg1(arg1);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (arg.HandlerWithArg2 is null)
|
||||
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
|
||||
if (i + 2 >= args.Length)
|
||||
throw new Exception($"argument <{args[i]}> should have two params after it");
|
||||
string arg1 = args[++i], arg2 = args[++i];
|
||||
arg.Handler = () => arg.HandlerWithArg2(arg1, arg2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
execQueue.Add(arg);
|
||||
}
|
||||
|
||||
// ascending sort by priority
|
||||
execQueue.Sort((a0, a1) => a0.Priority-a1.Priority);
|
||||
// finally executing handlers
|
||||
foreach (var a in execQueue)
|
||||
a.Handler!();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<!--package info-->
|
||||
<PackageId>DTLib</PackageId>
|
||||
<Version>1.6.5</Version>
|
||||
<Version>1.7.4</Version>
|
||||
<Authors>Timerix</Authors>
|
||||
<Description>Library for all my C# projects</Description>
|
||||
<RepositoryType>GIT</RepositoryType>
|
||||
@@ -31,6 +31,6 @@
|
||||
<ProjectReference Include="..\DTLib.Demystifier\DTLib.Demystifier.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
|
||||
<PackageReference Include="DTLib.Demystifier" Version="1.1.0" />
|
||||
<PackageReference Include="DTLib.Demystifier" Version="1.1.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -11,8 +11,8 @@ public class CompositeLogger : ILogger
|
||||
set
|
||||
{
|
||||
_debugLogEnabled = value;
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].DebugLogEnabled = value;
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.DebugLogEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ public class CompositeLogger : ILogger
|
||||
set
|
||||
{
|
||||
_infoLogEnabled = true;
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].InfoLogEnabled = value;
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.InfoLogEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ public class CompositeLogger : ILogger
|
||||
set
|
||||
{
|
||||
_warnLogEnabled = value;
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].WarnLogEnabled = value;
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.WarnLogEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,46 +44,38 @@ public class CompositeLogger : ILogger
|
||||
set
|
||||
{
|
||||
_errorLogenabled = value;
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].ErrorLogEnabled = value;
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.ErrorLogEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ILogFormat Format { get; set; }
|
||||
public readonly List<ILogger> ChildLoggers;
|
||||
|
||||
protected ILogger[] _loggers;
|
||||
private bool _debugLogEnabled =
|
||||
#if DEBUG
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
private bool _debugLogEnabled;
|
||||
private bool _infoLogEnabled = true;
|
||||
private bool _warnLogEnabled = true;
|
||||
private bool _errorLogenabled = true;
|
||||
|
||||
public CompositeLogger(ILogFormat format, params ILogger[] loggers)
|
||||
public CompositeLogger(ILogFormat format, params ILogger[] childLoggers)
|
||||
{
|
||||
Format = format;
|
||||
_loggers = loggers;
|
||||
ChildLoggers = new List<ILogger>(childLoggers);
|
||||
}
|
||||
|
||||
public CompositeLogger(params ILogger[] loggers) : this(new DefaultLogFormat(), loggers)
|
||||
public CompositeLogger(params ILogger[] childLoggers) : this(new DefaultLogFormat(), childLoggers)
|
||||
{}
|
||||
|
||||
|
||||
public void Log(string context, LogSeverity severity, object message, ILogFormat format)
|
||||
{
|
||||
if(!this.CheckSeverity(severity))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].Log(context, severity, message, format);
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.Log(context, severity, message, format);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].Dispose();
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user