Skip to content

Commit

Permalink
Add some constructor picking and arg resolve
Browse files Browse the repository at this point in the history
  • Loading branch information
BigBang1112 committed Jul 18, 2024
1 parent ab4b4be commit cfed157
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 59 deletions.
71 changes: 71 additions & 0 deletions Src/GBX.NET.Tool.CLI/ArgsResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using GBX.NET.Tool.CLI.Inputs;

namespace GBX.NET.Tool.CLI;

internal sealed class ArgsResolver
{
private readonly string[] args;
private readonly HttpClient client;

public bool HasArgs => args.Length > 0;

public ArgsResolver(string[] args, HttpClient http)
{
this.args = args;
this.client = http;
}

public ToolConfiguration Resolve()
{
if (!HasArgs)
{
return new();
}

var inputs = new List<Input>();

var argsEnumerator = args.GetEnumerator();

while (argsEnumerator.MoveNext())
{
var arg = argsEnumerator.Current as string ?? string.Empty;

if (arg.StartsWith('-'))
{
continue;
}

// - check http:// and https:// for URLs
// - check for individual files and files in zip archives
// - check for folders
// - check for stdin (maybe?)
// - check for configured user data path
if (Directory.Exists(arg))
{
inputs.Add(new DirectoryInput(arg));
continue;
}

if (File.Exists(arg))
{
inputs.Add(new FileInput(arg));
continue;
}

if (Uri.TryCreate(arg, UriKind.Absolute, out var uri))
{
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{
inputs.Add(new UriInput(client, uri));
}

continue;
}
}

return new ToolConfiguration
{
Inputs = inputs
};
}
}
6 changes: 6 additions & 0 deletions Src/GBX.NET.Tool.CLI/ConsoleOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace GBX.NET.Tool.CLI;

public sealed class ConsoleOptions
{
public bool DisableUpdateCheck { get; set; }
}
1 change: 1 addition & 0 deletions Src/GBX.NET.Tool.CLI/GBX.NET.Tool.CLI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\GBX.NET.LZO\GBX.NET.LZO.csproj" />
<ProjectReference Include="..\GBX.NET.Tool\GBX.NET.Tool.csproj" />
</ItemGroup>

Expand Down
27 changes: 27 additions & 0 deletions Src/GBX.NET.Tool.CLI/Inputs/DirectoryInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

using GBX.NET.Exceptions;

namespace GBX.NET.Tool.CLI.Inputs;

internal sealed record DirectoryInput(string DirectoryPath) : Input
{
public override async Task<object?> ResolveAsync(CancellationToken cancellationToken)
{
var files = Directory.GetFiles(DirectoryPath, "*.*", SearchOption.AllDirectories);

var tasks = files.Select<string, Task<object?>>(async file =>
{
try
{
await using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true);
return await Gbx.ParseAsync(stream, cancellationToken: cancellationToken);
}
catch (NotAGbxException)
{
return await File.ReadAllBytesAsync(file, cancellationToken);
}
});

return await Task.WhenAll(tasks);
}
}
20 changes: 20 additions & 0 deletions Src/GBX.NET.Tool.CLI/Inputs/FileInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

using GBX.NET.Exceptions;

namespace GBX.NET.Tool.CLI.Inputs;

internal sealed record FileInput(string FilePath) : Input
{
public override async Task<object?> ResolveAsync(CancellationToken cancellationToken)
{
try
{
await using var stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true);
return await Gbx.ParseAsync(stream, cancellationToken: cancellationToken);
}
catch (NotAGbxException)
{
return File.ReadAllBytesAsync(FilePath, cancellationToken);
}
}
}
7 changes: 7 additions & 0 deletions Src/GBX.NET.Tool.CLI/Inputs/Input.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

namespace GBX.NET.Tool.CLI.Inputs;

internal abstract record Input
{
public abstract Task<object?> ResolveAsync(CancellationToken cancellationToken);
}
24 changes: 24 additions & 0 deletions Src/GBX.NET.Tool.CLI/Inputs/UriInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

using GBX.NET.Exceptions;

namespace GBX.NET.Tool.CLI.Inputs;

internal sealed record UriInput(HttpClient Http, Uri Uri) : Input
{
public override async Task<object?> ResolveAsync(CancellationToken cancellationToken)
{
using var response = await Http.GetAsync(Uri, cancellationToken);

response.EnsureSuccessStatusCode();

try
{
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
return await Gbx.ParseAsync(stream, cancellationToken: cancellationToken);
}
catch (NotAGbxException)
{
return await response.Content.ReadAsByteArrayAsync(cancellationToken);
}
}
}
9 changes: 9 additions & 0 deletions Src/GBX.NET.Tool.CLI/ToolConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using GBX.NET.Tool.CLI.Inputs;

namespace GBX.NET.Tool.CLI;

internal sealed class ToolConfiguration
{
public ConsoleOptions ConsoleOptions { get; init; } = new();
public IReadOnlyCollection<Input> Inputs { get; init; } = [];
}
130 changes: 115 additions & 15 deletions Src/GBX.NET.Tool.CLI/ToolConsole.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using GBX.NET.Tool.CLI.Exceptions;
using GBX.NET.LZO;
using GBX.NET.Tool.CLI.Exceptions;
using GBX.NET.Tool.CLI.Inputs;
using Microsoft.Extensions.Logging;
using Spectre.Console;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace GBX.NET.Tool.CLI;

