Compare commits

..

27 Commits

Author SHA1 Message Date
8d55c1c533 implemented check of HttpMethod as binary flags 2025-05-22 21:42:13 +05:00
e4ee03364c updated dependencies 2025-05-22 20:43:55 +05:00
c77b3e0742 LaunchArgumentParser.UnknownArguments 2025-04-26 05:29:30 +05:00
e391f0238a DTLib v1.7.2 2025-04-26 04:20:40 +05:00
823169ca91 LaunchArgumentParser.HelpMessageHeader 2025-04-26 04:19:14 +05:00
ee20c9c5ec added requestLogger to IRouteHandler 2025-04-06 14:59:27 +05:00
af658b1656 Composite logger fix for different child logger severity settings 2025-04-06 14:51:23 +05:00
26c118195c small changes in ColoredConsole 2025-04-06 13:50:42 +05:00
c461418185 refactored LaunchArgumentParser 2025-04-06 13:25:44 +05:00
29cde170c6 abstract class replaced with IRouteHandler interface 2025-04-06 12:19:11 +05:00
448161239e response elapsed time logging 2025-03-24 00:31:47 +05:00
a4c2ae3e28 HomeRoute removed (use MapRoute "/") 2025-03-23 02:44:51 +05:00
1eb208cba3 HttpMethod 2025-03-23 02:36:47 +05:00
2b5d6b6a54 fixes for ServeFilesRouteHandler 2025-03-23 01:20:25 +05:00
386d71260c added IOPath double separator removal 2025-03-23 01:19:32 +05:00
dc35725b64 Task.AsCancellable 2025-03-23 00:09:40 +05:00
7d814ee4cb IRouter 2025-03-23 00:07:06 +05:00
9082d7a4d0 Created new project: DTLib.Web 2025-03-22 23:36:29 +05:00
f9e4e533bb WriteAllText return type fix 2025-03-22 21:21:37 +05:00
4da75bfa8a v1.6.2 2025-03-22 20:55:10 +05:00
cfa26ce807 async methods in File 2025-03-22 20:53:16 +05:00
f2cdfc86b7 TransformStream 2024-11-03 22:01:02 +05:00
fa9c5ac689 log format 2024-11-03 21:44:03 +05:00
613b11e88c ColoredConsole rework 2024-10-20 12:32:30 +05:00
052169df1c pack.sh package selection 2024-10-20 10:36:57 +05:00
826c11aa55 FileMode.Create 2024-09-29 09:22:03 +05:00
7fccb3810f Filesystem 2024-09-26 03:09:28 +05:00
32 changed files with 793 additions and 406 deletions

1
.gitignore vendored
View File

@@ -18,6 +18,7 @@ nuget/
.idea/ .idea/
.editorconfig .editorconfig
*.user *.user
*.DotSettings
#backups #backups
.old*/ .old*/

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<!--package info--> <!--package info-->
<PackageId>DTLib.Logging.Microsoft</PackageId> <PackageId>DTLib.Logging.Microsoft</PackageId>
<Version>1.1.0</Version> <Version>1.1.3</Version>
<Authors>Timerix</Authors> <Authors>Timerix</Authors>
<Description>DTLib logger wrapper with dependency injection</Description> <Description>DTLib logger wrapper with dependency injection</Description>
<RepositoryType>GIT</RepositoryType> <RepositoryType>GIT</RepositoryType>
@@ -11,16 +11,16 @@
<Configuration>Release</Configuration> <Configuration>Release</Configuration>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<!--compilation properties--> <!--compilation properties-->
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFramework>netstandard2.0</TargetFramework>
<!--language features--> <!--language features-->
<LangVersion>12</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings> <ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<!--external dependencies--> <!--external dependencies-->
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
</ItemGroup> </ItemGroup>
<!--DTLib dependencies--> <!--DTLib dependencies-->
@@ -28,6 +28,6 @@
<ProjectReference Include="..\DTLib\DTLib.csproj" /> <ProjectReference Include="..\DTLib\DTLib.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' "> <ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.4.*" /> <PackageReference Include="DTLib" Version="1.7.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--package info-->
<PackageId>DTLib.Web</PackageId>
<Version>1.4.0</Version>
<Authors>Timerix</Authors>
<Description>HTTP Server with simple routing</Description>
<RepositoryType>GIT</RepositoryType>
<RepositoryUrl>https://timerix.ddns.net:3322/Timerix/DTLib</RepositoryUrl>
<PackageProjectUrl>https://timerix.ddns.net:3322/Timerix/DTLib</PackageProjectUrl>
<Configuration>Release</Configuration>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!--compilation properties-->
<TargetFramework>netstandard2.0</TargetFramework>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<!--language features-->
<LangVersion>latest</LangVersion>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
</PropertyGroup>
<!--DTLib dependencies-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\DTLib\DTLib.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.7.4" />
</ItemGroup>
</Project>

18
DTLib.Web/HttpMethod.cs Normal file
View 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
}

View File

@@ -0,0 +1,9 @@
namespace DTLib.Web.Routes;
public class DelegateRouteHandler(
Func<HttpListenerContext, ContextLogger, Task<HttpStatusCode>> routeHandler
) : IRouteHandler
{
public Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger)
=> routeHandler(ctx, requestLogger);
}

View File

@@ -0,0 +1,6 @@
namespace DTLib.Web.Routes;
public interface IRouteHandler
{
Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger);
}

View File

@@ -0,0 +1,6 @@
namespace DTLib.Web.Routes;
public interface IRouter
{
Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger);
}

View File

