Merge pull request #2093 from wabbajack-tools/cli-rework

Cli rework
This commit is contained in:
Timothy Baldridge 2022-09-30 19:59:09 -06:00 committed by GitHub
commit b7e848b7a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 343 additions and 433 deletions

View File

@ -1,28 +1,139 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.CommandLine; using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.NamingConventionBinder;
using System.ComponentModel.Design;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Wabbajack.CLI.Verbs; using Wabbajack.CLI.Verbs;
using Wabbajack.Paths;
using Wabbajack.Services.OSIntegrated; using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.CLI; namespace Wabbajack.CLI;
public class CommandLineBuilder public partial class CommandLineBuilder
{ {
private readonly IConsole _console; private readonly IConsole _console;
private readonly IEnumerable<IVerb> _verbs; private readonly IEnumerable<IVerb> _verbs;
private static IServiceProvider _provider;
public CommandLineBuilder(IEnumerable<IVerb> verbs, IConsole console, LoggingRateLimiterReporter _) public CommandLineBuilder(IServiceProvider provider, IConsole console, LoggingRateLimiterReporter _)
{ {
_console = console; _provider = provider;
_verbs = verbs; }
static CommandLineBuilder()
{
RegisterAll();
} }
public async Task<int> Run(string[] args) public async Task<int> Run(string[] args)
{ {
var root = new RootCommand(); var root = new RootCommand();
foreach (var verb in _verbs) foreach (var verb in _commands)
root.Add(verb.MakeCommand()); {
root.Add(MakeCommend(verb.Type, verb.Handler, verb.Definition));
}
return await root.InvokeAsync(args); return await root.InvokeAsync(args);
} }
private static Dictionary<Type, Func<OptionDefinition, Option>> _optionCtors = new()
{
{
typeof(string),
d => new Option<string>(d.Aliases, description: d.Description)
},
{
typeof(AbsolutePath),
d => new Option<AbsolutePath>(d.Aliases, description: d.Description, parseArgument: d => d.Tokens.Single().Value.ToAbsolutePath())
},
{
typeof(Uri),
d => new Option<Uri>(d.Aliases, description: d.Description)
},
{
typeof(bool),
d => new Option<bool>(d.Aliases, description: d.Description)
},
};
private Command MakeCommend(Type verbType, Func<IVerb, Delegate> 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<IVerb, Delegate> _delgate;
public HandlerDelegate(IServiceProvider provider, Type type, Func<IVerb, Delegate> 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<int> 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<IVerb, Delegate> Handler)> _commands { get; set; } = new();
public static IEnumerable<Type> Verbs => _commands.Select(c => c.Type);
public static void RegisterCommand<T>(VerbDefinition definition, Func<IVerb, Delegate> 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<CommandLineBuilder>();
foreach (var verb in CommandLineBuilder.Verbs)
{
services.AddSingleton(verb);
}
return services;
}
} }

View File

@ -57,32 +57,7 @@ internal class Program
services.AddTransient<Context>(); services.AddTransient<Context>();
services.AddSingleton<IVerb, HashFile>(); services.AddCommands();
services.AddSingleton<IVerb, VFSIndexFolder>();
services.AddSingleton<IVerb, Encrypt>();
services.AddSingleton<IVerb, Decrypt>();
services.AddSingleton<IVerb, ValidateLists>();
services.AddSingleton<IVerb, DownloadCef>();
services.AddSingleton<IVerb, DownloadUrl>();
services.AddSingleton<IVerb, GenerateMetricsReports>();
services.AddSingleton<IVerb, ForceHeal>();
services.AddSingleton<IVerb, MirrorFile>();
services.AddSingleton<IVerb, SteamLogin>();
services.AddSingleton<IVerb, SteamAppDumpInfo>();
services.AddSingleton<IVerb, SteamDownloadFile>();
services.AddSingleton<IVerb, UploadToNexus>();
services.AddSingleton<IVerb, ListCreationClubContent>();
services.AddSingleton<IVerb, ListModlists>();
services.AddSingleton<IVerb, Extract>();
services.AddSingleton<IVerb, DumpZipInfo>();
services.AddSingleton<IVerb, Install>();
services.AddSingleton<IVerb, Compile>();
services.AddSingleton<IVerb, InstallCompileInstallVerify>();
services.AddSingleton<IVerb, HashUrlString>();
services.AddSingleton<IVerb, DownloadAll>();
services.AddSingleton<IVerb, ModlistReport>();
services.AddSingleton<IVerb, ListGames>();
services.AddSingleton<IUserInterventionHandler, UserInterventionHandler>(); services.AddSingleton<IUserInterventionHandler, UserInterventionHandler>();
}).Build(); }).Build();

