From 9a3a3448d043e1d1ca59f88b5e28415e04d84d28 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 14:04:40 +0200 Subject: [PATCH 01/10] Enabled Nullable --- .editorconfig | 65 ++++++++++++++++++++++++++- Wabbajack.CLI/CLIUtils.cs | 12 +++++ Wabbajack.CLI/Verbs/AVerb.cs | 4 +- Wabbajack.CLI/Verbs/ChangeDownload.cs | 13 +++--- Wabbajack.CLI/Verbs/Changelog.cs | 24 ++++++++++ Wabbajack.CLI/Verbs/Decrypt.cs | 4 +- Wabbajack.CLI/Verbs/DeleteFile.cs | 3 +- Wabbajack.CLI/Verbs/DownloadUrl.cs | 4 +- Wabbajack.CLI/Verbs/Encrypt.cs | 4 +- Wabbajack.CLI/Verbs/Validate.cs | 7 +-- Wabbajack.CLI/Wabbajack.CLI.csproj | 2 + 11 files changed, 120 insertions(+), 22 deletions(-) create mode 100644 Wabbajack.CLI/Verbs/Changelog.cs diff --git a/.editorconfig b/.editorconfig index 9270fbad..5b46bd60 100644 --- a/.editorconfig +++ b/.editorconfig @@ -121,4 +121,67 @@ csharp_preserve_single_line_blocks = true # CS4014: Task not awaited dotnet_diagnostic.CS4014.severity = error # CS1998: Async function does not contain await -dotnet_diagnostic.CS1998.severity = silent \ No newline at end of file +dotnet_diagnostic.CS1998.severity = silent + +############################### +# C# Nullability # +############################### +# CS8602: Dereference of a possibly null reference. +dotnet_diagnostic.CS8602.severity = error + +# CS8600: Converting null literal or possible null value to non-nullable type. +dotnet_diagnostic.CS8600.severity = error + +# CS8619: Nullability of reference types in value doesn't match target type. +dotnet_diagnostic.CS8619.severity = error + +# CS8603: Possible null reference return. +dotnet_diagnostic.CS8603.severity = error + +# CS8625: Cannot convert null literal to non-nullable reference type. +dotnet_diagnostic.CS8625.severity = error + +# CS8653: A default expression introduces a null value for a type parameter. +dotnet_diagnostic.CS8653.severity = silent + +# CS8601: Possible null reference assignment. +dotnet_diagnostic.CS8601.severity = error + +# CS8604: Possible null reference argument. +dotnet_diagnostic.CS8604.severity = error + +# CS8622: Nullability of reference types in type of parameter doesn't match the target delegate. +dotnet_diagnostic.CS8622.severity = error + +# CS8610: Nullability of reference types in type of parameter doesn't match overridden member. +dotnet_diagnostic.CS8610.severity = error + +# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable. +dotnet_diagnostic.CS8618.severity = error + +# CS8629: Nullable value type may be null. +dotnet_diagnostic.CS8629.severity = error + +# CS8620: Argument cannot be used for parameter due to differences in the nullability of reference types. +dotnet_diagnostic.CS8620.severity = error + +# CS8614: Nullability of reference types in type of parameter doesn't match implicitly implemented member. +dotnet_diagnostic.CS8614.severity = error + +# CS8617: Nullability of reference types in type of parameter doesn't match implemented member. +dotnet_diagnostic.CS8617.severity = error + +# CS8611: Nullability of reference types in type of parameter doesn't match partial method declaration. +dotnet_diagnostic.CS8611.severity = error + +# CS8597: Thrown value may be null. +dotnet_diagnostic.CS8597.severity = error + +# CS8609: Nullability of reference types in return type doesn't match overridden member. +dotnet_diagnostic.CS8609.severity = error + +# CS8714: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint. +dotnet_diagnostic.CS8714.severity = error + +# CS8605: Unboxing a possibly null value. +dotnet_diagnostic.CS8605.severity = error \ No newline at end of file diff --git a/Wabbajack.CLI/CLIUtils.cs b/Wabbajack.CLI/CLIUtils.cs index c9a081e8..8f20180e 100644 --- a/Wabbajack.CLI/CLIUtils.cs +++ b/Wabbajack.CLI/CLIUtils.cs @@ -1,9 +1,21 @@ using System; +using Wabbajack.CLI.Verbs; namespace Wabbajack.CLI { + [AttributeUsage(AttributeTargets.Property)] + internal class FileAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + internal class DirectoryAttribute : Attribute { } + internal static class CLIUtils { + internal static bool VerifyArguments(AVerb verb) + { + return true; + } + internal static void Log(string msg, bool newLine = true) { //TODO: maybe also write to a log file? diff --git a/Wabbajack.CLI/Verbs/AVerb.cs b/Wabbajack.CLI/Verbs/AVerb.cs index 5d341f0a..5693a520 100644 --- a/Wabbajack.CLI/Verbs/AVerb.cs +++ b/Wabbajack.CLI/Verbs/AVerb.cs @@ -1,6 +1,4 @@ -using System.Text; -using System.Threading; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Wabbajack.CLI.Verbs { diff --git a/Wabbajack.CLI/Verbs/ChangeDownload.cs b/Wabbajack.CLI/Verbs/ChangeDownload.cs index 966dfad9..f974d308 100644 --- a/Wabbajack.CLI/Verbs/ChangeDownload.cs +++ b/Wabbajack.CLI/Verbs/ChangeDownload.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -18,16 +19,16 @@ namespace Wabbajack.CLI.Verbs public class ChangeDownload : AVerb { [Option("input", Required = true, HelpText = "Input folder containing the downloads you want to move")] - public string Input { get; set; } + public string? Input { get; set; } [Option("output", Required = true, HelpText = "Output folder the downloads should be transferred to")] - public string Output { get; set; } + public string? Output { get; set; } [Option("modlist", Required = true, HelpText = "The Modlist, can either be a .wabbajack or a modlist.txt file")] - public string Modlist { get; set; } + public string? Modlist { get; set; } [Option("mods", Required = false, HelpText = "Mods folder location if the provided modlist file is an MO2 modlist.txt")] - public string Mods { get; set; } + public string? Mods { get; set; } [Option("copy", Default = true, HelpText = "Whether to copy the files", SetName = "copy")] public bool Copy { get; set; } @@ -69,13 +70,13 @@ namespace Wabbajack.CLI.Verbs Directory.CreateDirectory(Output); } - if (!Modlist.EndsWith(Consts.ModListExtension) && !Modlist.EndsWith("modlist.txt")) + if (Modlist != null && (!Modlist.EndsWith(Consts.ModListExtension) && !Modlist.EndsWith("modlist.txt"))) return CLIUtils.Exit($"The file {Modlist} is not a valid modlist file!", -1); if (Copy && Move) return CLIUtils.Exit("You can't set both copy and move flags!", -1); - var isModlist = Modlist.EndsWith(Consts.ModListExtension); + var isModlist = Modlist != null && Modlist.EndsWith(Consts.ModListExtension); var list = new List(); diff --git a/Wabbajack.CLI/Verbs/Changelog.cs b/Wabbajack.CLI/Verbs/Changelog.cs new file mode 100644 index 00000000..794e92ee --- /dev/null +++ b/Wabbajack.CLI/Verbs/Changelog.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using CommandLine; + +namespace Wabbajack.CLI.Verbs +{ + [Verb("changelog", HelpText = "Generate a changelog using two different versions of the same Modlist.")] + public class Changelog : AVerb + { + [Option("original", Required = true, HelpText = "The original/previous modlist")] + public string? Original { get; set; } + + [Option("update", Required = true, HelpText = "The current/updated modlist")] + public string? Update { get; set; } + + [Option('o', "output", Required = false, HelpText = "The output file")] + public string? Output { get; set; } + + protected override Task Run() + { + throw new NotImplementedException(); + } + } +} diff --git a/Wabbajack.CLI/Verbs/Decrypt.cs b/Wabbajack.CLI/Verbs/Decrypt.cs index d0d0126d..388207d0 100644 --- a/Wabbajack.CLI/Verbs/Decrypt.cs +++ b/Wabbajack.CLI/Verbs/Decrypt.cs @@ -9,11 +9,11 @@ namespace Wabbajack.CLI.Verbs public class Decrypt : AVerb { [Option('n', "name", Required = true, HelpText = @"Credential to encrypt and store in AppData\Local\Wabbajack")] - public string Name { get; set; } + public string? Name { get; set; } [Option('o', "output", Required = true, HelpText = @"Output file for the decrypted data")] - public string Output { get; set; } + public string? Output { get; set; } protected override async Task Run() { diff --git a/Wabbajack.CLI/Verbs/DeleteFile.cs b/Wabbajack.CLI/Verbs/DeleteFile.cs index c89b698a..124e9f94 100644 --- a/Wabbajack.CLI/Verbs/DeleteFile.cs +++ b/Wabbajack.CLI/Verbs/DeleteFile.cs @@ -9,7 +9,8 @@ namespace Wabbajack.CLI.Verbs public class DeleteFile : AVerb { [Option('n', "name", Required = true, HelpText = @"Full name (as returned by my-files) of the file")] - public string Name { get; set; } + public string? Name { get; set; } + protected override async Task Run() { Console.WriteLine(await AuthorAPI.DeleteFile(Name)); diff --git a/Wabbajack.CLI/Verbs/DownloadUrl.cs b/Wabbajack.CLI/Verbs/DownloadUrl.cs index 3a957fd2..f29ef59a 100644 --- a/Wabbajack.CLI/Verbs/DownloadUrl.cs +++ b/Wabbajack.CLI/Verbs/DownloadUrl.cs @@ -14,10 +14,10 @@ namespace Wabbajack.CLI.Verbs public class DownloadUrl : AVerb { [Option('u', "url", Required = true, HelpText = "Url to download")] - public Uri Url { get; set; } + public Uri? Url { get; set; } [Option('o', "output", Required = true, HelpText = "Output file name")] - public string Output { get; set; } + public string? Output { get; set; } protected override async Task Run() { diff --git a/Wabbajack.CLI/Verbs/Encrypt.cs b/Wabbajack.CLI/Verbs/Encrypt.cs index ace41e2a..9213349f 100644 --- a/Wabbajack.CLI/Verbs/Encrypt.cs +++ b/Wabbajack.CLI/Verbs/Encrypt.cs @@ -9,10 +9,10 @@ namespace Wabbajack.CLI.Verbs public class Encrypt : AVerb { [Option('n', "name", Required = true, HelpText = @"Credential to encrypt and store in AppData\Local\Wabbajack")] - public string Name { get; set; } + public string? Name { get; set; } [Option('i', "input", Required = true, HelpText = @"Source data file name")] - public string Input { get; set; } + public string? Input { get; set; } protected override async Task Run() { diff --git a/Wabbajack.CLI/Verbs/Validate.cs b/Wabbajack.CLI/Verbs/Validate.cs index 5a21e0af..141c9464 100644 --- a/Wabbajack.CLI/Verbs/Validate.cs +++ b/Wabbajack.CLI/Verbs/Validate.cs @@ -1,11 +1,9 @@ using System; -using System.Reactive.Linq; using System.Threading.Tasks; using CommandLine; using Wabbajack.Common; using Wabbajack.Lib; using Wabbajack.Lib.Validation; -using Wabbajack.VirtualFileSystem; using File = Alphaleonis.Win32.Filesystem.File; namespace Wabbajack.CLI.Verbs @@ -14,12 +12,11 @@ namespace Wabbajack.CLI.Verbs public class Validate : AVerb { [Option('i', "input", Required = true, HelpText = @"Modlist file")] - public string Input { get; set; } + public string? Input { get; set; } /// /// Runs the Validation of a Modlist /// - /// /// /// /// -1 bad Input @@ -33,7 +30,7 @@ namespace Wabbajack.CLI.Verbs return CLIUtils.Exit($"The file {Input} does not exist!", -1); - if (!Input.EndsWith(Consts.ModListExtension)) + if (Input != null && !Input.EndsWith(Consts.ModListExtension)) return CLIUtils.Exit($"The file {Input} does not end with {Consts.ModListExtension}!", -1); ModList modlist; diff --git a/Wabbajack.CLI/Wabbajack.CLI.csproj b/Wabbajack.CLI/Wabbajack.CLI.csproj index 177a680f..ed51c614 100644 --- a/Wabbajack.CLI/Wabbajack.CLI.csproj +++ b/Wabbajack.CLI/Wabbajack.CLI.csproj @@ -12,6 +12,8 @@ An automated ModList installer true win10-x64 + 8.0 + enable From b8be153c99b40d7e964364bd158528ee09d619b9 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 14:22:07 +0200 Subject: [PATCH 02/10] Created HasValidArguments function for checking paths --- Wabbajack.CLI/CLIUtils.cs | 58 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/Wabbajack.CLI/CLIUtils.cs b/Wabbajack.CLI/CLIUtils.cs index 8f20180e..c554bde9 100644 --- a/Wabbajack.CLI/CLIUtils.cs +++ b/Wabbajack.CLI/CLIUtils.cs @@ -1,5 +1,10 @@ using System; +using System.Linq; +using System.Reflection; +using Alphaleonis.Win32.Filesystem; +using CommandLine; using Wabbajack.CLI.Verbs; +using Wabbajack.Common; namespace Wabbajack.CLI { @@ -11,9 +16,58 @@ namespace Wabbajack.CLI internal static class CLIUtils { - internal static bool VerifyArguments(AVerb verb) + internal static bool HasValidArguments(AVerb verb) { - return true; + var props = verb.GetType().GetProperties().Where(p => + { + var hasAttr = p.HasAttribute(typeof(OptionAttribute)); + if (!hasAttr) + return false; + + if (p.PropertyType != typeof(string)) + return false; + + var value = p.GetValue(verb); + if (value == null) + return false; + + var stringValue = (string)value; + return string.IsNullOrWhiteSpace(stringValue); + }); + + var valid = true; + + props.Do(p => + { + if (!valid) + return; + + var valueObject = p.GetValue(verb); + + // not really possible since we filtered them out but whatever + if (valueObject == null) + return; + + var value = (string)valueObject; + + if (p.HasAttribute(typeof(FileAttribute))) + { + valid = File.Exists(value); + } + + if (p.HasAttribute(typeof(DirectoryAttribute))) + { + valid = Directory.Exists(value); + } + }); + + return valid; + } + + internal static bool HasAttribute(this MemberInfo member, Type attribute) + { + var attributes = member.GetCustomAttributes(attribute); + return attributes.Count() == 1; } internal static void Log(string msg, bool newLine = true) From 3bcab0db5161ef95cab358ead56f3b94728b3500 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 14:23:36 +0200 Subject: [PATCH 03/10] Renamed File/Directory Attributes to IsFile/Directory --- Wabbajack.CLI/CLIUtils.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Wabbajack.CLI/CLIUtils.cs b/Wabbajack.CLI/CLIUtils.cs index c554bde9..7fe21152 100644 --- a/Wabbajack.CLI/CLIUtils.cs +++ b/Wabbajack.CLI/CLIUtils.cs @@ -9,10 +9,10 @@ using Wabbajack.Common; namespace Wabbajack.CLI { [AttributeUsage(AttributeTargets.Property)] - internal class FileAttribute : Attribute { } + internal class IsFileAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property)] - internal class DirectoryAttribute : Attribute { } + internal class IsDirectoryAttribute : Attribute { } internal static class CLIUtils { @@ -50,12 +50,12 @@ namespace Wabbajack.CLI var value = (string)valueObject; - if (p.HasAttribute(typeof(FileAttribute))) + if (p.HasAttribute(typeof(IsFileAttribute))) { valid = File.Exists(value); } - if (p.HasAttribute(typeof(DirectoryAttribute))) + if (p.HasAttribute(typeof(IsDirectoryAttribute))) { valid = Directory.Exists(value); } From 48332d67f1983ee3d4b4acaf23d22cb68f12749b Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 14:57:37 +0200 Subject: [PATCH 04/10] Implemented new Attributes in ChangeDownload --- Wabbajack.CLI/CLIUtils.cs | 92 ++++++++++++++++++-- Wabbajack.CLI/Properties/launchSettings.json | 8 ++ Wabbajack.CLI/Verbs/ChangeDownload.cs | 17 ++-- 3 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 Wabbajack.CLI/Properties/launchSettings.json diff --git a/Wabbajack.CLI/CLIUtils.cs b/Wabbajack.CLI/CLIUtils.cs index 7fe21152..708b268c 100644 --- a/Wabbajack.CLI/CLIUtils.cs +++ b/Wabbajack.CLI/CLIUtils.cs @@ -8,19 +8,52 @@ using Wabbajack.Common; namespace Wabbajack.CLI { + /// + /// Abstract class to mark attributes which need validating + /// [AttributeUsage(AttributeTargets.Property)] - internal class IsFileAttribute : Attribute { } + internal abstract class AValidateAttribute : Attribute + { + /// + /// Exit the application if validation failed. Will just log if set to false + /// + public bool Exit { get; set; } - [AttributeUsage(AttributeTargets.Property)] - internal class IsDirectoryAttribute : Attribute { } + /// + /// Custom message if validation failed. Use placeholder %1 to insert the value + /// + public string? CustomMessage { get; set; } + } + + /// + /// Validating if the file exists + /// + internal class IsFileAttribute : AValidateAttribute { } + + /// + /// Validating if the directory exists + /// + internal class IsDirectoryAttribute : AValidateAttribute + { + /// + /// Create the directory if it does not exists + /// + public bool Create { get; set; } + } internal static class CLIUtils { + /// + /// Validates all Attributes of type + /// + /// The verb to validate + /// internal static bool HasValidArguments(AVerb verb) { var props = verb.GetType().GetProperties().Where(p => { - var hasAttr = p.HasAttribute(typeof(OptionAttribute)); + var hasAttr = p.HasAttribute(typeof(OptionAttribute)) + && p.HasAttribute(typeof(AValidateAttribute)); if (!hasAttr) return false; @@ -32,7 +65,7 @@ namespace Wabbajack.CLI return false; var stringValue = (string)value; - return string.IsNullOrWhiteSpace(stringValue); + return !string.IsNullOrWhiteSpace(stringValue); }); var valid = true; @@ -49,21 +82,68 @@ namespace Wabbajack.CLI return; var value = (string)valueObject; + var attribute = (AValidateAttribute)p.GetAttribute(typeof(AValidateAttribute)); + var isFile = false; if (p.HasAttribute(typeof(IsFileAttribute))) { + isFile = true; valid = File.Exists(value); } if (p.HasAttribute(typeof(IsDirectoryAttribute))) { - valid = Directory.Exists(value); + var dirAttribute = (IsDirectoryAttribute)attribute; + var exists = Directory.Exists(value); + + if (!exists) + { + if (dirAttribute.Create) + { + Log($"Directory {value} does not exist and will be created"); + Directory.CreateDirectory(value); + } + else + valid = false; + } } + + if (valid) + return; + + var message = string.IsNullOrWhiteSpace(attribute.CustomMessage) + ? isFile + ? $"The file {value} does not exist!" + : $"The folder {value} does not exist!" + : attribute.CustomMessage.Replace("%1", value); + + if (attribute.Exit) + Exit(message, -1); + else + Log(message); }); return valid; } + /// + /// Gets an attribute of a specific type + /// + /// + /// + /// + internal static Attribute GetAttribute(this MemberInfo member, Type attribute) + { + var attributes = member.GetCustomAttributes(attribute); + return attributes.ElementAt(0); + } + + /// + /// Checks if a has a custom attribute + /// + /// + /// + /// internal static bool HasAttribute(this MemberInfo member, Type attribute) { var attributes = member.GetCustomAttributes(attribute); diff --git a/Wabbajack.CLI/Properties/launchSettings.json b/Wabbajack.CLI/Properties/launchSettings.json new file mode 100644 index 00000000..ad46cac6 --- /dev/null +++ b/Wabbajack.CLI/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Wabbajack.CLI": { + "commandName": "Project", + "commandLineArgs": "change-download --input=\"S:\\Modding FAST\\Wabbajack\\downloads\" --output=\"S:\\Modding FAST\\test\" --modlist=\"S:\\Modding FAST\\Wabbajack\\profiles\\erri120's Dope Test Modlist\\modlist.txt\"" + } + } +} \ No newline at end of file diff --git a/Wabbajack.CLI/Verbs/ChangeDownload.cs b/Wabbajack.CLI/Verbs/ChangeDownload.cs index f974d308..dc826a68 100644 --- a/Wabbajack.CLI/Verbs/ChangeDownload.cs +++ b/Wabbajack.CLI/Verbs/ChangeDownload.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -18,12 +17,15 @@ namespace Wabbajack.CLI.Verbs [Verb("change-download", HelpText = "Move or Copy all used Downloads from a Modlist to another directory")] public class ChangeDownload : AVerb { + [IsDirectory(Exit = true, CustomMessage = "Downloads folder %1 does not exist!")] [Option("input", Required = true, HelpText = "Input folder containing the downloads you want to move")] public string? Input { get; set; } + [IsDirectory(Create = true)] [Option("output", Required = true, HelpText = "Output folder the downloads should be transferred to")] public string? Output { get; set; } + [IsFile(Exit = true, CustomMessage = "Modlist file %1 does not exist!")] [Option("modlist", Required = true, HelpText = "The Modlist, can either be a .wabbajack or a modlist.txt file")] public string? Modlist { get; set; } @@ -58,17 +60,8 @@ namespace Wabbajack.CLI.Verbs protected override async Task Run() { - if (!File.Exists(Modlist)) - return CLIUtils.Exit($"The file {Modlist} does not exist!", -1); - - if (!Directory.Exists(Input)) - return CLIUtils.Exit($"The input directory {Input} does not exist!", -1); - - if (!Directory.Exists(Output)) - { - CLIUtils.Log($"The output directory {Output} does not exist, it will be created."); - Directory.CreateDirectory(Output); - } + if (!CLIUtils.HasValidArguments(this)) + CLIUtils.Exit("Arguments are not valid!", -1); if (Modlist != null && (!Modlist.EndsWith(Consts.ModListExtension) && !Modlist.EndsWith("modlist.txt"))) return CLIUtils.Exit($"The file {Modlist} is not a valid modlist file!", -1); From f5082d97e6f10b10f2c6dee9cb40c81d743c911f Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 14:59:38 +0200 Subject: [PATCH 05/10] Using OptionAttribute.Required to check if we need to exit --- Wabbajack.CLI/CLIUtils.cs | 9 +++------ Wabbajack.CLI/Verbs/ChangeDownload.cs | 4 ++-- Wabbajack.CLI/Verbs/Decrypt.cs | 1 - 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Wabbajack.CLI/CLIUtils.cs b/Wabbajack.CLI/CLIUtils.cs index 708b268c..c185bd2d 100644 --- a/Wabbajack.CLI/CLIUtils.cs +++ b/Wabbajack.CLI/CLIUtils.cs @@ -14,11 +14,6 @@ namespace Wabbajack.CLI [AttributeUsage(AttributeTargets.Property)] internal abstract class AValidateAttribute : Attribute { - /// - /// Exit the application if validation failed. Will just log if set to false - /// - public bool Exit { get; set; } - /// /// Custom message if validation failed. Use placeholder %1 to insert the value /// @@ -117,7 +112,9 @@ namespace Wabbajack.CLI : $"The folder {value} does not exist!" : attribute.CustomMessage.Replace("%1", value); - if (attribute.Exit) + var optionAttribute = (OptionAttribute)p.GetAttribute(typeof(OptionAttribute)); + + if (optionAttribute.Required) Exit(message, -1); else Log(message); diff --git a/Wabbajack.CLI/Verbs/ChangeDownload.cs b/Wabbajack.CLI/Verbs/ChangeDownload.cs index dc826a68..bce19853 100644 --- a/Wabbajack.CLI/Verbs/ChangeDownload.cs +++ b/Wabbajack.CLI/Verbs/ChangeDownload.cs @@ -17,7 +17,7 @@ namespace Wabbajack.CLI.Verbs [Verb("change-download", HelpText = "Move or Copy all used Downloads from a Modlist to another directory")] public class ChangeDownload : AVerb { - [IsDirectory(Exit = true, CustomMessage = "Downloads folder %1 does not exist!")] + [IsDirectory(CustomMessage = "Downloads folder %1 does not exist!")] [Option("input", Required = true, HelpText = "Input folder containing the downloads you want to move")] public string? Input { get; set; } @@ -25,7 +25,7 @@ namespace Wabbajack.CLI.Verbs [Option("output", Required = true, HelpText = "Output folder the downloads should be transferred to")] public string? Output { get; set; } - [IsFile(Exit = true, CustomMessage = "Modlist file %1 does not exist!")] + [IsFile(CustomMessage = "Modlist file %1 does not exist!")] [Option("modlist", Required = true, HelpText = "The Modlist, can either be a .wabbajack or a modlist.txt file")] public string? Modlist { get; set; } diff --git a/Wabbajack.CLI/Verbs/Decrypt.cs b/Wabbajack.CLI/Verbs/Decrypt.cs index 388207d0..6cf9a82a 100644 --- a/Wabbajack.CLI/Verbs/Decrypt.cs +++ b/Wabbajack.CLI/Verbs/Decrypt.cs @@ -11,7 +11,6 @@ namespace Wabbajack.CLI.Verbs [Option('n', "name", Required = true, HelpText = @"Credential to encrypt and store in AppData\Local\Wabbajack")] public string? Name { get; set; } - [Option('o', "output", Required = true, HelpText = @"Output file for the decrypted data")] public string? Output { get; set; } From 0bb8d9e84c8a5c5f6d59d914575f32bc0245819f Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 15:06:02 +0200 Subject: [PATCH 06/10] Implemented new attributes in all verbs --- Wabbajack.CLI/Verbs/AVerb.cs | 3 +++ Wabbajack.CLI/Verbs/ChangeDownload.cs | 3 --- Wabbajack.CLI/Verbs/Encrypt.cs | 1 + Wabbajack.CLI/Verbs/Validate.cs | 5 +---- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Wabbajack.CLI/Verbs/AVerb.cs b/Wabbajack.CLI/Verbs/AVerb.cs index 5693a520..95bf203c 100644 --- a/Wabbajack.CLI/Verbs/AVerb.cs +++ b/Wabbajack.CLI/Verbs/AVerb.cs @@ -6,6 +6,9 @@ namespace Wabbajack.CLI.Verbs { public int Execute() { + if (!CLIUtils.HasValidArguments(this)) + CLIUtils.Exit("The provided arguments are not valid! Check previous messages for more information", -1); + return Run().Result; } diff --git a/Wabbajack.CLI/Verbs/ChangeDownload.cs b/Wabbajack.CLI/Verbs/ChangeDownload.cs index bce19853..17d783a5 100644 --- a/Wabbajack.CLI/Verbs/ChangeDownload.cs +++ b/Wabbajack.CLI/Verbs/ChangeDownload.cs @@ -60,9 +60,6 @@ namespace Wabbajack.CLI.Verbs protected override async Task Run() { - if (!CLIUtils.HasValidArguments(this)) - CLIUtils.Exit("Arguments are not valid!", -1); - if (Modlist != null && (!Modlist.EndsWith(Consts.ModListExtension) && !Modlist.EndsWith("modlist.txt"))) return CLIUtils.Exit($"The file {Modlist} is not a valid modlist file!", -1); diff --git a/Wabbajack.CLI/Verbs/Encrypt.cs b/Wabbajack.CLI/Verbs/Encrypt.cs index 9213349f..1e6592da 100644 --- a/Wabbajack.CLI/Verbs/Encrypt.cs +++ b/Wabbajack.CLI/Verbs/Encrypt.cs @@ -11,6 +11,7 @@ namespace Wabbajack.CLI.Verbs [Option('n', "name", Required = true, HelpText = @"Credential to encrypt and store in AppData\Local\Wabbajack")] public string? Name { get; set; } + [IsFile(CustomMessage = "The input file %1 does not exist!")] [Option('i', "input", Required = true, HelpText = @"Source data file name")] public string? Input { get; set; } diff --git a/Wabbajack.CLI/Verbs/Validate.cs b/Wabbajack.CLI/Verbs/Validate.cs index 141c9464..d8d98670 100644 --- a/Wabbajack.CLI/Verbs/Validate.cs +++ b/Wabbajack.CLI/Verbs/Validate.cs @@ -11,6 +11,7 @@ namespace Wabbajack.CLI.Verbs [Verb("validate", HelpText = @"Validates a Modlist")] public class Validate : AVerb { + [IsFile(CustomMessage = "The modlist file %1 does not exist!")] [Option('i', "input", Required = true, HelpText = @"Modlist file")] public string? Input { get; set; } @@ -26,10 +27,6 @@ namespace Wabbajack.CLI.Verbs /// protected override async Task Run() { - if (!File.Exists(Input)) - return CLIUtils.Exit($"The file {Input} does not exist!", -1); - - if (Input != null && !Input.EndsWith(Consts.ModListExtension)) return CLIUtils.Exit($"The file {Input} does not end with {Consts.ModListExtension}!", -1); From 0f4c843730392db34a613b2cbcf2c4e6c4c4299d Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 15:09:17 +0200 Subject: [PATCH 07/10] Created and implemented ExitCode enum --- Wabbajack.CLI/CLIUtils.cs | 13 ++++++++++--- Wabbajack.CLI/Verbs/AVerb.cs | 3 ++- Wabbajack.CLI/Verbs/ChangeDownload.cs | 14 +++++++------- Wabbajack.CLI/Verbs/DownloadUrl.cs | 2 +- Wabbajack.CLI/Verbs/Validate.cs | 8 ++++---- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Wabbajack.CLI/CLIUtils.cs b/Wabbajack.CLI/CLIUtils.cs index c185bd2d..f53844f6 100644 --- a/Wabbajack.CLI/CLIUtils.cs +++ b/Wabbajack.CLI/CLIUtils.cs @@ -8,6 +8,13 @@ using Wabbajack.Common; namespace Wabbajack.CLI { + internal enum ExitCode + { + BadArguments = -1, + Ok = 0, + Error = 1 + } + /// /// Abstract class to mark attributes which need validating /// @@ -115,7 +122,7 @@ namespace Wabbajack.CLI var optionAttribute = (OptionAttribute)p.GetAttribute(typeof(OptionAttribute)); if (optionAttribute.Required) - Exit(message, -1); + Exit(message, ExitCode.BadArguments); else Log(message); }); @@ -156,10 +163,10 @@ namespace Wabbajack.CLI Console.Write(msg); } - internal static int Exit(string msg, int code) + internal static int Exit(string msg, ExitCode code) { Log(msg); - return code; + return (int)code; } internal static void LogException(Exception e, string msg) diff --git a/Wabbajack.CLI/Verbs/AVerb.cs b/Wabbajack.CLI/Verbs/AVerb.cs index 95bf203c..1443ecf7 100644 --- a/Wabbajack.CLI/Verbs/AVerb.cs +++ b/Wabbajack.CLI/Verbs/AVerb.cs @@ -7,7 +7,8 @@ namespace Wabbajack.CLI.Verbs public int Execute() { if (!CLIUtils.HasValidArguments(this)) - CLIUtils.Exit("The provided arguments are not valid! Check previous messages for more information", -1); + CLIUtils.Exit("The provided arguments are not valid! Check previous messages for more information", + ExitCode.BadArguments); return Run().Result; } diff --git a/Wabbajack.CLI/Verbs/ChangeDownload.cs b/Wabbajack.CLI/Verbs/ChangeDownload.cs index 17d783a5..aa53fa72 100644 --- a/Wabbajack.CLI/Verbs/ChangeDownload.cs +++ b/Wabbajack.CLI/Verbs/ChangeDownload.cs @@ -61,10 +61,10 @@ namespace Wabbajack.CLI.Verbs protected override async Task Run() { if (Modlist != null && (!Modlist.EndsWith(Consts.ModListExtension) && !Modlist.EndsWith("modlist.txt"))) - return CLIUtils.Exit($"The file {Modlist} is not a valid modlist file!", -1); + return CLIUtils.Exit($"The file {Modlist} is not a valid modlist file!", ExitCode.BadArguments); if (Copy && Move) - return CLIUtils.Exit("You can't set both copy and move flags!", -1); + return CLIUtils.Exit("You can't set both copy and move flags!", ExitCode.BadArguments); var isModlist = Modlist != null && Modlist.EndsWith(Consts.ModListExtension); @@ -80,12 +80,12 @@ namespace Wabbajack.CLI.Verbs } catch (Exception e) { - return CLIUtils.Exit($"Error while loading the Modlist!\n{e}", 1); + return CLIUtils.Exit($"Error while loading the Modlist!\n{e}", ExitCode.Error); } if (modlist == null) { - return CLIUtils.Exit("The Modlist could not be loaded!", 1); + return CLIUtils.Exit("The Modlist could not be loaded!", ExitCode.Error); } CLIUtils.Log($"Modlist contains {modlist.Archives.Count} archives."); @@ -144,18 +144,18 @@ namespace Wabbajack.CLI.Verbs else { if (!Directory.Exists(Mods)) - return CLIUtils.Exit($"Mods directory {Mods} does not exist!", -1); + return CLIUtils.Exit($"Mods directory {Mods} does not exist!", ExitCode.BadArguments); CLIUtils.Log($"Reading modlist.txt from {Modlist}"); string[] modlist = File.ReadAllLines(Modlist); if (modlist == null || modlist.Length == 0) - return CLIUtils.Exit($"Provided modlist.txt file at {Modlist} is empty or could not be read!", -1); + return CLIUtils.Exit($"Provided modlist.txt file at {Modlist} is empty or could not be read!", ExitCode.BadArguments); var mods = modlist.Where(s => s.StartsWith("+")).Select(s => s.Substring(1)).ToHashSet(); if (mods.Count == 0) - return CLIUtils.Exit("Counted mods from modlist.txt are 0!", -1); + return CLIUtils.Exit("Counted mods from modlist.txt are 0!", ExitCode.BadArguments); CLIUtils.Log($"Found {mods.Count} mods in modlist.txt"); diff --git a/Wabbajack.CLI/Verbs/DownloadUrl.cs b/Wabbajack.CLI/Verbs/DownloadUrl.cs index f29ef59a..d27d03ed 100644 --- a/Wabbajack.CLI/Verbs/DownloadUrl.cs +++ b/Wabbajack.CLI/Verbs/DownloadUrl.cs @@ -23,7 +23,7 @@ namespace Wabbajack.CLI.Verbs { var state = await DownloadDispatcher.Infer(Url); if (state == null) - return CLIUtils.Exit($"Could not find download source for URL {Url}", 1); + return CLIUtils.Exit($"Could not find download source for URL {Url}", ExitCode.Error); DownloadDispatcher.PrepareAll(new []{state}); diff --git a/Wabbajack.CLI/Verbs/Validate.cs b/Wabbajack.CLI/Verbs/Validate.cs index d8d98670..a08e7100 100644 --- a/Wabbajack.CLI/Verbs/Validate.cs +++ b/Wabbajack.CLI/Verbs/Validate.cs @@ -28,7 +28,7 @@ namespace Wabbajack.CLI.Verbs protected override async Task Run() { if (Input != null && !Input.EndsWith(Consts.ModListExtension)) - return CLIUtils.Exit($"The file {Input} does not end with {Consts.ModListExtension}!", -1); + return CLIUtils.Exit($"The file {Input} does not end with {Consts.ModListExtension}!", ExitCode.BadArguments); ModList modlist; @@ -38,12 +38,12 @@ namespace Wabbajack.CLI.Verbs } catch (Exception e) { - return CLIUtils.Exit($"Error while loading the Modlist!\n{e}", 1); + return CLIUtils.Exit($"Error while loading the Modlist!\n{e}", ExitCode.Error); } if (modlist == null) { - return CLIUtils.Exit($"The Modlist could not be loaded!", 1); + return CLIUtils.Exit($"The Modlist could not be loaded!", ExitCode.Error); } @@ -55,7 +55,7 @@ namespace Wabbajack.CLI.Verbs } catch (Exception e) { - return CLIUtils.Exit($"Error during Validation!\n{e}", 1); + return CLIUtils.Exit($"Error during Validation!\n{e}", ExitCode.Error); } return CLIUtils.Exit("The Modlist passed the Validation", 0); From fd66313858ae40c8d2ce436f147cb2733b1465bd Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 15:20:14 +0200 Subject: [PATCH 08/10] Added Extension check for files --- Wabbajack.CLI/CLIUtils.cs | 23 +++++++++++++++++++++-- Wabbajack.CLI/Verbs/Validate.cs | 14 ++------------ Wabbajack.Common/Consts.cs | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Wabbajack.CLI/CLIUtils.cs b/Wabbajack.CLI/CLIUtils.cs index f53844f6..34b462a6 100644 --- a/Wabbajack.CLI/CLIUtils.cs +++ b/Wabbajack.CLI/CLIUtils.cs @@ -30,7 +30,13 @@ namespace Wabbajack.CLI /// /// Validating if the file exists /// - internal class IsFileAttribute : AValidateAttribute { } + internal class IsFileAttribute : AValidateAttribute + { + /// + /// Extension the file should have + /// + public string? Extension { get; set; } + } /// /// Validating if the directory exists @@ -89,8 +95,21 @@ namespace Wabbajack.CLI if (p.HasAttribute(typeof(IsFileAttribute))) { + var fileAttribute = (IsFileAttribute)attribute; isFile = true; - valid = File.Exists(value); + + if (!File.Exists(value)) + valid = false; + else + { + if (!string.IsNullOrWhiteSpace(fileAttribute.Extension)) + { + valid = value.EndsWith(fileAttribute.Extension); + if(!valid) + Exit($"The file {value} does not have the extension {fileAttribute.Extension}!", + ExitCode.BadArguments); + } + } } if (p.HasAttribute(typeof(IsDirectoryAttribute))) diff --git a/Wabbajack.CLI/Verbs/Validate.cs b/Wabbajack.CLI/Verbs/Validate.cs index a08e7100..893892c9 100644 --- a/Wabbajack.CLI/Verbs/Validate.cs +++ b/Wabbajack.CLI/Verbs/Validate.cs @@ -4,32 +4,22 @@ using CommandLine; using Wabbajack.Common; using Wabbajack.Lib; using Wabbajack.Lib.Validation; -using File = Alphaleonis.Win32.Filesystem.File; namespace Wabbajack.CLI.Verbs { [Verb("validate", HelpText = @"Validates a Modlist")] public class Validate : AVerb { - [IsFile(CustomMessage = "The modlist file %1 does not exist!")] + [IsFile(CustomMessage = "The modlist file %1 does not exist!", Extension = Consts.ModListExtension)] [Option('i', "input", Required = true, HelpText = @"Modlist file")] public string? Input { get; set; } /// /// Runs the Validation of a Modlist /// - /// - /// - /// -1 bad Input - /// 0 valid modlist - /// 1 broken modlist - /// - /// + /// protected override async Task Run() { - if (Input != null && !Input.EndsWith(Consts.ModListExtension)) - return CLIUtils.Exit($"The file {Input} does not end with {Consts.ModListExtension}!", ExitCode.BadArguments); - ModList modlist; try diff --git a/Wabbajack.Common/Consts.cs b/Wabbajack.Common/Consts.cs index 085f16a4..39dec636 100644 --- a/Wabbajack.Common/Consts.cs +++ b/Wabbajack.Common/Consts.cs @@ -90,7 +90,7 @@ namespace Wabbajack.Common public static string HashFileExtension => ".xxHash"; public static string MetaFileExtension => ".meta"; - public static string ModListExtension = ".wabbajack"; + public const string ModListExtension = ".wabbajack"; public static string LocalAppDataPath => Path.Combine(KnownFolders.LocalAppData.Path, "Wabbajack"); public static string MetricsKeyHeader => "x-metrics-key"; From 3ae5ef6d835247d13aac3e50c9d025deb588b797 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 19:14:46 +0200 Subject: [PATCH 09/10] Changed return type to ExitCode --- Wabbajack.CLI/CLIUtils.cs | 6 +++--- Wabbajack.CLI/Verbs/AVerb.cs | 5 ++--- Wabbajack.CLI/Verbs/ChangeDownload.cs | 2 +- Wabbajack.CLI/Verbs/Decrypt.cs | 2 +- Wabbajack.CLI/Verbs/DeleteFile.cs | 2 +- Wabbajack.CLI/Verbs/DownloadUrl.cs | 3 +-- Wabbajack.CLI/Verbs/Encrypt.cs | 2 +- Wabbajack.CLI/Verbs/MyFiles.cs | 2 +- Wabbajack.CLI/Verbs/ServerLog.cs | 3 +-- Wabbajack.CLI/Verbs/UpdateModlists.cs | 5 ++--- Wabbajack.CLI/Verbs/UpdateNexusCache.cs | 6 ++---- Wabbajack.CLI/Verbs/Validate.cs | 2 +- 12 files changed, 17 insertions(+), 23 deletions(-) diff --git a/Wabbajack.CLI/CLIUtils.cs b/Wabbajack.CLI/CLIUtils.cs index 34b462a6..e5304c1b 100644 --- a/Wabbajack.CLI/CLIUtils.cs +++ b/Wabbajack.CLI/CLIUtils.cs @@ -8,7 +8,7 @@ using Wabbajack.Common; namespace Wabbajack.CLI { - internal enum ExitCode + public enum ExitCode { BadArguments = -1, Ok = 0, @@ -182,10 +182,10 @@ namespace Wabbajack.CLI Console.Write(msg); } - internal static int Exit(string msg, ExitCode code) + internal static ExitCode Exit(string msg, ExitCode code) { Log(msg); - return (int)code; + return code; } internal static void LogException(Exception e, string msg) diff --git a/Wabbajack.CLI/Verbs/AVerb.cs b/Wabbajack.CLI/Verbs/AVerb.cs index 1443ecf7..2d53c746 100644 --- a/Wabbajack.CLI/Verbs/AVerb.cs +++ b/Wabbajack.CLI/Verbs/AVerb.cs @@ -10,10 +10,9 @@ namespace Wabbajack.CLI.Verbs CLIUtils.Exit("The provided arguments are not valid! Check previous messages for more information", ExitCode.BadArguments); - return Run().Result; + return (int)Run().Result; } - protected abstract Task Run(); - + protected abstract Task Run(); } } diff --git a/Wabbajack.CLI/Verbs/ChangeDownload.cs b/Wabbajack.CLI/Verbs/ChangeDownload.cs index aa53fa72..f131f8ac 100644 --- a/Wabbajack.CLI/Verbs/ChangeDownload.cs +++ b/Wabbajack.CLI/Verbs/ChangeDownload.cs @@ -58,7 +58,7 @@ namespace Wabbajack.CLI.Verbs } } - protected override async Task Run() + protected override async Task Run() { if (Modlist != null && (!Modlist.EndsWith(Consts.ModListExtension) && !Modlist.EndsWith("modlist.txt"))) return CLIUtils.Exit($"The file {Modlist} is not a valid modlist file!", ExitCode.BadArguments); diff --git a/Wabbajack.CLI/Verbs/Decrypt.cs b/Wabbajack.CLI/Verbs/Decrypt.cs index 6cf9a82a..81ab193e 100644 --- a/Wabbajack.CLI/Verbs/Decrypt.cs +++ b/Wabbajack.CLI/Verbs/Decrypt.cs @@ -14,7 +14,7 @@ namespace Wabbajack.CLI.Verbs [Option('o', "output", Required = true, HelpText = @"Output file for the decrypted data")] public string? Output { get; set; } - protected override async Task Run() + protected override async Task Run() { File.WriteAllBytes(Output, Utils.FromEncryptedData(Name)); return 0; diff --git a/Wabbajack.CLI/Verbs/DeleteFile.cs b/Wabbajack.CLI/Verbs/DeleteFile.cs index 124e9f94..2b48617e 100644 --- a/Wabbajack.CLI/Verbs/DeleteFile.cs +++ b/Wabbajack.CLI/Verbs/DeleteFile.cs @@ -11,7 +11,7 @@ namespace Wabbajack.CLI.Verbs [Option('n', "name", Required = true, HelpText = @"Full name (as returned by my-files) of the file")] public string? Name { get; set; } - protected override async Task Run() + protected override async Task Run() { Console.WriteLine(await AuthorAPI.DeleteFile(Name)); return 0; diff --git a/Wabbajack.CLI/Verbs/DownloadUrl.cs b/Wabbajack.CLI/Verbs/DownloadUrl.cs index d27d03ed..800aa111 100644 --- a/Wabbajack.CLI/Verbs/DownloadUrl.cs +++ b/Wabbajack.CLI/Verbs/DownloadUrl.cs @@ -1,5 +1,4 @@ using System; -using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; using Alphaleonis.Win32.Filesystem; @@ -19,7 +18,7 @@ namespace Wabbajack.CLI.Verbs [Option('o', "output", Required = true, HelpText = "Output file name")] public string? Output { get; set; } - protected override async Task Run() + protected override async Task Run() { var state = await DownloadDispatcher.Infer(Url); if (state == null) diff --git a/Wabbajack.CLI/Verbs/Encrypt.cs b/Wabbajack.CLI/Verbs/Encrypt.cs index 1e6592da..e77c8b75 100644 --- a/Wabbajack.CLI/Verbs/Encrypt.cs +++ b/Wabbajack.CLI/Verbs/Encrypt.cs @@ -15,7 +15,7 @@ namespace Wabbajack.CLI.Verbs [Option('i', "input", Required = true, HelpText = @"Source data file name")] public string? Input { get; set; } - protected override async Task Run() + protected override async Task Run() { File.ReadAllBytes(Input).ToEcryptedData(Name); return 0; diff --git a/Wabbajack.CLI/Verbs/MyFiles.cs b/Wabbajack.CLI/Verbs/MyFiles.cs index 6dfcb63b..33a9042a 100644 --- a/Wabbajack.CLI/Verbs/MyFiles.cs +++ b/Wabbajack.CLI/Verbs/MyFiles.cs @@ -8,7 +8,7 @@ namespace Wabbajack.CLI.Verbs [Verb("my-files", HelpText = "List files I have uploaded to the CDN (requires Author API key)")] public class MyFiles : AVerb { - protected override async Task Run() + protected override async Task Run() { var files = await AuthorAPI.GetMyFiles(); foreach (var file in files) diff --git a/Wabbajack.CLI/Verbs/ServerLog.cs b/Wabbajack.CLI/Verbs/ServerLog.cs index 1c2ee4f5..a12f4930 100644 --- a/Wabbajack.CLI/Verbs/ServerLog.cs +++ b/Wabbajack.CLI/Verbs/ServerLog.cs @@ -1,5 +1,4 @@ using System; -using System.Net.Http; using System.Threading.Tasks; using CommandLine; using Wabbajack.Lib.FileUploader; @@ -9,7 +8,7 @@ namespace Wabbajack.CLI.Verbs [Verb("server-log", HelpText = @"Get the latest server log entries", Hidden = false)] public class ServerLog : AVerb { - protected override async Task Run() + protected override async Task Run() { Console.WriteLine(await AuthorAPI.GetServerLog()); return 0; diff --git a/Wabbajack.CLI/Verbs/UpdateModlists.cs b/Wabbajack.CLI/Verbs/UpdateModlists.cs index bc1922e5..fe8ff176 100644 --- a/Wabbajack.CLI/Verbs/UpdateModlists.cs +++ b/Wabbajack.CLI/Verbs/UpdateModlists.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using CommandLine; using Wabbajack.Lib.FileUploader; @@ -8,7 +7,7 @@ namespace Wabbajack.CLI.Verbs [Verb("update-server-modlists", HelpText = "Tell the Build server to update curated modlists (Requires Author API key)")] public class UpdateModlists : AVerb { - protected override async Task Run() + protected override async Task Run() { CLIUtils.Log($"Job ID: {await AuthorAPI.UpdateServerModLists()}"); return 0; diff --git a/Wabbajack.CLI/Verbs/UpdateNexusCache.cs b/Wabbajack.CLI/Verbs/UpdateNexusCache.cs index a293ecbb..00628344 100644 --- a/Wabbajack.CLI/Verbs/UpdateNexusCache.cs +++ b/Wabbajack.CLI/Verbs/UpdateNexusCache.cs @@ -1,7 +1,5 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using CommandLine; -using Wabbajack.Common; using Wabbajack.Lib.FileUploader; namespace Wabbajack.CLI.Verbs @@ -9,7 +7,7 @@ namespace Wabbajack.CLI.Verbs [Verb("update-nexus-cache", HelpText = "Tell the build server to update the Nexus cache (requires Author API key)")] public class UpdateNexusCache : AVerb { - protected override async Task Run() + protected override async Task Run() { CLIUtils.Log($"Job ID: {await AuthorAPI.UpdateNexusCache()}"); return 0; diff --git a/Wabbajack.CLI/Verbs/Validate.cs b/Wabbajack.CLI/Verbs/Validate.cs index 893892c9..447b01ea 100644 --- a/Wabbajack.CLI/Verbs/Validate.cs +++ b/Wabbajack.CLI/Verbs/Validate.cs @@ -18,7 +18,7 @@ namespace Wabbajack.CLI.Verbs /// Runs the Validation of a Modlist /// /// - protected override async Task Run() + protected override async Task Run() { ModList modlist; From 400db704ba6985f2dbe671bb657bb4a79824282d Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 6 Apr 2020 20:26:09 +0200 Subject: [PATCH 10/10] Added Changelog verb --- Wabbajack.CLI/OptionsDefinition.cs | 3 +- Wabbajack.CLI/Program.cs | 1 + Wabbajack.CLI/Properties/launchSettings.json | 3 +- Wabbajack.CLI/Verbs/Changelog.cs | 326 ++++++++++++++++++- Wabbajack.CLI/Wabbajack.CLI.csproj | 1 + 5 files changed, 329 insertions(+), 5 deletions(-) diff --git a/Wabbajack.CLI/OptionsDefinition.cs b/Wabbajack.CLI/OptionsDefinition.cs index 31143da8..af2373da 100644 --- a/Wabbajack.CLI/OptionsDefinition.cs +++ b/Wabbajack.CLI/OptionsDefinition.cs @@ -16,7 +16,8 @@ namespace Wabbajack.CLI typeof(ChangeDownload), typeof(ServerLog), typeof(MyFiles), - typeof(DeleteFile) + typeof(DeleteFile), + typeof(Changelog) }; } } diff --git a/Wabbajack.CLI/Program.cs b/Wabbajack.CLI/Program.cs index 17e61ede..d34708b1 100644 --- a/Wabbajack.CLI/Program.cs +++ b/Wabbajack.CLI/Program.cs @@ -19,6 +19,7 @@ namespace Wabbajack.CLI (ServerLog opts) => opts.Execute(), (MyFiles opts) => opts.Execute(), (DeleteFile opts) => opts.Execute(), + (Changelog opts) => opts.Execute(), errs => 1); } } diff --git a/Wabbajack.CLI/Properties/launchSettings.json b/Wabbajack.CLI/Properties/launchSettings.json index ad46cac6..be163303 100644 --- a/Wabbajack.CLI/Properties/launchSettings.json +++ b/Wabbajack.CLI/Properties/launchSettings.json @@ -1,8 +1,7 @@ { "profiles": { "Wabbajack.CLI": { - "commandName": "Project", - "commandLineArgs": "change-download --input=\"S:\\Modding FAST\\Wabbajack\\downloads\" --output=\"S:\\Modding FAST\\test\" --modlist=\"S:\\Modding FAST\\Wabbajack\\profiles\\erri120's Dope Test Modlist\\modlist.txt\"" + "commandName": "Project" } } } \ No newline at end of file diff --git a/Wabbajack.CLI/Verbs/Changelog.cs b/Wabbajack.CLI/Verbs/Changelog.cs index 794e92ee..79a47e29 100644 --- a/Wabbajack.CLI/Verbs/Changelog.cs +++ b/Wabbajack.CLI/Verbs/Changelog.cs @@ -1,24 +1,346 @@ using System; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; +using Alphaleonis.Win32.Filesystem; using CommandLine; +using Markdig; +using Markdig.Syntax; +using Wabbajack.Common; +using Wabbajack.Lib; +using Wabbajack.Lib.Downloaders; namespace Wabbajack.CLI.Verbs { [Verb("changelog", HelpText = "Generate a changelog using two different versions of the same Modlist.")] public class Changelog : AVerb { + [IsFile(CustomMessage = "Modlist %1 does not exist!", Extension = Consts.ModListExtension)] [Option("original", Required = true, HelpText = "The original/previous modlist")] public string? Original { get; set; } + [IsFile(CustomMessage = "Modlist %1 does not exist!", Extension = Consts.ModListExtension)] [Option("update", Required = true, HelpText = "The current/updated modlist")] public string? Update { get; set; } [Option('o', "output", Required = false, HelpText = "The output file")] public string? Output { get; set; } + + [Option("changes-downloads", Required = false, Default = true, HelpText = "Include download changes")] + public bool IncludeDownloadChanges { get; set; } + + [Option("changes-mods", Required = false, Default = false, HelpText = "Include mods changes")] + public bool IncludeModChanges { get; set; } + + [Option("changes-loadorder", Required = false, Default = false, HelpText = "Include load order changes")] + public bool IncludeLoadOrderChanges { get; set; } - protected override Task Run() + protected override async Task Run() { - throw new NotImplementedException(); + if (Original == null) + return ExitCode.BadArguments; + if (Update == null) + return ExitCode.BadArguments; + + ModList original, update; + + try + { + original = AInstaller.LoadFromFile(Original); + } + catch (Exception e) + { + return CLIUtils.Exit($"Error while loading the original Modlist from {Original}!\n{e}", ExitCode.Error); + } + + if(original == null) + return CLIUtils.Exit($"The Modlist from {Original} could not be loaded!", ExitCode.Error); + + try + { + update = AInstaller.LoadFromFile(Update); + } + catch (Exception e) + { + return CLIUtils.Exit($"Error while loading the updated Modlist from {Update}!\n{e}", ExitCode.Error); + } + + if(update == null) + return CLIUtils.Exit($"The Modlist from {Update} could not be loaded!", ExitCode.Error); + + var downloadSizeChanges = original.DownloadSize - update.DownloadSize; + var installSizeChanges = original.InstallSize - update.InstallSize; + + var versionRegex = new Regex(@"\s([0-9](\.|\s)?){1,4}"); + var matchOriginal = versionRegex.Match(original.Name); + var matchUpdated = versionRegex.Match(update.Name); + + if (!matchOriginal.Success || !matchUpdated.Success) + { + return CLIUtils.Exit( + !matchOriginal.Success + ? "The name of the original modlist did not match the version check regex!" + : "The name of the updated modlist did not match the version check regex!", ExitCode.Error); + } + + var version = matchUpdated.Value.Trim(); + + var mdText = + $"## {version}\n\n" + + $"**Build at:** `{File.GetCreationTime(Update)}`\n\n" + + "**Info**:\n\n" + + $"- Download Size change: {downloadSizeChanges.ToFileSizeString()} (Total: {update.DownloadSize.ToFileSizeString()})\n" + + $"- Install Size change: {installSizeChanges.ToFileSizeString()} (Total: {update.InstallSize.ToFileSizeString()})\n\n"; + + if (IncludeDownloadChanges) + { + var updatedArchives = update.Archives + .Where(a => original.Archives.All(x => x.Name != a.Name)) + .Where(a => + { + if (!(a.State is NexusDownloader.State nexusState)) + return false; + + return original.Archives.Any(x => + { + if (!(x.State is NexusDownloader.State originalState)) + return false; + + if (nexusState.Name != originalState.Name) + return false; + + if (nexusState.ModID != originalState.ModID) + return false; + + if (!int.TryParse(nexusState.FileID, out var currentFileID)) + return true; + + if (int.TryParse(originalState.FileID, out var originalFileID)) + { + return currentFileID > originalFileID; + } + + return true; + }); + }).ToList(); + + var newArchives = update.Archives + .Where(a => original.Archives.All(x => x.Name != a.Name)) + .Where(a => updatedArchives.All(x => x != a)) + .ToList(); + + var removedArchives = original.Archives + .Where(a => update.Archives.All(x => x.Name != a.Name)) + .Where(a => updatedArchives.All(x => x != a)) + .ToList(); + + if(newArchives.Any() || removedArchives.Any()) + mdText += "**Download Changes**:\n\n"; + + updatedArchives.Do(a => + { + mdText += $"- Updated [{GetModName(a)}]({a.State.GetManifestURL(a)})\n"; + }); + + removedArchives.Do(a => + { + mdText += $"- Removed [{GetModName(a)}]({a.State.GetManifestURL(a)})\n"; + }); + + newArchives.Do(a => + { + mdText += $"- Added [{GetModName(a)}]({a.State.GetManifestURL(a)})\n"; + }); + + mdText += "\n"; + } + + if (IncludeLoadOrderChanges) + { + var originalLoadOrderFile = original.Directives + .Where(d => d is InlineFile) + .Where(d => d.To.EndsWith("loadorder.txt")) + .Cast() + .First(); + + var updatedLoadOrderFile = update.Directives + .Where(d => d is InlineFile) + .Where(d => d.To.EndsWith("loadorder.txt")) + .Cast() + .First(); + + var originalLoadOrder = GetTextFileFromModlist(Original, original, originalLoadOrderFile.SourceDataID).Split("\n"); + var updatedLoadOrder = GetTextFileFromModlist(Update, update, updatedLoadOrderFile.SourceDataID).Split("\n"); + + var addedPlugins = updatedLoadOrder + .Where(p => originalLoadOrder.All(x => p != x)) + .ToList(); + + var removedPlugins = originalLoadOrder + .Where(p => updatedLoadOrder.All(x => p != x)) + .ToList(); + + if(addedPlugins.Any() || removedPlugins.Any()) + mdText += "**Load Order Changes**:\n\n"; + + addedPlugins.Do(p => + { + mdText += $"- Added {p}\n"; + }); + + removedPlugins.Do(p => + { + mdText += $"- Removed {p}\n"; + }); + + mdText += "\n"; + } + + if (IncludeModChanges) + { + var originalModlistFile = original.Directives + .Where(d => d is InlineFile) + .Where(d => d.To.EndsWith("modlist.txt")) + .Cast() + .First(); + + var updatedModlistFile = update.Directives + .Where(d => d is InlineFile) + .Where(d => d.To.EndsWith("modlist.txt")) + .Cast() + .First(); + + var originalModlist = GetTextFileFromModlist(Original, original, originalModlistFile.SourceDataID).Split("\n"); + var updatedModlist = GetTextFileFromModlist(Update, update, updatedModlistFile.SourceDataID).Split("\n"); + + var removedMods = originalModlist + .Where(m => m.StartsWith("+")) + .Where(m => updatedModlist.All(x => m != x)) + .Select(m => m.Substring(1)) + .ToList(); + + var addedMods = updatedModlist + .Where(m => m.StartsWith("+")) + .Where(m => originalModlist.All(x => m != x)) + .Select(m => m.Substring(1)) + .ToList(); + + if (removedMods.Any() || addedMods.Any()) + mdText += "**Mod Changes**:\n\n"; + + addedMods.Do(m => + { + mdText += $"- Added {m}\n"; + }); + + removedMods.Do(m => + { + mdText += $"- Removed {m}\n"; + }); + } + + var output = string.IsNullOrWhiteSpace(Output) + ? "changelog.md" + : Output; + + if (File.Exists(output) && output.EndsWith("md")) + { + CLIUtils.Log($"Output file {output} already exists and is a markdown file. It will be updated with the newest version"); + + var markdown = File.ReadAllLines(output).ToList(); + var lines = mdText.Split("\n"); + + if (lines.All(l => markdown.Contains(l))) + { + return CLIUtils.Exit("The output file is already up-to-date", ExitCode.Ok); + } + + var doc = Markdown.Parse(File.ReadAllText(output)); + + var hasToc = false; + var tocLine = 0; + + var headers = doc + .Where(b => b is HeadingBlock) + .Cast() + .ToList(); + + if (headers.Count < 2) + { + return CLIUtils.Exit("The provided output file has less than 2 headers!", ExitCode.Error); + } + + if (headers[0].Level == 1 && headers[1].Level == 2) + { + if (headers[1].Line - headers[0].Line > headers.Count - 1) + { + var listBlocks = doc + .Where(b => b.Line > headers[0].Line && b.Line < headers[1].Line) + .OfType() + .ToList(); + + if (listBlocks.Count == 1) + { + hasToc = true; + tocLine = listBlocks[0].Line; + + CLIUtils.Log($"Toc found at {tocLine}"); + } + } + } + + var firstHeader = headers + .First(h => h.Level >= 2); + + var line = firstHeader.Line-1; + + if (hasToc) + { + markdown.Insert(tocLine, $"- [{version}](#{ToTocLink(version)})"); + line++; + } + + markdown.InsertRange(line+1, lines); + + File.WriteAllLines(output, markdown); + CLIUtils.Log($"Wrote {markdown.Count} lines to {output}"); + + return ExitCode.Ok; + } + + var text = "# Changelog\n\n" + + $"- [{version}](#{ToTocLink(version)})\n\n" + + $"{mdText}"; + + File.WriteAllText(output, text); + CLIUtils.Log($"Wrote changelog to {output}"); + + return ExitCode.Ok; + } + + private static string GetTextFileFromModlist(string archive, ModList modlist, string sourceID) + { + var installer = new MO2Installer(archive, modlist, "", "", null); + byte[] bytes = installer.LoadBytesFromPath(sourceID); + return Encoding.Default.GetString(bytes); + } + + private static string ToTocLink(string header) + { + return header.Trim().Replace(" ", "").Replace(".", ""); + } + + private static string GetModName(Archive a) + { + var result = a.Name; + + if (a.State is IMetaState metaState) + { + result = metaState.Name; + } + + return result; } } } diff --git a/Wabbajack.CLI/Wabbajack.CLI.csproj b/Wabbajack.CLI/Wabbajack.CLI.csproj index ed51c614..e3225e63 100644 --- a/Wabbajack.CLI/Wabbajack.CLI.csproj +++ b/Wabbajack.CLI/Wabbajack.CLI.csproj @@ -18,6 +18,7 @@ +