@@ -0,0 +1,40 @@
namespace DTLib.Web.Routes;
public class ServeFilesRouteHandler(IOPath _publicDir, string _homePageUrl = "index.html") : IRouteHandler
{
public async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger)
{
if (ctx.Request.HttpMethod != "GET")
return HttpStatusCode.BadRequest;
string requestPath = ctx.Request.Url?.AbsolutePath!;
if (string.IsNullOrEmpty(requestPath) || requestPath == "/")
{
requestPath = _homePageUrl;
}
string ext = Path.Extension(requestPath).Str;
IOPath filePath = Path.Concat(_publicDir, requestPath);
if (!File.Exists(filePath))
return HttpStatusCode.NotFound;
List<(string key, string val)> headers = ext switch
{
"html" => [("Content-Type", "text/html")],
"css" => [("Content-Type", "text/css")],
"js" or "jsx" or "ts" or "tsx" or "map" => [("Content-Type", "text/javascript")],
_ =>
[
("Content-Type", "binary/octet-stream"),
("Content-Disposition", $"attachment filename={filePath.LastName()}")
]
};
foreach (var header in headers)
ctx.Response.Headers.Set(header.key, header.val);
var fileStream = File.OpenRead(filePath);
ctx.Response.ContentLength64 = fileStream.Length;
await fileStream.CopyToAsync(ctx.Response.OutputStream);
return HttpStatusCode.OK;
}
}

View File

@@ -0,0 +1,65 @@
namespace DTLib.Web.Routes;
public class SimpleRouter : IRouter
{
/// route for any url that doesn't have its own handler
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, RouteWithMethod> _routes = new();
private readonly ILogger _logger;
public RouteWithMethod? DefaultRoute { get; set; }
public SimpleRouter(ILogger 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, HttpMethod method,
Func<HttpListenerContext, ContextLogger, Task<HttpStatusCode>> route)
=> MapRoute(url, method, new DelegateRouteHandler(route));
public async Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger)
{
HttpStatusCode status = HttpStatusCode.InternalServerError;
try
{
string? requestPath = ctx.Request.Url?.AbsolutePath;
if (string.IsNullOrEmpty(requestPath))
requestPath = "/";
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();
}
}
}

73
DTLib.Web/WebApp.cs Normal file
View File

@@ -0,0 +1,73 @@
global using System;
global using System.Collections.Generic;
global using System.Text;
global using System.Threading;
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;
namespace DTLib.Web;
public class WebApp
{
private readonly string _baseUrl;
private readonly ContextLogger _logger;
private readonly IRouter _router;
private readonly CancellationToken _stopToken;
public WebApp(string baseUrl, ILogger logger, IRouter router, CancellationToken stopToken = default)
{
_logger = new ContextLogger(nameof(WebApp), logger);
_baseUrl = baseUrl;
_stopToken = stopToken;
_router = router;
}
public async Task Run()
{
_logger.LogInfo($"starting server at '{_baseUrl}'...");
HttpListener server = new HttpListener();
server.Prefixes.Add(_baseUrl);
server.Start();
_logger.LogInfo("server started");
long requestId = 1;
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");
}
private async void HandleRequestAsync(HttpListenerContext ctx, ContextLogger requestLogger)
{
try
{
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)
{
requestLogger.LogWarn(ex);
}
}
}

View File