View File

@ -0,0 +1,32 @@

namespace Wabbajack.CLI;
using Wabbajack.CLI.Verbs;
public partial class CommandLineBuilder {
private static void RegisterAll() {
RegisterCommand<Compile>(Compile.Definition, c => ((Compile)c).Run);
RegisterCommand<Decrypt>(Decrypt.Definition, c => ((Decrypt)c).Run);
RegisterCommand<DownloadAll>(DownloadAll.Definition, c => ((DownloadAll)c).Run);
RegisterCommand<DownloadUrl>(DownloadUrl.Definition, c => ((DownloadUrl)c).Run);
RegisterCommand<DumpZipInfo>(DumpZipInfo.Definition, c => ((DumpZipInfo)c).Run);
RegisterCommand<Encrypt>(Encrypt.Definition, c => ((Encrypt)c).Run);
RegisterCommand<Extract>(Extract.Definition, c => ((Extract)c).Run);
RegisterCommand<ForceHeal>(ForceHeal.Definition, c => ((ForceHeal)c).Run);
RegisterCommand<HashFile>(HashFile.Definition, c => ((HashFile)c).Run);
RegisterCommand<HashUrlString>(HashUrlString.Definition, c => ((HashUrlString)c).Run);
RegisterCommand<Install>(Install.Definition, c => ((Install)c).Run);
RegisterCommand<InstallCompileInstallVerify>(InstallCompileInstallVerify.Definition, c => ((InstallCompileInstallVerify)c).Run);
RegisterCommand<ListCreationClubContent>(ListCreationClubContent.Definition, c => ((ListCreationClubContent)c).Run);
RegisterCommand<ListGames>(ListGames.Definition, c => ((ListGames)c).Run);
RegisterCommand<ListModlists>(ListModlists.Definition, c => ((ListModlists)c).Run);
RegisterCommand<MirrorFile>(MirrorFile.Definition, c => ((MirrorFile)c).Run);
RegisterCommand<ModlistReport>(ModlistReport.Definition, c => ((ModlistReport)c).Run);
RegisterCommand<SteamDownloadFile>(SteamDownloadFile.Definition, c => ((SteamDownloadFile)c).Run);
RegisterCommand<SteamDumpAppInfo>(SteamDumpAppInfo.Definition, c => ((SteamDumpAppInfo)c).Run);
RegisterCommand<SteamLogin>(SteamLogin.Definition, c => ((SteamLogin)c).Run);
RegisterCommand<UploadToNexus>(UploadToNexus.Definition, c => ((UploadToNexus)c).Run);
RegisterCommand<ValidateLists>(ValidateLists.Definition, c => ((ValidateLists)c).Run);
RegisterCommand<VFSIndex>(VFSIndex.Definition, c => ((VFSIndex)c).Run);
}
}

View File

@ -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);
<#
}
#>
}
}

View File

