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.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<IVerb> _verbs;
private static IServiceProvider _provider;
public CommandLineBuilder(IEnumerable<IVerb> verbs, IConsole console, LoggingRateLimiterReporter _)
public CommandLineBuilder(IServiceProvider provider, IConsole console, LoggingRateLimiterReporter _)
{
_console = console;
_verbs = verbs;
_provider = provider;
}
static CommandLineBuilder()
{
RegisterAll();
}
public async Task<int> 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<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.AddSingleton<IVerb, HashFile>();
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.AddCommands();
services.AddSingleton<IUserInterventionHandler, UserInterventionHandler>();
}).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;
}
public Command MakeCommand()
public static VerbDefinition Definition = new("compile", "Compiles a modlist",
new[]
{
var command = new Command("compile");
command.Add(new Option<AbsolutePath>(new[] {"-i", "-installPath"}, "Install Path"));
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;
}
new OptionDefinition(typeof(AbsolutePath), "i", "installPath", "Install Path"),
new OptionDefinition(typeof(AbsolutePath), "o", "outputPath", "OutputPath")
});
public async Task<int> Run(AbsolutePath installPath, AbsolutePath outputPath,
CancellationToken token)
{

View File

@ -19,16 +19,14 @@ public class Decrypt : IVerb
_logger = logger;
}
public Command MakeCommand()
{
var command = new Command("decrypt");
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"));
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<int> Run(AbsolutePath output, string name)
{
var data = await name.ToRelativePath()

View File

@ -41,16 +41,14 @@ public class DownloadAll : IVerb
_cache = cache;
}
public Command MakeCommand()
{
var command = new Command("download-all");
command.Add(new Option<AbsolutePath>(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<int> 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<int> Run(AbsolutePath output, CancellationToken token)
{
_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;
}
public Command MakeCommand()
{
var command = new Command("download-url");
command.Add(new Option<Uri>(new[] {"-u", "-url"}, "Url to parse"));
command.Add(new Option<AbsolutePath>(new[] {"-o", "-output"}, "Output file"));
command.Add(new Option<bool>(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<int> 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<int> Run(Uri url, AbsolutePath output, bool proxy = true)
{
var parsed = _dispatcher.Parse(url);
if (parsed == null)

View File

@ -25,17 +25,15 @@ public class DumpZipInfo : IVerb
_logger = logger;
}
public Command MakeCommand()
{
var command = new Command("dump-zip-info");
command.Add(new Option<AbsolutePath>(new[] {"-i", "-input"}, "Zip file ot parse"));
command.Add(new Option<bool>(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<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);
foreach (var value in (await ar.GetFiles()))

View File

@ -18,17 +18,15 @@ public class Encrypt : IVerb
_logger = logger;
}
public Command MakeCommand()
{
var command = new Command("encrypt");
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"));
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<int> Run(AbsolutePath input, string name)
internal async Task<int> Run(AbsolutePath input, string name)
{
var data = await input.ReadAllBytesAsync();
_logger.LogInformation("Encrypting {bytes} bytes into `{key}`", data.Length, name);

View File

@ -25,17 +25,14 @@ public class Extract : IVerb
_extractor = extractor;
}
public Command MakeCommand()
{
var command = new Command("extract");
command.Add(new Option<AbsolutePath>(new[] {"-i", "-input"}, "Input Archive"));
command.Add(new Option<AbsolutePath>(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<int> Run(AbsolutePath input, AbsolutePath output, CancellationToken token)
internal async Task<int> Run(AbsolutePath input, AbsolutePath output, CancellationToken token)
{
if (!output.DirectoryExists())
output.Parent.CreateDirectory();

View File

@ -43,15 +43,13 @@ public class ForceHeal : IVerb
_httpClient = httpClient;
}
public Command MakeCommand()
{
var command = new Command("force-heal");
command.Add(new Option<AbsolutePath>(new[] {"-n", "-new-file"}, "New File"));
command.Add(new Option<string>(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<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;
}
public Command MakeCommand()
{
var command = new Command("hash-file");
command.Add(new Option<AbsolutePath>(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<int> Run(AbsolutePath input)
{

View File

@ -17,15 +17,11 @@ public class HashUrlString : IVerb
_logger = logger;
}
public Command MakeCommand()
{
var command = new Command("hash-url-string");
command.Add(new Option<AbsolutePath>(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<int> Run(string u)
{

View File

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

View File

@ -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<AbsolutePath>(new[] {"-w", "-wabbajack"}, "Wabbajack file"));
command.Add(new Option<AbsolutePath>(new[] {"-m", "-machineUrl"}, "Machine url to download"));
command.Add(new Option<AbsolutePath>(new[] {"-o", "-output"}, "Output path"));
command.Add(new Option<AbsolutePath>(new[] {"-d", "-downloads"}, "Downloads path"));
command.Description = "Installs a wabbajack file";
command.Handler = CommandHandler.Create(Run);
return command;
}
public async Task<int> 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<int> Run(AbsolutePath wabbajack, AbsolutePath output, AbsolutePath downloads, string machineUrl, CancellationToken token)
{
if (!string.IsNullOrEmpty(machineUrl))
{

View File

@ -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<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)
{
foreach (var machineUrl in machineUrls)

View File

@ -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<OptionDefinition>());
public async Task<int> Run(CancellationToken token)
{
_logger.LogInformation("Getting list of content");

View File

@ -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<OptionDefinition>());
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))
{

View File

@ -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<OptionDefinition>());
public async Task<int> Run(CancellationToken token)
{

View File

@ -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<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)
{
_logger.LogInformation("Generating File Definition for {Name}", input.FileName);

View File

@ -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<AbsolutePath>(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<string> ReportTemplate(object o)
{

View File

@ -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<string>(new[] {"-g", "-game", "-gameName"}, "Wabbajack game name"));
command.Add(new Option<string>(new[] {"-v", "-version"}, "Version of the game to download for"));
command.Add(new Option<string>(new[] {"-f", "-file"}, "File to download (relative path)"));
command.Add(new Option<string>(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<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))
_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;
public class SteamAppDumpInfo : IVerb
public class SteamDumpAppInfo : IVerb
{
private readonly ILogger<SteamAppDumpInfo> _logger;
private readonly ILogger<SteamDumpAppInfo> _logger;
private readonly Client _client;
private readonly ITokenProvider<SteamLoginState> _token;
private readonly DepotDownloader _downloader;
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)
{
_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");

View File

@ -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<string>(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<int> Run(string user)
{

View File

@ -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<AbsolutePath>(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<int> Run(AbsolutePath definition)
{

View File

@ -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<AbsolutePath>(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<int> Run(AbsolutePath folder)
{

View File

@ -74,19 +74,11 @@ public class ValidateLists : IVerb
_httpLimiter = httpLimiter;
}
public Command MakeCommand()
{
var command = new Command("validate-lists");
command.Add(new Option<AbsolutePath>(new[] {"-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 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<int> Run(AbsolutePath reports, AbsolutePath otherArchives)
{

View File

@ -40,6 +40,18 @@
<ItemGroup>
<None Remove="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>
</Project>