@@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Logging.Microsoft", "
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.XXHash", "DTLib.XXHash\DTLib.XXHash\DTLib.XXHash.csproj", "{C7029741-816D-41B2-A2C4-E20565B1739D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.XXHash", "DTLib.XXHash\DTLib.XXHash\DTLib.XXHash.csproj", "{C7029741-816D-41B2-A2C4-E20565B1739D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Web", "DTLib.Web\DTLib.Web.csproj", "{9A3220EB-CCED-4172-9BD4-C3700FF36539}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -44,6 +46,10 @@ Global
{C7029741-816D-41B2-A2C4-E20565B1739D}.Debug|Any CPU.Build.0 = Debug|Any CPU {C7029741-816D-41B2-A2C4-E20565B1739D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.ActiveCfg = Release|Any CPU {C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.Build.0 = Release|Any CPU {C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.Build.0 = Release|Any CPU
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -1,86 +1,76 @@
namespace DTLib.Console; namespace DTLib.Console;
//
// вывод и ввод цветного текста в консоли
// работает медленнее чем хотелось бы
//
public static class ColoredConsole public static class ColoredConsole
{ {
// парсит название цвета в ConsoleColor public static int Width => System.Console.WindowWidth;
public static ConsoleColor ParseColor(string color) => color switch public static int Height => System.Console.WindowHeight;
{
//case "magneta":
"m" => ConsoleColor.Magenta,
//case "green":
"g" => ConsoleColor.Green,
//case "red":
"r" => ConsoleColor.Red,
//case "yellow":
"y" => ConsoleColor.Yellow,
//case "white":
"w" => ConsoleColor.White,
//case "blue":
"b" => ConsoleColor.Blue,
//case "cyan":
"c" => ConsoleColor.Cyan,
//case "h":
"h" or "gray" => ConsoleColor.Gray,
//case "black":
"black" => ConsoleColor.Black,
_ => throw new Exception($"ColoredConsole.ParseColor({color}) error: incorrect color"),
};
public static void Write(ConsoleColor color,string msg) [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Fg(ConsoleColor color) => System.Console.ForegroundColor = color;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Bg(ConsoleColor color) => System.Console.BackgroundColor = color;
[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()
{ {
System.Console.ForegroundColor = color; ResetColor();
System.Console.Clear();
}
public static void Write(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
{
if(fg != null) Fg(fg.Value);
if(bg != null) Bg(bg.Value);
System.Console.Write(msg); System.Console.Write(msg);
System.Console.ForegroundColor = ConsoleColor.Gray;
} }
public static void Write(string msg) => Write(ConsoleColor.Gray, msg); [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLine() => System.Console.Write('\n');
// вывод цветного текста public static void WriteLine(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
public static void Write(params string[] input)
{ {
if (input.Length == 1) Write(msg, fg, bg);
{
Write(input[0]);
return;
}
if (input.Length % 2 != 0)
throw new Exception("ColoredConsole.Write() error: every text string must have color string before");
for (ushort i = 0; i < input.Length; i++)
{
System.Console.ForegroundColor = ParseColor(input[i++]);
System.Console.Write(input[i]);
}
System.Console.ForegroundColor = ConsoleColor.Gray;
}
public static void WriteLine() => System.Console.WriteLine();
public static void WriteLine(ConsoleColor color,string msg)
{
System.Console.ForegroundColor = color;
System.Console.WriteLine(msg);
System.Console.ForegroundColor = ConsoleColor.Gray;
}
public static void WriteLine(params string[] input)
{
Write(input);
WriteLine(); WriteLine();
} }
// ввод цветного текста public static string ReadLine(ConsoleColor? fg = null, ConsoleColor? bg = null)
public static string? Read(ConsoleColor color)
{ {
System.Console.ForegroundColor = color; if(fg != null) Fg(fg.Value);
var r = System.Console.ReadLine(); if(bg != null) Bg(bg.Value);
System.Console.ForegroundColor = ConsoleColor.Gray; return System.Console.ReadLine() ?? string.Empty;
return r;
} }
public static string? Read(string color) => Read(ParseColor(color)); 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 = "]",
ConsoleColor? fg = null, ConsoleColor? bg = null)
{
int both_spacing_length = Width - title.Length - left_framing.Length - right_framing.Length - 1;
int left_spacing_length = both_spacing_length / 2;
int right_spacing_length = left_spacing_length + both_spacing_length % 2;
var b = new StringBuilder();
if(left_spacing_length > 0)
b.Append(spacing.Multiply(left_spacing_length));
b.Append(left_framing);
b.Append(title);
b.Append(right_framing);
if(right_spacing_length > 0)
b.Append(spacing.Multiply(right_spacing_length));
WriteLine(b.ToString(), fg, bg);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteCentred(string title, ConsoleColor? fg = null, ConsoleColor? bg = null)
=> WriteTitle(title, ' ', "", "", fg, bg);
} }

View File

@@ -1,64 +1,96 @@
namespace DTLib.Console; namespace DTLib.Console;
#nullable enable public record LaunchArgument
public class LaunchArgument
{ {
public string[] Aliases; public struct Param
public string Description; {
protected string? ParamName1; public readonly string Name;
protected string? ParamName2; public string? Value { get; internal set; } = null;
public Action? Handler;
public Action<string>? HandlerWithArg1;
public Action<string, string>? HandlerWithArg2;
public int RequiredArgsCount;
public int Priority;
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; Aliases = aliases;
Description = description; Description = description;
Priority = priority; Priority = priority;
}
public LaunchArgument(string[] aliases, string description,
Action handler, int priority = 0)
: this(aliases, description, priority)
{
Handler = handler; Handler = handler;
RequiredArgsCount = 0; Params = [];
} }
public LaunchArgument(string[] aliases, string description, public LaunchArgument(string[] aliases, string description,
Action<string> handler, string paramName1, int priority=0) Action<string> handler, string paramName1, int priority=0)
: this(aliases, description, priority)
{ {
HandlerWithArg1 = handler; Aliases = aliases;
ParamName1 = paramName1; Description = description;
RequiredArgsCount = 1; Priority = priority;
Handler1Param = handler;
Params = [
new Param(paramName1)
];
} }
public LaunchArgument(string[] aliases, string description, public LaunchArgument(string[] aliases, string description,
Action<string, string> handler, string paramName1, string paramName2, int priority=0) Action<string, string> handler, string paramName1, string paramName2, int priority=0)
: this(aliases, description, priority)
{ {
HandlerWithArg2 = handler; Aliases = aliases;
ParamName1 = paramName1; Description = description;
ParamName2 = paramName2; Priority = priority;
RequiredArgsCount = 2;
Handler2Params = handler;
Params = [
new Param(paramName1),
new Param(paramName2),
];
} }
public StringBuilder AppendHelpInfo(StringBuilder b) internal StringBuilder AppendHelpInfo(StringBuilder b)
{ {
b.Append(Aliases[0]); b.Append(Aliases[0]);
for (int i = 1; i < Aliases.Length; i++) for (int i = 1; i < Aliases.Length; i++)
b.Append(", ").Append(Aliases[i]); b.Append(", ").Append(Aliases[i]);
if (!string.IsNullOrEmpty(ParamName1)) foreach (var param in Params)
b.Append(" [").Append(ParamName1).Append("] "); b.Append(" [").Append(param.Name).Append("]");
if (!string.IsNullOrEmpty(ParamName2)) b.Append(" - ").Append(Description);
b.Append(" [").Append(ParamName2).Append("] ");
b.Append("- ").Append(Description);
return b; return b;
} }
public override string ToString() => internal void Handle()
$"{{{{{Aliases.MergeToString(", ")}}}, Handler: {Handler is null}, HandlerWithArg: {HandlerWithArg1 is null}}}"; {
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;
}
}
} }

View File

@@ -2,31 +2,135 @@ namespace DTLib.Console;
public class LaunchArgumentParser public class LaunchArgumentParser
{ {
private Dictionary<string, LaunchArgument> argDict = new(); public string HelpMessageHeader = "USAGE:";
private List<LaunchArgument> argList = new(); public bool AllowedNoArguments;
public bool ExitIfNoArgs = true; 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() public string CreateHelpMessage()
{ {
StringBuilder b = new(); StringBuilder b = new(HelpMessageHeader);
foreach (var arg in argList) 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(); return b.ToString();
} }
public string CreateHelpArgMessage(string argAlias) public string CreateHelpArgMessage(string argAlias)
{ {
StringBuilder b = new(); StringBuilder b = new();
var arg = Parse(argAlias); if(!TryParseArg(argAlias, out var arg))
throw new Exception($"unknown argument '{argAlias}'");
arg.AppendHelpInfo(b); arg.AppendHelpInfo(b);
return b.ToString(); 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() private void HelpHandler()
{ {
System.Console.WriteLine(CreateHelpMessage()); System.Console.WriteLine(CreateHelpMessage());
@@ -39,104 +143,11 @@ public class LaunchArgumentParser
throw new ExitAfterHelpException(); throw new ExitAfterHelpException();
} }
public class ExitAfterHelpException : Exception
public LaunchArgumentParser() {
public 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 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++)
{
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!();
} }
} }

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<!--package info--> <!--package info-->
<PackageId>DTLib</PackageId> <PackageId>DTLib</PackageId>
<Version>1.4.1</Version> <Version>1.7.4</Version>
<Authors>Timerix</Authors> <Authors>Timerix</Authors>
<Description>Library for all my C# projects</Description> <Description>Library for all my C# projects</Description>
<RepositoryType>GIT</RepositoryType> <RepositoryType>GIT</RepositoryType>
@@ -14,7 +14,7 @@
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder> <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<!--language features--> <!--language features-->
<LangVersion>12</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>disable</ImplicitUsings> <ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow> <CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
@@ -31,6 +31,6 @@
<ProjectReference Include="..\DTLib.Demystifier\DTLib.Demystifier.csproj" /> <ProjectReference Include="..\DTLib.Demystifier\DTLib.Demystifier.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' "> <ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib.Demystifier" Version="1.1.0" /> <PackageReference Include="DTLib.Demystifier" Version="1.1.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,3 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=console/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=logging_005Cloggers/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -0,0 +1,37 @@
namespace DTLib.Extensions;
public static class TaskExtensions
{
// https://stackoverflow.com/a/69861689
public static Task<T> AsCancellable<T>(this Task<T> task, CancellationToken token)
{
if (!token.CanBeCanceled)
{
return task;
}
var tcs = new TaskCompletionSource<T>();
// This cancels the returned task:
// 1. If the token has been canceled, it cancels the TCS straightaway
// 2. Otherwise, it attempts to cancel the TCS whenever
// the token indicates cancelled
token.Register(() => tcs.TrySetCanceled(token),
useSynchronizationContext: false);
task.ContinueWith(t =>
{
// Complete the TCS per task status
// If the TCS has been cancelled, this continuation does nothing
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(t.Exception!);
else tcs.TrySetResult(t.Result);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
return tcs.Task;
}
}

View File

@@ -13,12 +13,13 @@ public static class Directory
if (dir.Contains(Path.Sep)) if (dir.Contains(Path.Sep))
{ {
var parentDir = dir.ParentDir(); var parentDir = dir.ParentDir();
if(!Exists(parentDir)) if (!Exists(parentDir))
Create(parentDir); Create(parentDir);
} }
System.IO.Directory.CreateDirectory(dir.Str); System.IO.Directory.CreateDirectory(dir.Str);
} }
/// копирует все файлы и папки /// копирует все файлы и папки
public static void Copy(IOPath sourceDir, IOPath newDir, bool owerwrite) public static void Copy(IOPath sourceDir, IOPath newDir, bool owerwrite)
{ {
@@ -54,7 +55,7 @@ public static class Directory
Delete(target_path); Delete(target_path);
else throw new Exception($"directory {target_path} already exists"); else throw new Exception($"directory {target_path} already exists");
} }
else Directory.Create(target_path.ParentDir()); else Create(target_path.ParentDir());
System.IO.Directory.Move(current_path.Str, target_path.Str); System.IO.Directory.Move(current_path.Str, target_path.Str);
} }
@@ -86,9 +87,9 @@ public static class Directory
all_subdirs = new List<IOPath>(); all_subdirs = new List<IOPath>();
return GetAllFiles_internal(dir, all_subdirs); return GetAllFiles_internal(dir, all_subdirs);
} }
private static List<IOPath> GetAllFiles_internal(IOPath dir, List<IOPath>? all_subdirs) private static List<IOPath> GetAllFiles_internal(IOPath dir, List<IOPath>? all_subdirs)
{ {
bool rememberSubdirs = all_subdirs is not null;
var all_files = new List<IOPath>(); var all_files = new List<IOPath>();
IOPath[] cur_files = GetFiles(dir); IOPath[] cur_files = GetFiles(dir);
for (int i = 0; i < cur_files.Length; i++) for (int i = 0; i < cur_files.Length; i++)
@@ -96,20 +97,12 @@ public static class Directory
IOPath[] cur_subdirs = GetDirectories(dir); IOPath[] cur_subdirs = GetDirectories(dir);
for (int i = 0; i < cur_subdirs.Length; i++) for (int i = 0; i < cur_subdirs.Length; i++)
{ {
if(rememberSubdirs)
all_subdirs?.Add(cur_subdirs[i]); all_subdirs?.Add(cur_subdirs[i]);
all_files.AddRange(GetAllFiles_internal(cur_subdirs[i], all_subdirs)); all_files.AddRange(GetAllFiles_internal(cur_subdirs[i], all_subdirs));
} }
return all_files; return all_files;
} }
public static string GetCurrent() => System.IO.Directory.GetCurrentDirectory(); public static IOPath GetCurrent() => new IOPath(System.IO.Directory.GetCurrentDirectory(), true);
public static void CreateSymlink(string sourcePath, string symlinkPath)
{
if (symlinkPath.Contains(Path.Sep))
Create(Path.ParentDir(symlinkPath));
if (!Symlink.CreateSymbolicLink(symlinkPath, sourcePath, Symlink.SymlinkTarget.Directory))
throw new InvalidOperationException($"some error occured while creating symlink\nDirectory.CreateSymlink({symlinkPath}, {sourcePath})");
}
} }

View File

@@ -1,4 +1,7 @@
namespace DTLib.Filesystem; using FileMode = System.IO.FileMode;
using FileAccess = System.IO.FileAccess;
namespace DTLib.Filesystem;
public static class File public static class File
{ {
@@ -14,23 +17,22 @@ public static class File
Directory.Create(file.ParentDir()); Directory.Create(file.ParentDir());
using System.IO.FileStream stream = System.IO.File.Create(file.Str); using System.IO.FileStream stream = System.IO.File.Create(file.Str);
stream.Close();
} }
public static void Copy(IOPath srcPath, IOPath newPath, bool overwrite) public static void Copy(IOPath srcPath, IOPath newPath, bool overwrite)
{ {
if (Exists(newPath)) if (Exists(newPath))
{ {
if(overwrite) System.IO.File.Delete(newPath.Str); if (overwrite)
Delete(newPath);
else throw new Exception($"file <{newPath}> alredy exists"); else throw new Exception($"file <{newPath}> alredy exists");
} }
else Directory.Create(newPath.ParentDir()); else Directory.Create(newPath.ParentDir());
using var srcFile=System.IO.File.Open(srcPath.Str, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
using var newFile=System.IO.File.Open(newPath.Str, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite); using var srcFile = OpenRead(srcPath);
using var newFile = OpenWrite(newPath);
srcFile.CopyTo(newFile); srcFile.CopyTo(newFile);
srcFile.Close();
newFile.Flush(); newFile.Flush();
newFile.Close();
} }
public static void Move(IOPath current_path, IOPath target_path, bool overwrite) public static void Move(IOPath current_path, IOPath target_path, bool overwrite)
@@ -42,6 +44,7 @@ public static class File
else throw new Exception($"file {target_path} already exists"); else throw new Exception($"file {target_path} already exists");
} }
else Directory.Create(target_path.ParentDir()); else Directory.Create(target_path.ParentDir());
System.IO.File.Move(current_path.Str, target_path.Str); System.IO.File.Move(current_path.Str, target_path.Str);
} }
@@ -49,63 +52,81 @@ public static class File
public static byte[] ReadAllBytes(IOPath file) public static byte[] ReadAllBytes(IOPath file)
{ {
using System.IO.FileStream stream = OpenRead(file); using System.IO.FileStream stream = OpenRead(file);
int size = GetSize(file).ToInt(); int size = GetSize(file).ToInt();
byte[] output = new byte[size]; byte[] output = new byte[size];
if (stream.Read(output, 0, size) < size) if (stream.Read(output, 0, size) < size)
throw new Exception("can't read all bytes"); throw new Exception("can't read all bytes");
stream.Close();
return output; return output;
} }
public static string ReadAllText(IOPath file) => ReadAllBytes(file).BytesToString(StringConverter.UTF8);
public static async Task<byte[]> ReadAllBytesAsync(IOPath file)
{
using System.IO.FileStream stream = OpenRead(file);
int size = GetSize(file).ToInt();
byte[] output = new byte[size];
if (await stream.ReadAsync(output, 0, size).ConfigureAwait(false) < size)
throw new Exception("can't read all bytes");
return output;
}
public static string ReadAllText(IOPath file) =>
ReadAllBytes(file).BytesToString(StringConverter.UTF8);
public static async Task<string> ReadAllTextAsync(IOPath file) =>
(await ReadAllBytesAsync(file)).BytesToString(StringConverter.UTF8);
public static void WriteAllBytes(IOPath file, byte[] content) public static void WriteAllBytes(IOPath file, byte[] content)
{ {
using System.IO.FileStream stream = OpenWrite(file); using System.IO.FileStream stream = OpenWrite(file);
stream.Write(content, 0, content.Length); stream.Write(content, 0, content.Length);
stream.Close(); }
public static async Task WriteAllBytesAsync(IOPath file, byte[] content)
{
using System.IO.FileStream stream = OpenWrite(file);
await stream.WriteAsync(content, 0, content.Length);
} }
public static void WriteAllText(IOPath file, string content) => WriteAllBytes(file, content.ToBytes(StringConverter.UTF8)); public static void WriteAllText(IOPath file, string content) =>
WriteAllBytes(file, content.ToBytes(StringConverter.UTF8));
public static async Task WriteAllTextAsync(IOPath file, string content) =>
await WriteAllBytesAsync(file, content.ToBytes(StringConverter.UTF8));
public static void AppendAllBytes(IOPath file, byte[] content) public static void AppendAllBytes(IOPath file, byte[] content)
{ {
using System.IO.FileStream stream = OpenAppend(file); using System.IO.FileStream stream = OpenAppend(file);
stream.Write(content, 0, content.Length); stream.Write(content, 0, content.Length);
stream.Close();
} }
public static void AppendAllText(IOPath file, string content) => AppendAllBytes(file, content.ToBytes(StringConverter.UTF8)); public static async Task AppendAllBytesAsync(IOPath file, byte[] content)
{
using System.IO.FileStream stream = OpenAppend(file);
await stream.WriteAsync(content, 0, content.Length);
}
public static void AppendAllText(IOPath file, string content) =>
AppendAllBytes(file, content.ToBytes(StringConverter.UTF8));
public static async Task AppendAllTextAsync(IOPath file, string content) =>
await AppendAllBytesAsync(file, content.ToBytes(StringConverter.UTF8));
public static System.IO.FileStream OpenRead(IOPath file) public static System.IO.FileStream OpenRead(IOPath file)
{ {
if (!Exists(file)) if (!Exists(file))
throw new Exception($"file not found: <{file}>"); throw new Exception($"file not found: <{file}>");
return System.IO.File.Open(file.Str, System.IO.FileMode.Open, System.IO.FileAccess.Read, return System.IO.File.Open(file.Str, FileMode.Open, FileAccess.Read,
System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete); System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete);
} }
public static System.IO.FileStream OpenWrite(IOPath file) public static System.IO.FileStream OpenWrite(IOPath file)
{ {
if (Exists(file)) Directory.Create(file.ParentDir());
Delete(file); return System.IO.File.Open(file.Str, FileMode.Create, FileAccess.Write, System.IO.FileShare.Read);
Create(file);
return System.IO.File.Open(file.Str, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.Read);
} }
public static System.IO.FileStream OpenAppend(IOPath file) public static System.IO.FileStream OpenAppend(IOPath file)
{ {
Directory.Create(file.ParentDir());
Create(file); return System.IO.File.Open(file.Str, FileMode.Append, FileAccess.Write, System.IO.FileShare.Read);
return System.IO.File.Open(file.Str, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.Read);
}
public static void CreateSymlink(IOPath sourcePath, IOPath symlinkPath)
{
if (symlinkPath.Contains(Path.Sep))
Directory.Create(symlinkPath.ParentDir());
if (!Symlink.CreateSymbolicLink(symlinkPath.Str, sourcePath.Str, Symlink.SymlinkTarget.File))
throw new InvalidOperationException($"some error occured while creating symlink\nFile.CreateSymlink({symlinkPath}, {sourcePath})");
} }
} }

