diff --git a/Wabbajack.CLI/CommandLineBuilder.cs b/Wabbajack.CLI/CommandLineBuilder.cs index 91fc91ae..0c0efa68 100644 --- a/Wabbajack.CLI/CommandLineBuilder.cs +++ b/Wabbajack.CLI/CommandLineBuilder.cs @@ -1,28 +1,139 @@ +using System; using System.Collections.Generic; using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.NamingConventionBinder; +using System.ComponentModel.Design; +using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Wabbajack.CLI.Verbs; +using Wabbajack.Paths; using Wabbajack.Services.OSIntegrated; namespace Wabbajack.CLI; -public class CommandLineBuilder +public partial class CommandLineBuilder { private readonly IConsole _console; private readonly IEnumerable _verbs; + private static IServiceProvider _provider; - public CommandLineBuilder(IEnumerable verbs, IConsole console, LoggingRateLimiterReporter _) + public CommandLineBuilder(IServiceProvider provider, IConsole console, LoggingRateLimiterReporter _) { - _console = console; - _verbs = verbs; + _provider = provider; + } + + static CommandLineBuilder() + { + RegisterAll(); } public async Task Run(string[] args) { var root = new RootCommand(); - foreach (var verb in _verbs) - root.Add(verb.MakeCommand()); - + foreach (var verb in _commands) + { + root.Add(MakeCommend(verb.Type, verb.Handler, verb.Definition)); + } + return await root.InvokeAsync(args); } + + private static Dictionary> _optionCtors = new() + { + { + typeof(string), + d => new Option(d.Aliases, description: d.Description) + }, + { + typeof(AbsolutePath), + d => new Option(d.Aliases, description: d.Description, parseArgument: d => d.Tokens.Single().Value.ToAbsolutePath()) + }, + { + typeof(Uri), + d => new Option(d.Aliases, description: d.Description) + }, + { + typeof(bool), + d => new Option(d.Aliases, description: d.Description) + }, + + }; + + private Command MakeCommend(Type verbType, Func verbHandler, VerbDefinition definition) + { + var command = new Command(definition.Name, definition.Description); + foreach (var option in definition.Options) + { + command.Add(_optionCtors[option.Type](option)); + } + command.Handler = new HandlerDelegate(_provider, verbType, verbHandler); + return command; + } + + private class HandlerDelegate : ICommandHandler + { + private IServiceProvider _provider; + private Type _type; + private readonly Func _delgate; + + public HandlerDelegate(IServiceProvider provider, Type type, Func inner) + { + _provider = provider; + _type = type; + _delgate = inner; + } + public int Invoke(InvocationContext context) + { + var service = (IVerb)_provider.GetRequiredService(_type); + var handler = CommandHandler.Create(_delgate(service)); + return handler.Invoke(context); + } + + public Task InvokeAsync(InvocationContext context) + { + var service = (IVerb)_provider.GetRequiredService(_type); + var handler = CommandHandler.Create(_delgate(service)); + return handler.InvokeAsync(context); + } + } + + private static List<(Type Type, VerbDefinition Definition, Func Handler)> _commands { get; set; } = new(); + public static IEnumerable Verbs => _commands.Select(c => c.Type); + public static void RegisterCommand(VerbDefinition definition, Func handler) + { + _commands.Add((typeof(T), definition, handler)); + + } +} + +public record OptionDefinition(Type Type, string ShortOption, string LongOption, string Description) +{ + public string[] Aliases + { + get + { + return new[] { "-" + ShortOption, "--" + LongOption }; + } + } +} + +public record VerbDefinition(string Name, string Description, OptionDefinition[] Options) +{ +} + +public static class CommandLineBuilderExtensions +{ + public static IServiceCollection AddCommands(this IServiceCollection services) + { + services.AddSingleton(); + + foreach (var verb in CommandLineBuilder.Verbs) + { + services.AddSingleton(verb); + } + + return services; + } } \ No newline at end of file diff --git a/Wabbajack.CLI/Program.cs b/Wabbajack.CLI/Program.cs index 1a1a56f6..a5575efd 100644 --- a/Wabbajack.CLI/Program.cs +++ b/Wabbajack.CLI/Program.cs @@ -57,32 +57,7 @@ internal class Program services.AddTransient(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - + services.AddCommands(); services.AddSingleton(); }).Build(); diff --git a/Wabbajack.CLI/VerbRegistration.cs b/Wabbajack.CLI/VerbRegistration.cs new file mode 100644 index 00000000..a04dbf5c --- /dev/null +++ b/Wabbajack.CLI/VerbRegistration.cs @@ -0,0 +1,32 @@ + +namespace Wabbajack.CLI; +using Wabbajack.CLI.Verbs; + +public partial class CommandLineBuilder { + +private static void RegisterAll() { +RegisterCommand(Compile.Definition, c => ((Compile)c).Run); +RegisterCommand(Decrypt.Definition, c => ((Decrypt)c).Run); +RegisterCommand(DownloadAll.Definition, c => ((DownloadAll)c).Run); +RegisterCommand(DownloadUrl.Definition, c => ((DownloadUrl)c).Run); +RegisterCommand(DumpZipInfo.Definition, c => ((DumpZipInfo)c).Run); +RegisterCommand(Encrypt.Definition, c => ((Encrypt)c).Run); +RegisterCommand(Extract.Definition, c => ((Extract)c).Run); +RegisterCommand(ForceHeal.Definition, c => ((ForceHeal)c).Run); +RegisterCommand(HashFile.Definition, c => ((HashFile)c).Run); +RegisterCommand(HashUrlString.Definition, c => ((HashUrlString)c).Run); +RegisterCommand(Install.Definition, c => ((Install)c).Run); +RegisterCommand(InstallCompileInstallVerify.Definition, c => ((InstallCompileInstallVerify)c).Run); +RegisterCommand(ListCreationClubContent.Definition, c => ((ListCreationClubContent)c).Run); +RegisterCommand(ListGames.Definition, c => ((ListGames)c).Run); +RegisterCommand(ListModlists.Definition, c => ((ListModlists)c).Run); +RegisterCommand(MirrorFile.Definition, c => ((MirrorFile)c).Run); +RegisterCommand(ModlistReport.Definition, c => ((ModlistReport)c).Run); +RegisterCommand(SteamDownloadFile.Definition, c => ((SteamDownloadFile)c).Run); +RegisterCommand(SteamDumpAppInfo.Definition, c => ((SteamDumpAppInfo)c).Run); +RegisterCommand(SteamLogin.Definition, c => ((SteamLogin)c).Run); +RegisterCommand(UploadToNexus.Definition, c => ((UploadToNexus)c).Run); +RegisterCommand(ValidateLists.Definition, c => ((ValidateLists)c).Run); +RegisterCommand(VFSIndex.Definition, c => ((VFSIndex)c).Run); +} +} \ No newline at end of file diff --git a/Wabbajack.CLI/VerbRegistration.tt b/Wabbajack.CLI/VerbRegistration.tt new file mode 100644 index 00000000..b6655fe3 --- /dev/null +++ b/Wabbajack.CLI/VerbRegistration.tt @@ -0,0 +1,26 @@ +<#@ template language="C#v3.5" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.IO"#> +<#@ import namespace="System.Collections.Generic" #> + +namespace Wabbajack.CLI; +using Wabbajack.CLI.Verbs; + +public partial class CommandLineBuilder { + +private static void RegisterAll() { +<# +foreach (var verb in Directory.EnumerateFiles("Verbs")) +{ + var klass = verb.Split('\\').Last().Split('.').First(); + if (klass == "IVerb") continue; + #> +RegisterCommand<<#=klass#>>(<#=klass#>.Definition, c => ((<#=klass#>)c).Run); +<# +} + +#> +} +} \ No newline at end of file diff --git a/Wabbajack.CLI/Verbs/Compile.cs b/Wabbajack.CLI/Verbs/Compile.cs index e332d2bf..4abb1e5f 100644 --- a/Wabbajack.CLI/Verbs/Compile.cs +++ b/Wabbajack.CLI/Verbs/Compile.cs @@ -38,16 +38,12 @@ public class Compile : IVerb _inferencer = inferencer; } - public Command MakeCommand() + public static VerbDefinition Definition = new("compile", "Compiles a modlist", + new[] { - var command = new Command("compile"); - command.Add(new Option(new[] {"-i", "-installPath"}, "Install Path")); - command.Add(new Option(new[] {"-o", "-output"}, "Output")); - command.Description = "Installs a modlist, compiles it, installs it again, verifies it"; - command.Handler = CommandHandler.Create(Run); - return command; - } - + new OptionDefinition(typeof(AbsolutePath), "i", "installPath", "Install Path"), + new OptionDefinition(typeof(AbsolutePath), "o", "outputPath", "OutputPath") + }); public async Task Run(AbsolutePath installPath, AbsolutePath outputPath, CancellationToken token) { diff --git a/Wabbajack.CLI/Verbs/Decrypt.cs b/Wabbajack.CLI/Verbs/Decrypt.cs index e3d7df47..4ec4c81f 100644 --- a/Wabbajack.CLI/Verbs/Decrypt.cs +++ b/Wabbajack.CLI/Verbs/Decrypt.cs @@ -19,16 +19,14 @@ public class Decrypt : IVerb _logger = logger; } - public Command MakeCommand() - { - var command = new Command("decrypt"); - command.Add(new Option(new[] {"-o", "-output"}, "Output file path")); - command.Add(new Option(new[] {"-n", "-name"}, "Name of the key to load data from")); - command.Description = "Decrypts a file from the Wabbajack encrypted storage"; - command.Handler = CommandHandler.Create(Run); - return command; - } - + public static VerbDefinition Definition = new VerbDefinition("decrypt", + "Decrypts a file from the wabbajack encrypted storage", + new[] + { + new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output file path"), + new OptionDefinition(typeof(string), "n", "name", "Name of the key to load data from") + }); + public async Task Run(AbsolutePath output, string name) { var data = await name.ToRelativePath() diff --git a/Wabbajack.CLI/Verbs/DownloadAll.cs b/Wabbajack.CLI/Verbs/DownloadAll.cs index cc1b4ffa..04a1c29d 100644 --- a/Wabbajack.CLI/Verbs/DownloadAll.cs +++ b/Wabbajack.CLI/Verbs/DownloadAll.cs @@ -41,16 +41,14 @@ public class DownloadAll : IVerb _cache = cache; } - public Command MakeCommand() - { - var command = new Command("download-all"); - command.Add(new Option(new[] {"-o", "-output"}, "Output folder")); - command.Description = "Downloads all files for all modlists in the gallery"; - command.Handler = CommandHandler.Create(Run); - return command; - } - - private async Task Run(AbsolutePath output, CancellationToken token) + public static VerbDefinition Definition = new VerbDefinition("download-all", + "Downloads all files for all modlists in the gallery", + new[] + { + new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output folder") + }); + + internal async Task Run(AbsolutePath output, CancellationToken token) { _logger.LogInformation("Downloading modlists"); diff --git a/Wabbajack.CLI/Verbs/DownloadCef.cs b/Wabbajack.CLI/Verbs/DownloadCef.cs deleted file mode 100644 index 5b140bc3..00000000 --- a/Wabbajack.CLI/Verbs/DownloadCef.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.CommandLine; -using System.CommandLine.Invocation; -using System.CommandLine.NamingConventionBinder; -using System.Diagnostics; -using System.IO; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using ICSharpCode.SharpZipLib.BZip2; -using ICSharpCode.SharpZipLib.Tar; -using Microsoft.Extensions.Logging; -using Wabbajack.CLI.DTOs; -using Wabbajack.Downloaders; -using Wabbajack.DTOs; -using Wabbajack.Paths; -using Wabbajack.Paths.IO; -using Version = System.Version; - -namespace Wabbajack.CLI.Verbs; - -public class DownloadCef : IVerb -{ - private readonly DownloadDispatcher _dispatcher; - private readonly FileExtractor.FileExtractor _fileExtractor; - private readonly HttpClient _httpClient; - private readonly ILogger _logger; - - public DownloadCef(ILogger logger, DownloadDispatcher dispatcher, - FileExtractor.FileExtractor fileExtractor, HttpClient httpClient) - { - _logger = logger; - _dispatcher = dispatcher; - _fileExtractor = fileExtractor; - _httpClient = httpClient; - } - - public Command MakeCommand() - { - var command = new Command("download-cef"); - command.Add(new Option(new[] {"-f", "-folder"}, "Path to Wabbajack")); - command.Add(new Option(new[] {"--force"}, "Force the download even if the output already exists")); - command.Description = "Downloads CEF into this folder"; - command.Handler = CommandHandler.Create(Run); - return command; - } - - - public async Task Run(AbsolutePath folder, bool force = false) - { - if (folder == default) folder = KnownFolders.EntryPoint; - - var cefNet = folder.Combine("CefNet.dll"); - if (!cefNet.FileExists()) - { - _logger.LogError("Cannot find CefNet.dll in {folder}", folder); - return 1; - } - - var version = Version.Parse(FileVersionInfo.GetVersionInfo(cefNet.ToString()).FileVersion!); - var downloadVersion = $"{version.Major}.{version.Minor}"; - var runtime = RuntimeInformation.RuntimeIdentifier; - if (folder.Combine("libcef.dll").FileExists() && !force) - { - _logger.LogInformation("Not downloading, cef already exists"); - return 0; - } - - _logger.LogInformation("Downloading Cef version {version} for {runtime}", downloadVersion, runtime); - - var versions = await CefCDNResponse.Load(_httpClient); - - var findSource = versions.FindSource(downloadVersion); - - var fileUri = new Uri($"https://cef-builds.spotifycdn.com/{findSource.Name}"); - - var parsed = _dispatcher.Parse(fileUri); - var tempFile = folder.Combine(findSource.Name); - await _dispatcher.Download(new Archive {State = parsed!}, tempFile, CancellationToken.None); - - { - _logger.LogInformation("Extracting {file}", tempFile); - - await using var istream = tempFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); - await using var bzip2 = new BZip2InputStream(istream); - await using var tar = new TarInputStream(bzip2, Encoding.UTF8); - var prefix = tempFile.FileName.WithoutExtension().WithoutExtension().Combine("Release"); - var fullPrefix = prefix.RelativeTo(folder); - while (true) - { - var entry = tar.GetNextEntry(); - if (entry == null) break; - - var path = entry.Name.ToRelativePath(); - - if (path.InFolder(prefix) && entry.Size > 0) - { - var outputPath = path.RelativeTo(folder).RelativeTo(fullPrefix).RelativeTo(folder); - outputPath.Parent.CreateDirectory(); - - _logger.LogInformation("Extracting {FileName} to {Folder}", outputPath.FileName, - outputPath.RelativeTo(folder)); - await using var os = outputPath.Open(FileMode.Create, FileAccess.Write); - tar.CopyEntryContents(os); - } - } - } - - tempFile.Delete(); - - return 0; - } -} \ No newline at end of file diff --git a/Wabbajack.CLI/Verbs/DownloadUrl.cs b/Wabbajack.CLI/Verbs/DownloadUrl.cs index 08b7cfe9..5ffbbc54 100644 --- a/Wabbajack.CLI/Verbs/DownloadUrl.cs +++ b/Wabbajack.CLI/Verbs/DownloadUrl.cs @@ -24,18 +24,15 @@ public class DownloadUrl : IVerb _dispatcher = dispatcher; } - public Command MakeCommand() - { - var command = new Command("download-url"); - command.Add(new Option(new[] {"-u", "-url"}, "Url to parse")); - command.Add(new Option(new[] {"-o", "-output"}, "Output file")); - command.Add(new Option(new [] {"-p", "--proxy"}, "Use the Wabbajack Proxy (default: true)")); - command.Description = "Downloads a file to a given output"; - command.Handler = CommandHandler.Create(Run); - return command; - } - - private async Task Run(Uri url, AbsolutePath output, bool proxy = true) + public static VerbDefinition Definition = new VerbDefinition("download-url", "Downloads a file to a given output", + new[] + { + new OptionDefinition(typeof(Uri), "u", "url", "Url to parse"), + new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output File"), + new OptionDefinition(typeof(bool), "p", "proxy", "Use the Wabbajack Proxy (default true)") + }); + + internal async Task Run(Uri url, AbsolutePath output, bool proxy = true) { var parsed = _dispatcher.Parse(url); if (parsed == null) diff --git a/Wabbajack.CLI/Verbs/DumpZipInfo.cs b/Wabbajack.CLI/Verbs/DumpZipInfo.cs index b27c6cc5..ef42b4c7 100644 --- a/Wabbajack.CLI/Verbs/DumpZipInfo.cs +++ b/Wabbajack.CLI/Verbs/DumpZipInfo.cs @@ -25,17 +25,15 @@ public class DumpZipInfo : IVerb _logger = logger; } - public Command MakeCommand() - { - var command = new Command("dump-zip-info"); - command.Add(new Option(new[] {"-i", "-input"}, "Zip file ot parse")); - command.Add(new Option(new[] {"-t", "-test"}, "Test extracting each file")); - command.Description = "Dumps the contents of a zip file to the console, for use in debugging wabbajack files"; - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = new("dump-zip-info", + "Dumps the contents of a zip file to the console, for use in debugging wabbajack files", + new[] + { + new OptionDefinition(typeof(AbsolutePath), "i", "input", "Zip file to parse"), + new OptionDefinition(typeof(bool), "t", "test", "Test extracting each file") + }); - private async Task Run(AbsolutePath input, bool test) + internal async Task Run(AbsolutePath input, bool test) { await using var ar = new ZipReader(input.Open(FileMode.Open), false); foreach (var value in (await ar.GetFiles())) diff --git a/Wabbajack.CLI/Verbs/Encrypt.cs b/Wabbajack.CLI/Verbs/Encrypt.cs index ed4fd300..eb31cb92 100644 --- a/Wabbajack.CLI/Verbs/Encrypt.cs +++ b/Wabbajack.CLI/Verbs/Encrypt.cs @@ -18,17 +18,15 @@ public class Encrypt : IVerb _logger = logger; } - public Command MakeCommand() - { - var command = new Command("encrypt"); - command.Add(new Option(new[] {"-i", "-input"}, "Path to the file to enrypt")); - command.Add(new Option(new[] {"-n", "-name"}, "Name of the key to store the data into")); - command.Description = "Encrypts a file and stores it in the Wabbajack encrypted storage"; - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = new("encrypt", + "Encrypts a file and stores it in the Wabbajack encrypted storage", + new[] + { + new OptionDefinition(typeof(AbsolutePath), "i", "input", "Path to the file to encrypt"), + new OptionDefinition(typeof(string), "n", "name", "Name of the key to store the data into") + }); - public async Task Run(AbsolutePath input, string name) + internal async Task Run(AbsolutePath input, string name) { var data = await input.ReadAllBytesAsync(); _logger.LogInformation("Encrypting {bytes} bytes into `{key}`", data.Length, name); diff --git a/Wabbajack.CLI/Verbs/Extract.cs b/Wabbajack.CLI/Verbs/Extract.cs index be9f449f..524bd2ab 100644 --- a/Wabbajack.CLI/Verbs/Extract.cs +++ b/Wabbajack.CLI/Verbs/Extract.cs @@ -25,17 +25,14 @@ public class Extract : IVerb _extractor = extractor; } - public Command MakeCommand() - { - var command = new Command("extract"); - command.Add(new Option(new[] {"-i", "-input"}, "Input Archive")); - command.Add(new Option(new[] {"-o", "-output"}, "Output folder")); - command.Description = "Extracts the contents of an archive into a folder"; - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = new("extract", + "Extracts the contents of an archive into a folder", new[] + { + new OptionDefinition(typeof(AbsolutePath), "i", "input", "Input Archive"), + new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output Folder") + }); - private async Task Run(AbsolutePath input, AbsolutePath output, CancellationToken token) + internal async Task Run(AbsolutePath input, AbsolutePath output, CancellationToken token) { if (!output.DirectoryExists()) output.Parent.CreateDirectory(); diff --git a/Wabbajack.CLI/Verbs/ForceHeal.cs b/Wabbajack.CLI/Verbs/ForceHeal.cs index 2d2975e4..d43a218c 100644 --- a/Wabbajack.CLI/Verbs/ForceHeal.cs +++ b/Wabbajack.CLI/Verbs/ForceHeal.cs @@ -43,15 +43,13 @@ public class ForceHeal : IVerb _httpClient = httpClient; } - public Command MakeCommand() - { - var command = new Command("force-heal"); - command.Add(new Option(new[] {"-n", "-new-file"}, "New File")); - command.Add(new Option(new[] {"-o", "-old-file"}, "Old File")); - command.Description = "Creates a patch from New file to Old File and uploads it"; - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = new("force-heal", + "Creates a patch from New file to Old file and uploads it", + new[] + { + new OptionDefinition(typeof(AbsolutePath), "n", "new-file", "New file"), + new OptionDefinition(typeof(AbsolutePath), "o", "old-file", "Old File") + }); public async Task Run(AbsolutePath oldFile, AbsolutePath newFile) { diff --git a/Wabbajack.CLI/Verbs/GenerateMetricsReports.cs b/Wabbajack.CLI/Verbs/GenerateMetricsReports.cs deleted file mode 100644 index 351efdbb..00000000 --- a/Wabbajack.CLI/Verbs/GenerateMetricsReports.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.CommandLine; -using System.CommandLine.Invocation; -using System.CommandLine.NamingConventionBinder; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Wabbajack.Common; -using Wabbajack.DTOs.JsonConverters; -using Wabbajack.DTOs.ServerResponses; -using Wabbajack.Paths; - -namespace Wabbajack.CLI.Verbs; - -public class GenerateMetricsReports : IVerb -{ - private readonly HttpClient _client; - private readonly DTOSerializer _dtos; - - public GenerateMetricsReports(HttpClient client, DTOSerializer dtos) - { - _client = client; - _dtos = dtos; - } - public Command MakeCommand() - { - var command = new Command("generate-metrics-report"); - command.Add(new Option(new[] {"-o", "-output"}, "Output folder")); - command.Description = "Generates usage metrics and outputs a html report about them"; - command.Handler = CommandHandler.Create(Run); - return command; - - - } - - private async Task Run(AbsolutePath output) - { - var subjects = await GetMetrics("one day ago", "now", "finish_install") - .Where(d => d.Action != d.Subject) - .Select(async d => d.GroupingSubject) - .ToHashSet(); - - var allTime = await GetMetrics("10 days ago", "now", "finish_install") - .Where(d => subjects.Contains(d.GroupingSubject)) - .ToList(); - - var grouped = allTime.GroupBy(g => (g.Timestamp.ToString("yyyy-MM-dd"), g.GroupingSubject)).ToArray(); - - - - return 0; - } - - private async IAsyncEnumerable GetMetrics(string start, string end, string action) - { - await using var response = await _client.GetStreamAsync(new Uri($"https://build.wabbajack.org/metrics/report?action={action}&from={start}&end={end}")); - - var sr = new StreamReader(response, leaveOpen: false); - - while (true) - { - var line = await sr.ReadLineAsync(); - if (line == null) break; - yield return _dtos.Deserialize(line)!; - } - } -} \ No newline at end of file diff --git a/Wabbajack.CLI/Verbs/HashFile.cs b/Wabbajack.CLI/Verbs/HashFile.cs index 32cbb242..2ef96eb5 100644 --- a/Wabbajack.CLI/Verbs/HashFile.cs +++ b/Wabbajack.CLI/Verbs/HashFile.cs @@ -20,15 +20,11 @@ public class HashFile : IVerb _logger = logger; } - public Command MakeCommand() - { - var command = new Command("hash-file"); - command.Add(new Option(new[] {"-i", "-input"}, "Path to the file to hash")); - command.Description = "Hashes a file with Wabbajack's xxHash64 implementation"; - command.Handler = CommandHandler.Create(Run); - return command; - } - + public static VerbDefinition Definition = new VerbDefinition("hash-file", + "Hashes a file with Wabbajack's hashing routines", new[] + { + new OptionDefinition(typeof(AbsolutePath), "i", "input", "Path to the file to hash") + }); public async Task Run(AbsolutePath input) { diff --git a/Wabbajack.CLI/Verbs/HashUrlString.cs b/Wabbajack.CLI/Verbs/HashUrlString.cs index 86104301..ed47e919 100644 --- a/Wabbajack.CLI/Verbs/HashUrlString.cs +++ b/Wabbajack.CLI/Verbs/HashUrlString.cs @@ -17,15 +17,11 @@ public class HashUrlString : IVerb _logger = logger; } - public Command MakeCommand() - { - var command = new Command("hash-url-string"); - command.Add(new Option(new[] {"-u", "-url"}, "Url string to hash")); - command.Description = "Hashes a URL string and returns the hashcode as hex"; - command.Handler = CommandHandler.Create(Run); - return command; - } - + public static VerbDefinition Definition = new VerbDefinition("hash-url-string", + "Hashes a URL string and returns the hashcode as hex", new[] + { + new OptionDefinition(typeof(AbsolutePath), "u", "url", "Url string to hash") + }); public async Task Run(string u) { diff --git a/Wabbajack.CLI/Verbs/IVerb.cs b/Wabbajack.CLI/Verbs/IVerb.cs index 433c350c..f0f85f57 100644 --- a/Wabbajack.CLI/Verbs/IVerb.cs +++ b/Wabbajack.CLI/Verbs/IVerb.cs @@ -4,5 +4,4 @@ namespace Wabbajack.CLI.Verbs; public interface IVerb { - public Command MakeCommand(); } \ No newline at end of file diff --git a/Wabbajack.CLI/Verbs/Install.cs b/Wabbajack.CLI/Verbs/Install.cs index b32f717b..1d208846 100644 --- a/Wabbajack.CLI/Verbs/Install.cs +++ b/Wabbajack.CLI/Verbs/Install.cs @@ -39,18 +39,16 @@ public class Install : IVerb _cache = cache; _gameLocator = gameLocator; } - public Command MakeCommand() + + public static VerbDefinition Definition = new VerbDefinition("install", "Installs a wabbajack file", new[] { - var command = new Command("install"); - command.Add(new Option(new[] {"-w", "-wabbajack"}, "Wabbajack file")); - command.Add(new Option(new[] {"-m", "-machineUrl"}, "Machine url to download")); - command.Add(new Option(new[] {"-o", "-output"}, "Output path")); - command.Add(new Option(new[] {"-d", "-downloads"}, "Downloads path")); - command.Description = "Installs a wabbajack file"; - command.Handler = CommandHandler.Create(Run); - return command; - } - public async Task Run(AbsolutePath wabbajack, AbsolutePath output, AbsolutePath downloads, string machineUrl, CancellationToken token) + new OptionDefinition(typeof(AbsolutePath), "w", "wabbajack", "Wabbajack file"), + new OptionDefinition(typeof(string), "m", "machineUrl", "Machine url to download"), + new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output path"), + new OptionDefinition(typeof(AbsolutePath), "d", "downloads", "Downloads path") + }); + + internal async Task Run(AbsolutePath wabbajack, AbsolutePath output, AbsolutePath downloads, string machineUrl, CancellationToken token) { if (!string.IsNullOrEmpty(machineUrl)) { diff --git a/Wabbajack.CLI/Verbs/InstallCompileInstallVerify.cs b/Wabbajack.CLI/Verbs/InstallCompileInstallVerify.cs index b0c81d2e..86ef8670 100644 --- a/Wabbajack.CLI/Verbs/InstallCompileInstallVerify.cs +++ b/Wabbajack.CLI/Verbs/InstallCompileInstallVerify.cs @@ -46,18 +46,15 @@ public class InstallCompileInstallVerify : IVerb _gameLocator = gameLocator; _inferencer = inferencer; } - - public Command MakeCommand() - { - var command = new Command("install-compile-install-verify"); - command.Add(new Option(new[] {"-m", "-machineUrls"}, "Machine url(s) to download")); - command.Add(new Option(new[] {"-d", "-downloads"}, "Downloads path")); - command.Add(new Option(new[] {"-o", "-outputs"}, "Outputs path")); - command.Description = "Installs a modlist, compiles it, installs it again, verifies it"; - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = new VerbDefinition("install-compile-install-verify", + "Installs a modlist, compiles it, installs it again, verifies it", new[] + { + new OptionDefinition(typeof(AbsolutePath), "m", "machineUrl", "Machine url(s) to download"), + new OptionDefinition(typeof(AbsolutePath), "d", "downloads", "Downloads path"), + new OptionDefinition(typeof(AbsolutePath), "o", "outputs", "Output paths") + }); + public async Task Run(AbsolutePath outputs, AbsolutePath downloads, IEnumerable machineUrls, CancellationToken token) { foreach (var machineUrl in machineUrls) diff --git a/Wabbajack.CLI/Verbs/ListCreationClubContent.cs b/Wabbajack.CLI/Verbs/ListCreationClubContent.cs index 7c5ad7ca..eb3a528e 100644 --- a/Wabbajack.CLI/Verbs/ListCreationClubContent.cs +++ b/Wabbajack.CLI/Verbs/ListCreationClubContent.cs @@ -29,14 +29,10 @@ public class ListCreationClubContent : IVerb _client = wjClient; _downloader = downloader; } - public Command MakeCommand() - { - var command = new Command("list-creation-club-content"); - command.Description = "Lists all known creation club content"; - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = + new("list-creation-club-content", "Lists all known creation club content", Array.Empty()); + public async Task Run(CancellationToken token) { _logger.LogInformation("Getting list of content"); diff --git a/Wabbajack.CLI/Verbs/ListGames.cs b/Wabbajack.CLI/Verbs/ListGames.cs index 637976fa..757bda77 100644 --- a/Wabbajack.CLI/Verbs/ListGames.cs +++ b/Wabbajack.CLI/Verbs/ListGames.cs @@ -1,3 +1,4 @@ +using System; using System.CommandLine; using System.CommandLine.Invocation; using System.CommandLine.NamingConventionBinder; @@ -22,15 +23,11 @@ public class ListGames : IVerb _logger = logger; _locator = locator; } - public Command MakeCommand() - { - var command = new Command("list-games"); - command.Description = "Lists all games Wabbajack recognizes, and their installed versions/locations (if any)"; - command.Handler = CommandHandler.Create(Run); - return command; - } + + public static VerbDefinition Definition = new VerbDefinition("list-games", + "Lists all games Wabbajack recognizes, and their installed versions/locations (if any)", Array.Empty()); - public async Task Run(CancellationToken token) + internal async Task Run(CancellationToken token) { foreach (var game in GameRegistry.Games.OrderBy(g => g.Value.HumanFriendlyGameName)) { diff --git a/Wabbajack.CLI/Verbs/ListModlists.cs b/Wabbajack.CLI/Verbs/ListModlists.cs index c943e319..46c27e4a 100644 --- a/Wabbajack.CLI/Verbs/ListModlists.cs +++ b/Wabbajack.CLI/Verbs/ListModlists.cs @@ -24,13 +24,9 @@ public class ListModlists : IVerb _logger = logger; _client = wjClient; } - public Command MakeCommand() - { - var command = new Command("list-modlists"); - command.Description = "Lists all known modlists"; - command.Handler = CommandHandler.Create(Run); - return command; - } + + public static VerbDefinition Definition = + new("list-modlists", "Lists all known modlists", Array.Empty()); public async Task Run(CancellationToken token) { diff --git a/Wabbajack.CLI/Verbs/MirrorFile.cs b/Wabbajack.CLI/Verbs/MirrorFile.cs index b4199c48..16f1c175 100644 --- a/Wabbajack.CLI/Verbs/MirrorFile.cs +++ b/Wabbajack.CLI/Verbs/MirrorFile.cs @@ -18,15 +18,12 @@ public class MirrorFile : IVerb _logger = logger; _client = wjClient; } - public Command MakeCommand() - { - var command = new Command("mirror-file"); - command.Add(new Option(new[] {"-i", "-input"}, "File to Mirror")); - command.Description = "Mirrors a file to the Wabbajack CDN"; - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = new("mirror-file", "Mirrors a file to the Wabbajack CDN", + new[] + { + new OptionDefinition(typeof(AbsolutePath), "i", "input", "File to Mirror") + }); public async Task Run(AbsolutePath input) { _logger.LogInformation("Generating File Definition for {Name}", input.FileName); diff --git a/Wabbajack.CLI/Verbs/ModlistReport.cs b/Wabbajack.CLI/Verbs/ModlistReport.cs index 6af8b504..7f1ab79a 100644 --- a/Wabbajack.CLI/Verbs/ModlistReport.cs +++ b/Wabbajack.CLI/Verbs/ModlistReport.cs @@ -28,14 +28,12 @@ public class ModlistReport : IVerb _logger = logger; _dtos = dtos; } - public Command MakeCommand() - { - var command = new Command("modlist-report"); - command.Add(new Option(new[] {"-i", "-input"}, "Wabbajack file from which to generate a report")); - command.Description = "Generates a usage report for a Modlist file"; - command.Handler = CommandHandler.Create(Run); - return command; - } + + public static VerbDefinition Definition = new("modlist-report", + "Generates a usage report for a Modlist file", new[] + { + new OptionDefinition(typeof(AbsolutePath), "i", "input", "Wabbajack file from which to generate a report") + }); private static async Task ReportTemplate(object o) { diff --git a/Wabbajack.CLI/Verbs/SteamDownloadFile.cs b/Wabbajack.CLI/Verbs/SteamDownloadFile.cs index 58c73402..8fd48583 100644 --- a/Wabbajack.CLI/Verbs/SteamDownloadFile.cs +++ b/Wabbajack.CLI/Verbs/SteamDownloadFile.cs @@ -34,21 +34,18 @@ public class SteamDownloadFile : IVerb _dtos = dtos; _wjClient = wjClient; } - public Command MakeCommand() - { - var command = new Command("steam-download-file"); - command.Description = "Dumps information to the console about the given app"; - - command.Add(new Option(new[] {"-g", "-game", "-gameName"}, "Wabbajack game name")); - command.Add(new Option(new[] {"-v", "-version"}, "Version of the game to download for")); - command.Add(new Option(new[] {"-f", "-file"}, "File to download (relative path)")); - command.Add(new Option(new[] {"-o", "-output"}, "Output location")); - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = new VerbDefinition("steam-download-file", + "Dumps information to the console about the given app", + new[] + { + new OptionDefinition(typeof(string), "g", "game", "Wabbajack game name"), + new OptionDefinition(typeof(string), "v", "version", "Version of the game to download for"), + new OptionDefinition(typeof(string), "f", "file", "File to download (relative path)"), + new OptionDefinition(typeof(string), "o", "output", "Output location") + }); - private async Task Run(string gameName, string version, string file, AbsolutePath output) + internal async Task Run(string gameName, string version, string file, AbsolutePath output) { if (!GameRegistry.TryGetByFuzzyName(gameName, out var game)) _logger.LogError("Can't find definition for {Game}", gameName); diff --git a/Wabbajack.CLI/Verbs/SteamDumpAppInfo.cs b/Wabbajack.CLI/Verbs/SteamDumpAppInfo.cs index 9e25aa40..181c7bc1 100644 --- a/Wabbajack.CLI/Verbs/SteamDumpAppInfo.cs +++ b/Wabbajack.CLI/Verbs/SteamDumpAppInfo.cs @@ -15,15 +15,15 @@ using JsonSerializer = System.Text.Json.JsonSerializer; namespace Wabbajack.CLI.Verbs; -public class SteamAppDumpInfo : IVerb +public class SteamDumpAppInfo : IVerb { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly Client _client; private readonly ITokenProvider _token; private readonly DepotDownloader _downloader; private readonly DTOSerializer _dtos; - public SteamAppDumpInfo(ILogger logger, Client steamClient, ITokenProvider token, + public SteamDumpAppInfo(ILogger logger, Client steamClient, ITokenProvider token, DepotDownloader downloader, DTOSerializer dtos) { _logger = logger; @@ -32,6 +32,13 @@ public class SteamAppDumpInfo : IVerb _downloader = downloader; _dtos = dtos; } + + public static VerbDefinition Definition = new VerbDefinition("steam-app-dump-info", + "Dumps information to the console about the given app", new[] + { + new OptionDefinition(typeof(string), "g", "game", "Wabbajack game name") + }); + public Command MakeCommand() { var command = new Command("steam-app-dump-info"); diff --git a/Wabbajack.CLI/Verbs/SteamLogin.cs b/Wabbajack.CLI/Verbs/SteamLogin.cs index 9c46bcba..1bc27af1 100644 --- a/Wabbajack.CLI/Verbs/SteamLogin.cs +++ b/Wabbajack.CLI/Verbs/SteamLogin.cs @@ -22,15 +22,12 @@ public class SteamLogin : IVerb _client = steamClient; _token = token; } - public Command MakeCommand() - { - var command = new Command("steam-login"); - command.Description = "Logs into Steam via interactive prompts"; - - command.Add(new Option(new[] {"-u", "-user"}, "Username for login")); - command.Handler = CommandHandler.Create(Run); - return command; - } + + public static VerbDefinition Definition = new("steam-login", + "Logs into Steam via interactive prompts", new[] + { + new OptionDefinition(typeof(string), "u", "user", "Username for login") + }); public async Task Run(string user) { diff --git a/Wabbajack.CLI/Verbs/UploadToNexus.cs b/Wabbajack.CLI/Verbs/UploadToNexus.cs index 19f45bac..90136a93 100644 --- a/Wabbajack.CLI/Verbs/UploadToNexus.cs +++ b/Wabbajack.CLI/Verbs/UploadToNexus.cs @@ -26,14 +26,13 @@ public class UploadToNexus : IVerb _client = wjClient; _dtos = dtos; } - public Command MakeCommand() - { - var command = new Command("upload-to-nexus"); - command.Add(new Option(new[] {"-d", "-definition"}, "Definition JSON file")); - command.Description = "Uploads a file to the Nexus defined by the given .json definition file"; - command.Handler = CommandHandler.Create(Run); - return command; - } + + public static VerbDefinition Definition = new("upload-to-nexus", + "Uploads a file to the Nexus defined by the given .json definition file", new[] + { + new OptionDefinition(typeof(AbsolutePath), "d", "definition", "Definition JSON file") + }); + public async Task Run(AbsolutePath definition) { diff --git a/Wabbajack.CLI/Verbs/VFSIndex.cs b/Wabbajack.CLI/Verbs/VFSIndex.cs index 83aee6ff..baba6320 100644 --- a/Wabbajack.CLI/Verbs/VFSIndex.cs +++ b/Wabbajack.CLI/Verbs/VFSIndex.cs @@ -8,24 +8,20 @@ using Wabbajack.VFS; namespace Wabbajack.CLI.Verbs; -public class VFSIndexFolder : IVerb +public class VFSIndex : IVerb { private readonly Context _context; - public VFSIndexFolder(Context context) + public VFSIndex(Context context) { _context = context; } - public Command MakeCommand() - { - var command = new Command("vfs-index"); - command.Add(new Option(new[] {"-f", "--folder"}, "Folder to index")); - command.Description = "Index and cache the contents of a folder"; - - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = new VerbDefinition("vfs-index", + "Index and cache the contents of a folder", new[] + { + new OptionDefinition(typeof(AbsolutePath), "f", "folder", "Folder to index") + }); public async Task Run(AbsolutePath folder) { diff --git a/Wabbajack.CLI/Verbs/ValidateLists.cs b/Wabbajack.CLI/Verbs/ValidateLists.cs index 234d7efb..c30360c6 100644 --- a/Wabbajack.CLI/Verbs/ValidateLists.cs +++ b/Wabbajack.CLI/Verbs/ValidateLists.cs @@ -74,19 +74,11 @@ public class ValidateLists : IVerb _httpLimiter = httpLimiter; } - public Command MakeCommand() - { - var command = new Command("validate-lists"); - command.Add(new Option(new[] {"-r", "--reports"}, "Location to store validation report outputs")); - - command.Add(new Option(new[] {"--other-archives"}, - "Look for files here before downloading (stored by hex hash name)") - {IsRequired = false}); - - command.Description = "Gets a list of modlists, validates them and exports a result list"; - command.Handler = CommandHandler.Create(Run); - return command; - } + public static VerbDefinition Definition = new VerbDefinition("validate-lists", + "Gets a list of modlists, validates them and exports a result list", new[] + { + new OptionDefinition(typeof(AbsolutePath), "r", "reports", "Location to store validation report outputs") + }); public async Task Run(AbsolutePath reports, AbsolutePath otherArchives) { diff --git a/Wabbajack.CLI/Wabbajack.CLI.csproj b/Wabbajack.CLI/Wabbajack.CLI.csproj index 82df7317..f52b795f 100644 --- a/Wabbajack.CLI/Wabbajack.CLI.csproj +++ b/Wabbajack.CLI/Wabbajack.CLI.csproj @@ -40,6 +40,18 @@ + + TextTemplatingFileGenerator + VerbRegistration.cs + + + + + + True + True + VerbRegistration.tt +