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);
}
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}");
}
}
}