View File

@@ -15,43 +15,54 @@ public readonly struct IOPath
public IOPath(char[] path, bool separatorsFixed=false) public IOPath(char[] path, bool separatorsFixed=false)
{ {
if (path.Length == 0)
throw new Exception("path is null or empty");
Str = separatorsFixed ? new string(path) : FixSeparators(path); Str = separatorsFixed ? new string(path) : FixSeparators(path);
} }
public IOPath(string path, bool separatorsFixed=false) public IOPath(string path, bool separatorsFixed=false) : this(path.ToCharArray(), separatorsFixed)
{ {
if (path.IsNullOrEmpty())
throw new Exception("path is null or empty");
Str = separatorsFixed ? path : FixSeparators(path.ToCharArray());
} }
static string FixSeparators(char[] path) static string FixSeparators(char[] path)
{ {
int length = path.Length; int length = path.Length;
if (path[length-1] == Path.Sep || path[length-1] == Path.NotSep) char lastChar = path[path.Length - 1];
if (lastChar == Path.Sep || lastChar == Path.NotSep)
length--; // removing trailing sep length--; // removing trailing sep
char[] fixed_path = new char[length]; StringBuilder sb = new StringBuilder();
bool prevWasSeparator = false;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
if (path[i] == Path.NotSep) if (path[i] == Path.NotSep)
fixed_path[i] = Path.Sep; {
else fixed_path[i] = path[i]; // prevent double separators like this "a//b"
if(!prevWasSeparator)
sb.Append(Path.Sep);
prevWasSeparator = true;
}
else
{
sb.Append(path[i]);
prevWasSeparator = false;
} }
return new string(fixed_path);
} }
public static IOPath[] ArrayCast(string[] a, bool correct_separators=false) return sb.ToString();
}
public static IOPath[] ArrayCast(string[] a, bool separatorsFixed=false)
{ {
IOPath[] b = new IOPath[a.Length]; IOPath[] b = new IOPath[a.Length];
for (int i = 0; i < a.Length; i++) for (int i = 0; i < a.Length; i++)
b[i] = new IOPath(a[i], correct_separators); b[i] = new IOPath(a[i], separatorsFixed);
return b; return b;
} }
public static IOPath[] ListCast(IList<string> a, bool correct_separators=false) public static IOPath[] ListCast(IList<string> a, bool separatorsFixed=false)
{ {
IOPath[] b = new IOPath[a.Count]; IOPath[] b = new IOPath[a.Count];
for (int i = 0; i < a.Count; i++) for (int i = 0; i < a.Count; i++)
b[i] = new IOPath(a[i], correct_separators); b[i] = new IOPath(a[i], separatorsFixed);
return b; return b;
} }

