2020-02-24 16:50:58 +00:00
|
|
|
|
using System;
|
2020-04-06 12:22:07 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using Alphaleonis.Win32.Filesystem;
|
|
|
|
|
using CommandLine;
|
2020-04-06 12:04:40 +00:00
|
|
|
|
using Wabbajack.CLI.Verbs;
|
2020-04-06 12:22:07 +00:00
|
|
|
|
using Wabbajack.Common;
|
2020-02-24 16:50:58 +00:00
|
|
|
|
|
|
|
|
|
namespace Wabbajack.CLI
|
|
|
|
|
{
|
2020-04-06 17:14:46 +00:00
|
|
|
|
public enum ExitCode
|
2020-04-06 13:09:17 +00:00
|
|
|
|
{
|
|
|
|
|
BadArguments = -1,
|
|
|
|
|
Ok = 0,
|
|
|
|
|
Error = 1
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-06 12:57:37 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Abstract class to mark attributes which need validating
|
|
|
|
|
/// </summary>
|
2020-04-06 12:04:40 +00:00
|
|
|
|
[AttributeUsage(AttributeTargets.Property)]
|
2020-04-06 12:57:37 +00:00
|
|
|
|
internal abstract class AValidateAttribute : Attribute
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Custom message if validation failed. Use placeholder %1 to insert the value
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string? CustomMessage { get; set; }
|
|
|
|
|
}
|
2020-04-06 12:04:40 +00:00
|
|
|
|
|
2020-04-06 12:57:37 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Validating if the file exists
|
|
|
|
|
/// </summary>
|
2020-04-06 13:20:14 +00:00
|
|
|
|
internal class IsFileAttribute : AValidateAttribute
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Extension the file should have
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string? Extension { get; set; }
|
|
|
|
|
}
|
2020-04-06 12:57:37 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Validating if the directory exists
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class IsDirectoryAttribute : AValidateAttribute
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Create the directory if it does not exists
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool Create { get; set; }
|
|
|
|
|
}
|
2020-04-06 12:04:40 +00:00
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
internal static class CLIUtils
|
|
|
|
|
{
|
2020-04-06 12:57:37 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Validates all Attributes of type <see cref="AValidateAttribute"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="verb">The verb to validate</param>
|
|
|
|
|
/// <returns></returns>
|
2020-04-06 12:22:07 +00:00
|
|
|
|
internal static bool HasValidArguments(AVerb verb)
|
2020-04-06 12:04:40 +00:00
|
|
|
|
{
|
2020-04-06 12:22:07 +00:00
|
|
|
|
var props = verb.GetType().GetProperties().Where(p =>
|
|
|
|
|
{
|
2020-04-06 12:57:37 +00:00
|
|
|
|
var hasAttr = p.HasAttribute(typeof(OptionAttribute))
|
|
|
|
|
&& p.HasAttribute(typeof(AValidateAttribute));
|
2020-04-06 12:22:07 +00:00
|
|
|
|
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;
|
2020-04-06 12:57:37 +00:00
|
|
|
|
return !string.IsNullOrWhiteSpace(stringValue);
|
2020-04-06 12:22:07 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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;
|
2020-04-06 12:57:37 +00:00
|
|
|
|
var attribute = (AValidateAttribute)p.GetAttribute(typeof(AValidateAttribute));
|
|
|
|
|
var isFile = false;
|
2020-04-06 12:22:07 +00:00
|
|
|
|
|
2020-04-06 12:23:36 +00:00
|
|
|
|
if (p.HasAttribute(typeof(IsFileAttribute)))
|
2020-04-06 12:22:07 +00:00
|
|
|
|
{
|
2020-04-06 13:20:14 +00:00
|
|
|
|
var fileAttribute = (IsFileAttribute)attribute;
|
2020-04-06 12:57:37 +00:00
|
|
|
|
isFile = true;
|
2020-04-06 13:20:14 +00:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-06 12:22:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-06 12:23:36 +00:00
|
|
|
|
if (p.HasAttribute(typeof(IsDirectoryAttribute)))
|
2020-04-06 12:22:07 +00:00
|
|
|
|
{
|
2020-04-06 12:57:37 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2020-04-06 12:22:07 +00:00
|
|
|
|
}
|
2020-04-06 12:57:37 +00:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2020-04-06 12:59:38 +00:00
|
|
|
|
var optionAttribute = (OptionAttribute)p.GetAttribute(typeof(OptionAttribute));
|
|
|
|
|
|
|
|
|
|
if (optionAttribute.Required)
|
2020-04-06 13:09:17 +00:00
|
|
|
|
Exit(message, ExitCode.BadArguments);
|
2020-04-06 12:57:37 +00:00
|
|
|
|
else
|
|
|
|
|
Log(message);
|
2020-04-06 12:22:07 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return valid;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-06 12:57:37 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets an attribute of a specific type
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="member"></param>
|
|
|
|
|
/// <param name="attribute"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
internal static Attribute GetAttribute(this MemberInfo member, Type attribute)
|
|
|
|
|
{
|
|
|
|
|
var attributes = member.GetCustomAttributes(attribute);
|
|
|
|
|
return attributes.ElementAt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks if a <see cref="MemberInfo"/> has a custom attribute
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="member"></param>
|
|
|
|
|
/// <param name="attribute"></param>
|
|
|
|
|
/// <returns></returns>
|
2020-04-06 12:22:07 +00:00
|
|
|
|
internal static bool HasAttribute(this MemberInfo member, Type attribute)
|
|
|
|
|
{
|
|
|
|
|
var attributes = member.GetCustomAttributes(attribute);
|
|
|
|
|
return attributes.Count() == 1;
|
2020-04-06 12:04:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
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);
|
2020-07-13 03:50:12 +00:00
|
|
|
|
Console.Out.Flush();
|
2020-02-24 16:50:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-06 17:14:46 +00:00
|
|
|
|
internal static ExitCode Exit(string msg, ExitCode code)
|
2020-02-24 17:05:42 +00:00
|
|
|
|
{
|
|
|
|
|
Log(msg);
|
2020-04-06 17:14:46 +00:00
|
|
|
|
return code;
|
2020-02-24 17:05:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
internal static void LogException(Exception e, string msg)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"{msg}\n{e}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|