Expand All @@ -16,6 +19,11 @@ public ToolConsole(string[] args, HttpClient http)
this.http = http;
}

static ToolConsole()
{
Gbx.LZO = new Lzo();
}

public static async Task<ToolConsoleRunResult<T>> RunAsync(string[] args)
{
using var http = new HttpClient();
Expand All @@ -32,37 +40,38 @@ public static async Task<ToolConsoleRunResult<T>> RunAsync(string[] args)
catch (ConsoleProblemException ex)
{
AnsiConsole.MarkupInterpolated($"[yellow]{ex.Message}[/]");
AnsiConsole.WriteLine();
AnsiConsole.Markup("Press any key to continue...");
Console.ReadKey(true);
}
catch (OperationCanceledException)
{
AnsiConsole.Markup("[yellow]Operation canceled.[/]");
AnsiConsole.WriteLine();
AnsiConsole.Markup("Press any key to continue...");
Console.ReadKey(true);
}
catch (Exception ex)
{
AnsiConsole.WriteException(ex);
AnsiConsole.WriteLine();
AnsiConsole.Markup("Press any key to continue...");
Console.ReadKey(true);
}

AnsiConsole.WriteLine();
AnsiConsole.Markup("Press any key to continue...");
Console.ReadKey(true);

return new ToolConsoleRunResult<T>(tool);
}

private async Task RunAsync(CancellationToken cancellationToken)
{
var argsResolver = new ArgsResolver(args, http);
var toolConfig = argsResolver.Resolve();

// Request update info and additional stuff
var updateChecker = ToolUpdateChecker.Check(http);
var updateChecker = toolConfig.ConsoleOptions.DisableUpdateCheck
? null
: ToolUpdateChecker.Check(http);

await IntroWriter<T>.WriteIntroAsync(args);

// Check for updates here if received. If not, check at the end of the tool execution
var updateCheckCompleted = await updateChecker.TryCompareVersionAsync(cancellationToken);
var updateCheckCompleted = updateChecker is null
|| await updateChecker.TryCompareVersionAsync(cancellationToken);

AnsiConsole.WriteLine();

Expand All @@ -73,16 +82,107 @@ private async Task RunAsync(CancellationToken cancellationToken)
// See what the tool can do
var toolFunctionality = ToolFunctionalityResolver<T>.Resolve(logger);

// Read the files from the arguments
// Quickly invalidate ones that do not meet functionality
var resolvedInputsDict = new Dictionary<Input, object?>();
var pickedCtor = default(ConstructorInfo);
var paramsForCtor = new List<object>();

foreach (var constructor in toolFunctionality.Constructors)
{
var isInvalidCtor = false;
var parameters = constructor.GetParameters();

if (parameters.Length == 0)
{
// inputless tools?
continue;
}

// issue here is with multiple inputs where it should repeat tool constructors
var inputEnumerator = toolConfig.Inputs.GetEnumerator();
if (!inputEnumerator.MoveNext())
{
continue;
}
var input = inputEnumerator.Current;

foreach (var parameter in parameters)
{
var type = parameter.ParameterType;

if (type == typeof(ILogger))
{
paramsForCtor.Add(logger);
continue;
}

if (type == typeof(Gbx))
{
var resolvedObject = await input.ResolveAsync(cancellationToken);

if (resolvedObject is not Gbx)
{
isInvalidCtor = true;
break;
}

paramsForCtor.Add(resolvedObject);
continue;
}

if (!type.IsGenericType)
{
continue;
}

var typeDef = type.GetGenericTypeDefinition();

if (typeDef == typeof(Gbx<>))
{
var nodeType = type.GetGenericArguments()[0];

var resolvedObject = await input.ResolveAsync(cancellationToken);

if (resolvedObject is not Gbx gbx || gbx.Node?.GetType() != nodeType)
{
isInvalidCtor = true;
break;
}

paramsForCtor.Add(resolvedObject);
continue;
}

if (typeDef == typeof(IEnumerable<>))
{
var elementType = type.GetGenericArguments()[0];
continue;
}
}

if (isInvalidCtor)
{
paramsForCtor.Clear();
}
else
{
pickedCtor = constructor;
break;
}
}

// Check again for updates if not done before
if (!updateCheckCompleted)
if (!updateCheckCompleted && updateChecker is not null)
{
updateCheckCompleted = await updateChecker.TryCompareVersionAsync(cancellationToken);
}

// Instantiate the tool
if (pickedCtor is null)
{
throw new ConsoleProblemException("Invalid files passed to the tool.");
}

var toolInstance = pickedCtor.Invoke(paramsForCtor.ToArray());

// Run all produce methods in parallel and run mutate methods in sequence

Expand Down
6 changes: 4 additions & 2 deletions Src/GBX.NET.Tool/ToolFunctionality.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace GBX.NET.Tool;
using System.Reflection;

namespace GBX.NET.Tool;

public sealed class ToolFunctionality<T> where T : ITool
{
public required object?[] InputParameters { get; init; }
public required ConstructorInfo[] Constructors { get; init; }
}
Loading

0 comments on commit cfed157

Please sign in to comment.