View File

@@ -5,7 +5,7 @@ namespace DTLib.Filesystem;
public static class Path public static class Path
{ {
public static readonly char Sep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '\\' : '/'; public static readonly char Sep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '\\' : '/';
public static readonly char NotSep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '/' : '\\' ; public static readonly char NotSep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '/' : '\\';
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -25,14 +25,19 @@ public static class Path
char c = r[i]; char c = r[i];
switch (c) switch (c)
{ {
case '\n': case '\r': case '\n':
case ':': case ';': case '\r':
case ':':
case ';':
break; break;
case '/': case '\\': case '/':
case '\\':
b.Append('-'); b.Append('-');
break; break;
case '<': case '>': case '<':
case '?': case '|': case '>':
case '?':
case '|':
b.Append('_'); b.Append('_');
break; break;
case '"': case '"':
@@ -46,6 +51,7 @@ public static class Path
break; break;
} }
} }
return new IOPath(b.ToString(), true); return new IOPath(b.ToString(), true);
} }
@@ -53,15 +59,15 @@ public static class Path
private static void CopyTo(this string s, char[] b, int startIndex) private static void CopyTo(this string s, char[] b, int startIndex)
{ {
for (int i = 0; i < s.Length; i++) for (int i = 0; i < s.Length; i++)
b[startIndex+i] = s[i]; b[startIndex + i] = s[i];
} }
#endif #endif
public static IOPath Concat(params IOPath[] parts) public static IOPath Concat(params IOPath[] parts)
{ {
var needSeparator = new bool[parts.Length-1]; var needSeparator = new bool[parts.Length - 1];
int lengthSum = 0; int lengthSum = 0;
for (int i = 0; i < parts.Length-1; i++) for (int i = 0; i < parts.Length - 1; i++)
{ {
lengthSum += parts[i].Length; lengthSum += parts[i].Length;
if (!parts[i].Str.EndsWith(Sep) && !parts[i + 1].Str.StartsWith(Sep)) if (!parts[i].Str.EndsWith(Sep) && !parts[i + 1].Str.StartsWith(Sep))
@@ -71,13 +77,14 @@ public static class Path
} }
else needSeparator[i] = false; else needSeparator[i] = false;
} }
lengthSum += parts[parts.Length-1].Length;
lengthSum += parts[parts.Length - 1].Length;
var buffer = new char[lengthSum]; var buffer = new char[lengthSum];
parts[0].Str.CopyTo(buffer, 0); parts[0].Str.CopyTo(buffer, 0);
int copiedChars = parts[0].Length; int copiedChars = parts[0].Length;
for (int i = 1; i < parts.Length; i++) for (int i = 1; i < parts.Length; i++)
{ {
if (needSeparator[i-1]) if (needSeparator[i - 1])
buffer[copiedChars++] = Sep; buffer[copiedChars++] = Sep;
parts[i].Str.CopyTo(buffer, copiedChars); parts[i].Str.CopyTo(buffer, copiedChars);
copiedChars += parts[i].Length; copiedChars += parts[i].Length;
@@ -89,11 +96,14 @@ public static class Path
/// returns just dir name or file name with extension /// returns just dir name or file name with extension
public static IOPath LastName(this IOPath path) public static IOPath LastName(this IOPath path)
{ {
if(path.Length < 2)
return path;
int i = path.LastIndexOf(Sep); int i = path.LastIndexOf(Sep);
if (i == path.Length - 1) // ends with separator if (i == path.Length - 1) // ends with separator
i = path.LastIndexOf(Sep, i-1); i = path.LastIndexOf(Sep, i - 1);
if (i == -1) return path; if (i == -1)
return path.Substring(i+1); return path;
return path.Substring(i + 1);
} }
public static IOPath Extension(this IOPath path) public static IOPath Extension(this IOPath path)
@@ -116,10 +126,10 @@ public static class Path
{ {
int i = path.LastIndexOf(Sep); int i = path.LastIndexOf(Sep);
if (i == path.Length - 1) // ends with separator if (i == path.Length - 1) // ends with separator
i = path.LastIndexOf(Sep, i-1); i = path.LastIndexOf(Sep, i - 1);
if (i == -1) // no parent dir if (i == -1) // no parent dir
return $".{Sep}"; return $".{Sep}";
return path.Remove(i+1); return path.Remove(i + 1);
} }
public static IOPath ReplaceBase(this IOPath path, IOPath baseDir, IOPath otherDir) public static IOPath ReplaceBase(this IOPath path, IOPath baseDir, IOPath otherDir)
@@ -133,6 +143,6 @@ public static class Path
{ {
if (!path.StartsWith(baseDir)) if (!path.StartsWith(baseDir))
throw new Exception($"path <{path}> doesnt starts with <{baseDir}"); throw new Exception($"path <{path}> doesnt starts with <{baseDir}");
return path.Substring(baseDir.Length+1); return path.Substring(baseDir.Length + 1);
} }
} }