@ -38,16 +38,12 @@ public class Compile : IVerb
_inferencer = inferencer; _inferencer = inferencer;
} }
public Command MakeCommand() public static VerbDefinition Definition = new("compile", "Compiles a modlist",
new[]
{ {
var command = new Command("compile"); new OptionDefinition(typeof(AbsolutePath), "i", "installPath", "Install Path"),
command.Add(new Option<AbsolutePath>(new[] {"-i", "-installPath"}, "Install Path")); new OptionDefinition(typeof(AbsolutePath), "o", "outputPath", "OutputPath")
command.Add(new Option<AbsolutePath>(new[] {"-o", "-output"}, "Output")); });
command.Description = "Installs a modlist, compiles it, installs it again, verifies it";
command.Handler = CommandHandler.Create(Run);
return command;
}
public async Task<int> Run(AbsolutePath installPath, AbsolutePath outputPath, public async Task<int> Run(AbsolutePath installPath, AbsolutePath outputPath,
CancellationToken token) CancellationToken token)
{ {

View File

@ -19,16 +19,14 @@ public class Decrypt : IVerb
_logger = logger; _logger = logger;
} }
public Command MakeCommand() public static VerbDefinition Definition = new VerbDefinition("decrypt",
{ "Decrypts a file from the wabbajack encrypted storage",
var command = new Command("decrypt"); new[]
command.Add(new Option<AbsolutePath>(new[] {"-o", "-output"}, "Output file path")); {
command.Add(new Option<string>(new[] {"-n", "-name"}, "Name of the key to load data from")); new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output file path"),
command.Description = "Decrypts a file from the Wabbajack encrypted storage"; new OptionDefinition(typeof(string), "n", "name", "Name of the key to load data from")
command.Handler = CommandHandler.Create(Run); });
return command;
}
public async Task<int> Run(AbsolutePath output, string name) public async Task<int> Run(AbsolutePath output, string name)
{ {
var data = await name.ToRelativePath() var data = await name.ToRelativePath()

View File

@ -41,16 +41,14 @@ public class DownloadAll : IVerb
_cache = cache; _cache = cache;
} }
public Command MakeCommand() public static VerbDefinition Definition = new VerbDefinition("download-all",
{ "Downloads all files for all modlists in the gallery",
var command = new Command("download-all"); new[]
command.Add(new Option<AbsolutePath>(new[] {"-o", "-output"}, "Output folder")); {
command.Description = "Downloads all files for all modlists in the gallery"; new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output folder")
command.Handler = CommandHandler.Create(Run); });
return command;
} internal async Task<int> Run(AbsolutePath output, CancellationToken token)
private async Task<int> Run(AbsolutePath output, CancellationToken token)
{ {
_logger.LogInformation("Downloading modlists"); _logger.LogInformation("Downloading modlists");

View File

@ -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<DownloadCef> _logger;
public DownloadCef(ILogger<DownloadCef> 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<AbsolutePath>(new[] {"-f", "-folder"}, "Path to Wabbajack"));
command.Add(new Option<bool>(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<int> 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;
}
}

View File

@ -24,18 +24,15 @@ public class DownloadUrl : IVerb
_dispatcher = dispatcher; _dispatcher = dispatcher;
} }
public Command MakeCommand() public static VerbDefinition Definition = new VerbDefinition("download-url", "Downloads a file to a given output",
{ new[]
var command = new Command("download-url"); {
command.Add(new Option<Uri>(new[] {"-u", "-url"}, "Url to parse")); new OptionDefinition(typeof(Uri), "u", "url", "Url to parse"),
command.Add(new Option<AbsolutePath>(new[] {"-o", "-output"}, "Output file")); new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output File"),
command.Add(new Option<bool>(new [] {"-p", "--proxy"}, "Use the Wabbajack Proxy (default: true)")); new OptionDefinition(typeof(bool), "p", "proxy", "Use the Wabbajack Proxy (default true)")
command.Description = "Downloads a file to a given output"; });
command.Handler = CommandHandler.Create(Run);
return command; internal async Task<int> Run(Uri url, AbsolutePath output, bool proxy = true)
}
private async Task<int> Run(Uri url, AbsolutePath output, bool proxy = true)
{ {
var parsed = _dispatcher.Parse(url); var parsed = _dispatcher.Parse(url);
if (parsed == null) if (parsed == null)

View File

@ -25,17 +25,15 @@ public class DumpZipInfo : IVerb
_logger = logger; _logger = logger;
} }
public Command MakeCommand() public static VerbDefinition Definition = new("dump-zip-info",
{ "Dumps the contents of a zip file to the console, for use in debugging wabbajack files",
var command = new Command("dump-zip-info"); new[]
command.Add(new Option<AbsolutePath>(new[] {"-i", "-input"}, "Zip file ot parse")); {
command.Add(new Option<bool>(new[] {"-t", "-test"}, "Test extracting each file")); new OptionDefinition(typeof(AbsolutePath), "i", "input", "Zip file to parse"),
command.Description = "Dumps the contents of a zip file to the console, for use in debugging wabbajack files"; new OptionDefinition(typeof(bool), "t", "test", "Test extracting each file")
command.Handler = CommandHandler.Create(Run); });
return command;
}
private async Task<int> Run(AbsolutePath input, bool test) internal async Task<int> Run(AbsolutePath input, bool test)
{ {
await using var ar = new ZipReader(input.Open(FileMode.Open), false); await using var ar = new ZipReader(input.Open(FileMode.Open), false);
foreach (var value in (await ar.GetFiles())) foreach (var value in (await ar.GetFiles()))

View File

@ -18,17 +18,15 @@ public class Encrypt : IVerb
_logger = logger; _logger = logger;
} }
public Command MakeCommand() public static VerbDefinition Definition = new("encrypt",
{ "Encrypts a file and stores it in the Wabbajack encrypted storage",
var command = new Command("encrypt"); new[]
command.Add(new Option<AbsolutePath>(new[] {"-i", "-input"}, "Path to the file to enrypt")); {
command.Add(new Option<string>(new[] {"-n", "-name"}, "Name of the key to store the data into")); new OptionDefinition(typeof(AbsolutePath), "i", "input", "Path to the file to encrypt"),
command.Description = "Encrypts a file and stores it in the Wabbajack encrypted storage"; new OptionDefinition(typeof(string), "n", "name", "Name of the key to store the data into")
command.Handler = CommandHandler.Create(Run); });
return command;
}
public async Task<int> Run(AbsolutePath input, string name) internal async Task<int> Run(AbsolutePath input, string name)
{ {
var data = await input.ReadAllBytesAsync(); var data = await input.ReadAllBytesAsync();
_logger.LogInformation("Encrypting {bytes} bytes into `{key}`", data.Length, name); _logger.LogInformation("Encrypting {bytes} bytes into `{key}`", data.Length, name);

View File

@ -25,17 +25,14 @@ public class Extract : IVerb
_extractor = extractor; _extractor = extractor;
} }
public Command MakeCommand() public static VerbDefinition Definition = new("extract",
{ "Extracts the contents of an archive into a folder", new[]
var command = new Command("extract"); {
command.Add(new Option<AbsolutePath>(new[] {"-i", "-input"}, "Input Archive")); new OptionDefinition(typeof(AbsolutePath), "i", "input", "Input Archive"),
command.Add(new Option<AbsolutePath>(new[] {"-o", "-output"}, "Output folder")); new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output Folder")
command.Description = "Extracts the contents of an archive into a folder"; });
command.Handler = CommandHandler.Create(Run);
return command;
}
private async Task<int> Run(AbsolutePath input, AbsolutePath output, CancellationToken token) internal async Task<int> Run(AbsolutePath input, AbsolutePath output, CancellationToken token)
{ {
if (!output.DirectoryExists()) if (!output.DirectoryExists())
output.Parent.CreateDirectory(); output.Parent.CreateDirectory();

View File

@ -43,15 +43,13 @@ public class ForceHeal : IVerb
_httpClient = httpClient; _httpClient = httpClient;
} }
public Command MakeCommand() public static VerbDefinition Definition = new("force-heal",
{ "Creates a patch from New file to Old file and uploads it",
var command = new Command("force-heal"); new[]
command.Add(new Option<AbsolutePath>(new[] {"-n", "-new-file"}, "New File")); {
command.Add(new Option<string>(new[] {"-o", "-old-file"}, "Old File")); new OptionDefinition(typeof(AbsolutePath), "n", "new-file", "New file"),
command.Description = "Creates a patch from New file to Old File and uploads it"; new OptionDefinition(typeof(AbsolutePath), "o", "old-file", "Old File")
command.Handler = CommandHandler.Create(Run); });
return command;
}
public async Task<int> Run(AbsolutePath oldFile, AbsolutePath newFile) public async Task<int> Run(AbsolutePath oldFile, AbsolutePath newFile)
{ {

View File

@ -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<AbsolutePath>(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<int> 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<MetricResult> 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<MetricResult>(line)!;
}
}
}

