using System; using System.Linq; using System.Reflection; using Alphaleonis.Win32.Filesystem; using CommandLine; using Wabbajack.CLI.Verbs; using Wabbajack.Common; namespace Wabbajack.CLI { public enum ExitCode { BadArguments = -1, Ok = 0, Error = 1 } /// /// Abstract class to mark attributes which need validating /// [AttributeUsage(AttributeTargets.Property)] internal abstract class AValidateAttribute : 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 { /// /// Extension the file should have /// public string? Extension { get; set; } } /// /// 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)) && p.HasAttribute(typeof(AValidateAttribute)); 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; var attribute = (AValidateAttribute)p.GetAttribute(typeof(AValidateAttribute)); var isFile = false; if (p.HasAttribute(typeof(IsFileAttribute))) { var fileAttribute = (IsFileAttribute)attribute; isFile = true; 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))) { 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); var optionAttribute = (OptionAttribute)p.GetAttribute(typeof(OptionAttribute)); if (optionAttribute.Required) Exit(message, ExitCode.BadArguments); 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); return attributes.Count() == 1; } internal static void Log(string msg, bool newLine = true) { //TODO: maybe also write to a log file? if(newLine) Console.WriteLine(msg); else Console.Write(msg); Console.Out.Flush(); } internal static ExitCode Exit(string msg, ExitCode code) { Log(msg); return code; } internal static void LogException(Exception e, string msg) { Console.WriteLine($"{msg}\n{e}"); } } }