View File

@@ -1,15 +0,0 @@
using System.Runtime.InteropServices;
namespace DTLib.Filesystem;
internal class Symlink
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern bool CreateSymbolicLink(string symlinkName, string sourceName, SymlinkTarget type);
internal enum SymlinkTarget
{
File,
Directory
}
}

View File

@@ -2,23 +2,16 @@ namespace DTLib.Logging;
public class DefaultLogFormat : ILogFormat public class DefaultLogFormat : ILogFormat
{ {
public bool PrintTimeStamp { get; set; } = true;
public bool PrintTimeStamp { get; set; } public bool PrintContext { get; set; } = true;
public bool PrintContext { get; set; } public bool PrintSeverity { get; set; } = true;
public bool PrintSeverity { get; set; } public string TimeStampFormat { get; set; } = MyTimeFormat.ForText;
public DefaultLogFormat(bool printTimeStamp = true, bool printContext = true, bool printSeverity = true)
{
PrintTimeStamp = printTimeStamp;
PrintContext = printContext;
PrintSeverity = printSeverity;
}
public string CreateMessage(string context, LogSeverity severity, object message) public string CreateMessage(string context, LogSeverity severity, object message)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
if (PrintTimeStamp) if (PrintTimeStamp)
sb.Append('[').Append(DateTime.Now.ToString(MyTimeFormat.ForText)).Append(']'); sb.Append('[').Append(DateTime.Now.ToString(TimeStampFormat)).Append(']');
if (PrintContext && PrintSeverity) if (PrintContext && PrintSeverity)
sb.Append('[').Append(context).Append('/').Append(severity.ToString()).Append(']'); sb.Append('[').Append(context).Append('/').Append(severity.ToString()).Append(']');
else if(PrintContext) else if(PrintContext)

View File

@@ -2,9 +2,5 @@ namespace DTLib.Logging;
public interface ILogFormat public interface ILogFormat
{ {
bool PrintTimeStamp { get; set; }
bool PrintContext { get; set; }
bool PrintSeverity { get; set; }
string CreateMessage(string context, LogSeverity severity, object message); string CreateMessage(string context, LogSeverity severity, object message);
} }

View File

@@ -11,8 +11,8 @@ public class CompositeLogger : ILogger
set set
{ {
_debugLogEnabled = value; _debugLogEnabled = value;
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].DebugLogEnabled = value; childLogger.DebugLogEnabled = value;
} }
} }
@@ -22,8 +22,8 @@ public class CompositeLogger : ILogger
set set
{ {
_infoLogEnabled = true; _infoLogEnabled = true;
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].InfoLogEnabled = value; childLogger.InfoLogEnabled = value;
} }
} }
@@ -33,8 +33,8 @@ public class CompositeLogger : ILogger
set set
{ {
_warnLogEnabled = value; _warnLogEnabled = value;
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].WarnLogEnabled = value; childLogger.WarnLogEnabled = value;
} }
} }
@@ -44,46 +44,38 @@ public class CompositeLogger : ILogger
set set
{ {
_errorLogenabled = value; _errorLogenabled = value;
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].ErrorLogEnabled = value; childLogger.ErrorLogEnabled = value;
} }
} }
public ILogFormat Format { get; set; } public ILogFormat Format { get; set; }
public readonly List<ILogger> ChildLoggers;
protected ILogger[] _loggers; private bool _debugLogEnabled;
private bool _debugLogEnabled =
#if DEBUG
true;
#else
false;
#endif
private bool _infoLogEnabled = true; private bool _infoLogEnabled = true;
private bool _warnLogEnabled = true; private bool _warnLogEnabled = true;
private bool _errorLogenabled = true; private bool _errorLogenabled = true;
public CompositeLogger(ILogFormat format, params ILogger[] loggers) public CompositeLogger(ILogFormat format, params ILogger[] childLoggers)
{ {
Format = format; 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) public void Log(string context, LogSeverity severity, object message, ILogFormat format)
{ {
if(!this.CheckSeverity(severity)) foreach (var childLogger in ChildLoggers)
return; childLogger.Log(context, severity, message, format);
for (int i = 0; i < _loggers.Length; i++)
_loggers[i].Log(context, severity, message, format);
} }
public void Dispose() public void Dispose()
{ {
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].Dispose(); childLogger.Dispose();
} }
} }