View File

@ -20,15 +20,11 @@ public class HashFile : IVerb
_logger = logger; _logger = logger;
} }
public Command MakeCommand() public static VerbDefinition Definition = new VerbDefinition("hash-file",
{ "Hashes a file with Wabbajack's hashing routines", new[]
var command = new Command("hash-file"); {
command.Add(new Option<AbsolutePath>(new[] {"-i", "-input"}, "Path to the file to hash")); new OptionDefinition(typeof(AbsolutePath), "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 async Task<int> Run(AbsolutePath input) public async Task<int> Run(AbsolutePath input)
{ {

View File

@ -17,15 +17,11 @@ public class HashUrlString : IVerb
_logger = logger; _logger = logger;
} }
public Command MakeCommand() public static VerbDefinition Definition = new VerbDefinition("hash-url-string",
{ "Hashes a URL string and returns the hashcode as hex", new[]
var command = new Command("hash-url-string"); {
command.Add(new Option<AbsolutePath>(new[] {"-u", "-url"}, "Url string to hash")); new OptionDefinition(typeof(AbsolutePath), "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 async Task<int> Run(string u) public async Task<int> Run(string u)
{ {

View File

@ -4,5 +4,4 @@ namespace Wabbajack.CLI.Verbs;
public interface IVerb public interface IVerb
{ {
public Command MakeCommand();
} }

View File

@ -39,18 +39,16 @@ public class Install : IVerb
_cache = cache; _cache = cache;
_gameLocator = gameLocator; _gameLocator = gameLocator;
} }
public Command MakeCommand()
public static VerbDefinition Definition = new VerbDefinition("install", "Installs a wabbajack file", new[]
{ {
var command = new Command("install"); new OptionDefinition(typeof(AbsolutePath), "w", "wabbajack", "Wabbajack file"),
command.Add(new Option<AbsolutePath>(new[] {"-w", "-wabbajack"}, "Wabbajack file")); new OptionDefinition(typeof(string), "m", "machineUrl", "Machine url to download"),
command.Add(new Option<AbsolutePath>(new[] {"-m", "-machineUrl"}, "Machine url to download")); new OptionDefinition(typeof(AbsolutePath), "o", "output", "Output path"),
command.Add(new Option<AbsolutePath>(new[] {"-o", "-output"}, "Output path")); new OptionDefinition(typeof(AbsolutePath), "d", "downloads", "Downloads path")
command.Add(new Option<AbsolutePath>(new[] {"-d", "-downloads"}, "Downloads path")); });
command.Description = "Installs a wabbajack file";
command.Handler = CommandHandler.Create(Run); internal async Task<int> Run(AbsolutePath wabbajack, AbsolutePath output, AbsolutePath downloads, string machineUrl, CancellationToken token)
return command;
}
public async Task<int> Run(AbsolutePath wabbajack, AbsolutePath output, AbsolutePath downloads, string machineUrl, CancellationToken token)
{ {
if (!string.IsNullOrEmpty(machineUrl)) if (!string.IsNullOrEmpty(machineUrl))
{ {

View File

@ -46,18 +46,15 @@ public class InstallCompileInstallVerify : IVerb
_gameLocator = gameLocator; _gameLocator = gameLocator;
_inferencer = inferencer; _inferencer = inferencer;
} }
public Command MakeCommand()
{
var command = new Command("install-compile-install-verify");
command.Add(new Option<AbsolutePath>(new[] {"-m", "-machineUrls"}, "Machine url(s) to download"));
command.Add(new Option<AbsolutePath>(new[] {"-d", "-downloads"}, "Downloads path"));
command.Add(new Option<AbsolutePath>(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<int> Run(AbsolutePath outputs, AbsolutePath downloads, IEnumerable<string> machineUrls, CancellationToken token) public async Task<int> Run(AbsolutePath outputs, AbsolutePath downloads, IEnumerable<string> machineUrls, CancellationToken token)
{ {
foreach (var machineUrl in machineUrls) foreach (var machineUrl in machineUrls)

View File

@ -29,14 +29,10 @@ public class ListCreationClubContent : IVerb
_client = wjClient; _client = wjClient;
_downloader = downloader; _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<OptionDefinition>());
public async Task<int> Run(CancellationToken token) public async Task<int> Run(CancellationToken token)
{ {
_logger.LogInformation("Getting list of content"); _logger.LogInformation("Getting list of content");

View File

@ -1,3 +1,4 @@
using System;
using System.CommandLine; using System.CommandLine;
using System.CommandLine.Invocation; using System.CommandLine.Invocation;
using System.CommandLine.NamingConventionBinder; using System.CommandLine.NamingConventionBinder;
@ -22,15 +23,11 @@ public class ListGames : IVerb
_logger = logger; _logger = logger;
_locator = locator; _locator = locator;
} }
public Command MakeCommand()
{ public static VerbDefinition Definition = new VerbDefinition("list-games",
var command = new Command("list-games"); "Lists all games Wabbajack recognizes, and their installed versions/locations (if any)", Array.Empty<OptionDefinition>());
command.Description = "Lists all games Wabbajack recognizes, and their installed versions/locations (if any)";
command.Handler = CommandHandler.Create(Run);
return command;
}
public async Task<int> Run(CancellationToken token) internal async Task<int> Run(CancellationToken token)
{ {
foreach (var game in GameRegistry.Games.OrderBy(g => g.Value.HumanFriendlyGameName)) foreach (var game in GameRegistry.Games.OrderBy(g => g.Value.HumanFriendlyGameName))
{ {

View File

@ -24,13 +24,9 @@ public class ListModlists : IVerb
_logger = logger; _logger = logger;
_client = wjClient; _client = wjClient;
} }
public Command MakeCommand()
{ public static VerbDefinition Definition =
var command = new Command("list-modlists"); new("list-modlists", "Lists all known modlists", Array.Empty<OptionDefinition>());
command.Description = "Lists all known modlists";
command.Handler = CommandHandler.Create(Run);
return command;
}
public async Task<int> Run(CancellationToken token) public async Task<int> Run(CancellationToken token)
{ {

View File

@ -18,15 +18,12 @@ public class MirrorFile : IVerb
_logger = logger; _logger = logger;
_client = wjClient; _client = wjClient;
} }
public Command MakeCommand()
{
var command = new Command("mirror-file");
command.Add(new Option<AbsolutePath>(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<int> Run(AbsolutePath input) public async Task<int> Run(AbsolutePath input)
{ {
_logger.LogInformation("Generating File Definition for {Name}", input.FileName); _logger.LogInformation("Generating File Definition for {Name}", input.FileName);

View File

@ -28,14 +28,12 @@ public class ModlistReport : IVerb
_logger = logger; _logger = logger;
_dtos = dtos; _dtos = dtos;
} }
public Command MakeCommand()
{ public static VerbDefinition Definition = new("modlist-report",
var command = new Command("modlist-report"); "Generates a usage report for a Modlist file", new[]
command.Add(new Option<AbsolutePath>(new[] {"-i", "-input"}, "Wabbajack file from which to generate a report")); {
command.Description = "Generates a usage report for a Modlist file"; new OptionDefinition(typeof(AbsolutePath), "i", "input", "Wabbajack file from which to generate a report")
command.Handler = CommandHandler.Create(Run); });
return command;
}
private static async Task<string> ReportTemplate(object o) private static async Task<string> ReportTemplate(object o)
{ {

View File

@ -34,21 +34,18 @@ public class SteamDownloadFile : IVerb
_dtos = dtos; _dtos = dtos;
_wjClient = wjClient; _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<string>(new[] {"-g", "-game", "-gameName"}, "Wabbajack game name"));
command.Add(new Option<string>(new[] {"-v", "-version"}, "Version of the game to download for")); public static VerbDefinition Definition = new VerbDefinition("steam-download-file",
command.Add(new Option<string>(new[] {"-f", "-file"}, "File to download (relative path)")); "Dumps information to the console about the given app",
command.Add(new Option<string>(new[] {"-o", "-output"}, "Output location")); new[]
command.Handler = CommandHandler.Create(Run); {
return command; 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<int> Run(string gameName, string version, string file, AbsolutePath output) internal async Task<int> Run(string gameName, string version, string file, AbsolutePath output)
{ {
if (!GameRegistry.TryGetByFuzzyName(gameName, out var game)) if (!GameRegistry.TryGetByFuzzyName(gameName, out var game))
_logger.LogError("Can't find definition for {Game}", gameName); _logger.LogError("Can't find definition for {Game}", gameName);

View File

@ -15,15 +15,15 @@ using JsonSerializer = System.Text.Json.JsonSerializer;
namespace Wabbajack.CLI.Verbs; namespace Wabbajack.CLI.Verbs;
public class SteamAppDumpInfo : IVerb public class SteamDumpAppInfo : IVerb
{ {
private readonly ILogger<SteamAppDumpInfo> _logger; private readonly ILogger<SteamDumpAppInfo> _logger;
private readonly Client _client; private readonly Client _client;
private readonly ITokenProvider<SteamLoginState> _token; private readonly ITokenProvider<SteamLoginState> _token;
private readonly DepotDownloader _downloader; private readonly DepotDownloader _downloader;
private readonly DTOSerializer _dtos; private readonly DTOSerializer _dtos;
public SteamAppDumpInfo(ILogger<SteamAppDumpInfo> logger, Client steamClient, ITokenProvider<SteamLoginState> token, public SteamDumpAppInfo(ILogger<SteamDumpAppInfo> logger, Client steamClient, ITokenProvider<SteamLoginState> token,
DepotDownloader downloader, DTOSerializer dtos) DepotDownloader downloader, DTOSerializer dtos)
{ {
_logger = logger; _logger = logger;
@ -32,6 +32,13 @@ public class SteamAppDumpInfo : IVerb
_downloader = downloader; _downloader = downloader;
_dtos = dtos; _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() public Command MakeCommand()
{ {
var command = new Command("steam-app-dump-info"); var command = new Command("steam-app-dump-info");

View File

@ -22,15 +22,12 @@ public class SteamLogin : IVerb
_client = steamClient; _client = steamClient;
_token = token; _token = token;
} }
public Command MakeCommand()
{ public static VerbDefinition Definition = new("steam-login",
var command = new Command("steam-login"); "Logs into Steam via interactive prompts", new[]
command.Description = "Logs into Steam via interactive prompts"; {
new OptionDefinition(typeof(string), "u", "user", "Username for login")
command.Add(new Option<string>(new[] {"-u", "-user"}, "Username for login")); });
command.Handler = CommandHandler.Create(Run);
return command;
}
public async Task<int> Run(string user) public async Task<int> Run(string user)
{ {

View File

@ -26,14 +26,13 @@ public class UploadToNexus : IVerb
_client = wjClient; _client = wjClient;
_dtos = dtos; _dtos = dtos;
} }
public Command MakeCommand()
{ public static VerbDefinition Definition = new("upload-to-nexus",
var command = new Command("upload-to-nexus"); "Uploads a file to the Nexus defined by the given .json definition file", new[]
command.Add(new Option<AbsolutePath>(new[] {"-d", "-definition"}, "Definition JSON file")); {
command.Description = "Uploads a file to the Nexus defined by the given .json definition file"; new OptionDefinition(typeof(AbsolutePath), "d", "definition", "Definition JSON file")
command.Handler = CommandHandler.Create(Run); });
return command;
}
public async Task<int> Run(AbsolutePath definition) public async Task<int> Run(AbsolutePath definition)
{ {

View File

@ -8,24 +8,20 @@ using Wabbajack.VFS;
namespace Wabbajack.CLI.Verbs; namespace Wabbajack.CLI.Verbs;
public class VFSIndexFolder : IVerb public class VFSIndex : IVerb
{ {
private readonly Context _context; private readonly Context _context;
public VFSIndexFolder(Context context) public VFSIndex(Context context)
{ {
_context = context; _context = context;
} }
public Command MakeCommand() public static VerbDefinition Definition = new VerbDefinition("vfs-index",
{ "Index and cache the contents of a folder", new[]
var command = new Command("vfs-index"); {
command.Add(new Option<AbsolutePath>(new[] {"-f", "--folder"}, "Folder to index")); new OptionDefinition(typeof(AbsolutePath), "f", "folder", "Folder to index")
command.Description = "Index and cache the contents of a folder"; });
command.Handler = CommandHandler.Create(Run);
return command;
}
public async Task<int> Run(AbsolutePath folder) public async Task<int> Run(AbsolutePath folder)
{ {

View File

@ -74,19 +74,11 @@ public class ValidateLists : IVerb
_httpLimiter = httpLimiter; _httpLimiter = httpLimiter;
} }
public Command MakeCommand() public static VerbDefinition Definition = new VerbDefinition("validate-lists",
{ "Gets a list of modlists, validates them and exports a result list", new[]
var command = new Command("validate-lists"); {
command.Add(new Option<AbsolutePath>(new[] {"-r", "--reports"}, "Location to store validation report outputs")); new OptionDefinition(typeof(AbsolutePath), "r", "reports", "Location to store validation report outputs")
});
command.Add(new Option<AbsolutePath>(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 async Task<int> Run(AbsolutePath reports, AbsolutePath otherArchives) public async Task<int> Run(AbsolutePath reports, AbsolutePath otherArchives)
{ {

View File

@ -40,6 +40,18 @@
<ItemGroup> <ItemGroup>
<None Remove="Resources\ModlistReport.html" /> <None Remove="Resources\ModlistReport.html" />
<EmbeddedResource Include="Resources\ModlistReport.html" /> <EmbeddedResource Include="Resources\ModlistReport.html" />
<None Update="VerbRegistration.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>VerbRegistration.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Compile Update="VerbRegistration.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>VerbRegistration.tt</DependentUpon>
</Compile>
</ItemGroup> </ItemGroup>
</Project> </Project>