View File

@@ -27,7 +27,7 @@ public class ConsoleLogger : ILogger
var msg = format.CreateMessage(context, severity, message); var msg = format.CreateMessage(context, severity, message);
lock (consolelocker) lock (consolelocker)
ColoredConsole.WriteLine(ColorFromSeverity(severity),msg); ColoredConsole.WriteLine(msg, fg: ColorFromSeverity(severity));
} }
private static ConsoleColor ColorFromSeverity(LogSeverity severity) private static ConsoleColor ColorFromSeverity(LogSeverity severity)

View File

@@ -1,13 +1,11 @@
using DTLib.Logging; namespace DTLib.Logging;
namespace launcher_client; public interface ILineWriter : IDisposable
public interface IConsoleWrapper : IDisposable
{ {
public void WriteLine(string msg); public void WriteLine(string msg);
} }
public class ConsoleWrapperLogger : ILogger public class LineWriterLogger : ILogger
{ {
public bool DebugLogEnabled { get; set; } = false; public bool DebugLogEnabled { get; set; } = false;
public bool InfoLogEnabled { get; set; } = true; public bool InfoLogEnabled { get; set; } = true;
@@ -15,16 +13,16 @@ public class ConsoleWrapperLogger : ILogger
public bool ErrorLogEnabled { get; set; } = true; public bool ErrorLogEnabled { get; set; } = true;
public ILogFormat Format { get; set; } public ILogFormat Format { get; set; }
private readonly IConsoleWrapper _consoleWrapper; private readonly ILineWriter _lineWriter;
public ConsoleWrapperLogger(IConsoleWrapper consoleWrapper, ILogFormat format) public LineWriterLogger(ILineWriter lineWriter, ILogFormat format)
{ {
_consoleWrapper = consoleWrapper; _lineWriter = lineWriter;
Format = format; Format = format;
} }
public ConsoleWrapperLogger(IConsoleWrapper consoleWrapper) public LineWriterLogger(ILineWriter lineWriter)
: this(consoleWrapper, new DefaultLogFormat()) : this(lineWriter, new DefaultLogFormat())
{} {}
public void Log(string context, LogSeverity severity, object message, ILogFormat format) public void Log(string context, LogSeverity severity, object message, ILogFormat format)
@@ -33,14 +31,14 @@ public class ConsoleWrapperLogger : ILogger
return; return;
var msg = format.CreateMessage(context, severity, message); var msg = format.CreateMessage(context, severity, message);
lock (_consoleWrapper) lock (_lineWriter)
_consoleWrapper.WriteLine(msg); _lineWriter.WriteLine(msg);
} }
public void Dispose() public void Dispose()
{ {
_consoleWrapper.Dispose(); _lineWriter.Dispose();
} }
~ConsoleWrapperLogger() => Dispose(); ~LineWriterLogger() => Dispose();
} }

View File

@@ -4,4 +4,6 @@ public static class MyTimeFormat
{ {
public const string ForFileNames="yyyy.MM.dd_HH-mm-ss_zz"; public const string ForFileNames="yyyy.MM.dd_HH-mm-ss_zz";
public const string ForText="yyyy.MM.dd HH:mm:ss zz"; public const string ForText="yyyy.MM.dd HH:mm:ss zz";
public const string TimeOnly="HH:mm:ss";
public const string DateOnly="yyyy.MM.dd";
} }

69
DTLib/TransformStream.cs Normal file
View File

@@ -0,0 +1,69 @@
using System.IO;
using File = System.IO.File;
namespace DTLib;
/// <summary>
/// Stream wrapper for some operations over data chunks.
/// You can add multiple transform operations.
/// <example><code>
/// using var pipe = new TransformStream(File.OpenRead("encrypted"))
/// .AddTransform(ReportProgress)
/// .AddTransform(Decrypt);
/// using var o = File.OpenWrite("decrypted");
/// pipe.CopyTo(o);
/// </code></example>
/// </summary>
public class TransformStream : Stream
{
private readonly Stream _inputStream;
public delegate void TransformFuncDelegate(byte[] buffer, int offset, int count);
private List<TransformFuncDelegate> _transformFunctions = new();
public TransformStream(Stream inputStream)
{
_inputStream = inputStream;
}
public TransformStream AddTransform(TransformFuncDelegate f)
{
_transformFunctions.Add(f);
return this;
}
public TransformStream AddTransforms(IEnumerable<TransformFuncDelegate> f)
{
_transformFunctions.AddRange(f);
return this;
}
public override bool CanRead => _inputStream.CanRead;
public override bool CanSeek => _inputStream.CanSeek;
public override bool CanWrite => false;
public override long Length => _inputStream.Length;
public override long Position { get => _inputStream.Position; set => _inputStream.Position = value; }
public override int Read(byte[] buffer, int offset, int count)
{
int read = _inputStream.Read(buffer, offset, count);
if(read > 0)
{
for (int i = 0; i < _transformFunctions.Count; i++)
{
_transformFunctions[i].Invoke(buffer, offset, read);
}
}
return read;
}
public override void Flush() {}
public override long Seek(long offset, SeekOrigin origin) => _inputStream.Seek(offset, origin);
public override void SetLength(long value) => throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
public override void Close() => _inputStream.Close();
}

24
pack.sh
View File

@@ -1,17 +1,10 @@
#!/usr/bin/bash #!/usr/bin/bash
set -eo pipefail set -eo pipefail
if [[ -d nuget ]]; then rm -rf nuget
echo "archiving old ./nuget/ content"
mkdir -p nuget_old
TIMESTAMP=$(date +%Y.%m.%d_%H-%M-%S)
mv nuget "nuget_$TIMESTAMP"
tar cvf "nuget_old/nuget_$TIMESTAMP.tar" "nuget_$TIMESTAMP"
rm -rf "nuget_$TIMESTAMP"
fi
mkdir nuget mkdir nuget
function create_package() { function build_package() {
echo "----------[$1]----------" echo "----------[$1]----------"
cd "$1" || return 1 cd "$1" || return 1
dotnet pack -c Release -o bin/pack_tmp || return 1 dotnet pack -c Release -o bin/pack_tmp || return 1
@@ -21,9 +14,12 @@ function create_package() {
cd .. cd ..
} }
echo "building packages" packages_to_build="$@"
create_package DTLib.Demystifier if [ -z "$packages_to_build" ]; then
create_package DTLib.XXHash packages_to_build='DTLib.Demystifier DTLib.XXHash DTLib DTLib.Logging.Microsoft DTLib.Web'
create_package DTLib fi
create_package DTLib.Logging.Microsoft echo "building packages $packages_to_build"
for p in $packages_to_build; do
build_package "$p"
done
ls -shk nuget ls -shk nuget