fork from 1.3
This commit is contained in:
13
FreeSR.Shared/Command/CommandAttribute.cs
Normal file
13
FreeSR.Shared/Command/CommandAttribute.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class CommandAttribute : Attribute
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public CommandAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
FreeSR.Shared/Command/CommandCategory.cs
Normal file
46
FreeSR.Shared/Command/CommandCategory.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
using FreeSR.Shared.Command.Context;
|
||||
using System.Reflection;
|
||||
|
||||
public class CommandCategory : ICommandCategory
|
||||
{
|
||||
private readonly Dictionary<string, CommandHandler> handlers = new Dictionary<string, CommandHandler>();
|
||||
private readonly Dictionary<string, ICommandCategory> categories = new Dictionary<string, ICommandCategory>();
|
||||
|
||||
public void Build()
|
||||
{
|
||||
Type type = GetType();
|
||||
foreach (MethodInfo info in type.GetMethods())
|
||||
{
|
||||
CommandAttribute attribute = info.GetCustomAttribute<CommandAttribute>();
|
||||
if (attribute == null)
|
||||
continue;
|
||||
|
||||
var handler = new CommandHandler(type, info);
|
||||
handlers.Add(attribute.Name, handler);
|
||||
}
|
||||
|
||||
foreach (Type info in type.GetNestedTypes())
|
||||
{
|
||||
CommandAttribute attribute = info.GetCustomAttribute<CommandAttribute>();
|
||||
if (attribute == null)
|
||||
continue;
|
||||
|
||||
CommandCategory category = (CommandCategory)Activator.CreateInstance(info);
|
||||
category.Build();
|
||||
categories.Add(attribute.Name, category);
|
||||
}
|
||||
}
|
||||
|
||||
public CommandResult Invoke(ICommandContext context, string[] parameters, uint depth)
|
||||
{
|
||||
if (handlers.TryGetValue(parameters[depth], out CommandHandler handler))
|
||||
return handler.Invoke(this, context, parameters, depth + 1);
|
||||
if (categories.TryGetValue(parameters[depth], out ICommandCategory category))
|
||||
return category.Invoke(context, parameters, depth + 1);
|
||||
|
||||
return CommandResult.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
FreeSR.Shared/Command/CommandException.cs
Normal file
10
FreeSR.Shared/Command/CommandException.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
public class CommandException : Exception
|
||||
{
|
||||
public CommandException(string message) : base(message)
|
||||
{
|
||||
// CommandException.
|
||||
}
|
||||
}
|
||||
}
|
||||
68
FreeSR.Shared/Command/CommandHandler.cs
Normal file
68
FreeSR.Shared/Command/CommandHandler.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
using FreeSR.Shared.Command.Context;
|
||||
using FreeSR.Shared.Command.Convert;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
public class CommandHandler
|
||||
{
|
||||
private Delegate _handlerDelegate;
|
||||
private readonly List<ICommandParameterConverter> _additionalParameterConverters = new List<ICommandParameterConverter>();
|
||||
|
||||
public CommandHandler(Type type, MethodInfo method)
|
||||
{
|
||||
InitializeDelegate(type, method);
|
||||
InitializeParameters(method);
|
||||
}
|
||||
|
||||
private void InitializeDelegate(Type type, MethodInfo method)
|
||||
{
|
||||
ParameterInfo[] info = method.GetParameters();
|
||||
if (info.Length < 1 || !typeof(ICommandContext).IsAssignableFrom(info[0].ParameterType))
|
||||
throw new CommandException("");
|
||||
|
||||
ParameterExpression instance = Expression.Parameter(type);
|
||||
|
||||
var parameters = new List<ParameterExpression> { instance };
|
||||
parameters.AddRange(info.Select(p => Expression.Parameter(p.ParameterType)));
|
||||
|
||||
MethodCallExpression call = Expression.Call(instance, method, parameters.Skip(1));
|
||||
LambdaExpression lambda = Expression.Lambda(call, parameters);
|
||||
_handlerDelegate = lambda.Compile();
|
||||
}
|
||||
|
||||
private void InitializeParameters(MethodInfo method)
|
||||
{
|
||||
foreach (Type parameterType in method.GetParameters()
|
||||
.Skip(1)
|
||||
.Select(p => p.ParameterType))
|
||||
{
|
||||
ICommandParameterConverter converter = CommandManager.Instance.GetConverter(parameterType);
|
||||
if (converter == null)
|
||||
throw new CommandException("");
|
||||
|
||||
_additionalParameterConverters.Add(converter);
|
||||
}
|
||||
}
|
||||
|
||||
public CommandResult Invoke(ICommandCategory category, ICommandContext context, string[] parameters, uint depth)
|
||||
{
|
||||
var additionalParameterCount = parameters.Length - depth;
|
||||
if (additionalParameterCount < _additionalParameterConverters.Count)
|
||||
return CommandResult.Parameter;
|
||||
|
||||
var parameterObjects = new List<object> { category, context };
|
||||
for (int i = 0; i < _additionalParameterConverters.Count; i++)
|
||||
{
|
||||
if (!_additionalParameterConverters[i].TryConvert(parameters[depth + i], out object result))
|
||||
return CommandResult.Parameter;
|
||||
|
||||
parameterObjects.Add(result);
|
||||
}
|
||||
|
||||
_handlerDelegate.DynamicInvoke(parameterObjects.ToArray());
|
||||
return CommandResult.Ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
FreeSR.Shared/Command/CommandManager.cs
Normal file
93
FreeSR.Shared/Command/CommandManager.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
using System.Reflection;
|
||||
using FreeSR.Shared.Command.Context;
|
||||
using FreeSR.Shared.Command.Convert;
|
||||
using NLog;
|
||||
|
||||
public sealed class CommandManager : Singleton<CommandManager>
|
||||
{
|
||||
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly Dictionary<Type, ICommandParameterConverter> converters = new Dictionary<Type, ICommandParameterConverter>();
|
||||
private readonly Dictionary<string, CommandCategory> categories = new Dictionary<string, CommandCategory>();
|
||||
|
||||
private CommandManager()
|
||||
{
|
||||
// CommandManager.
|
||||
}
|
||||
|
||||
public void Initialize(params Type[] categories)
|
||||
{
|
||||
InitializeConverters();
|
||||
InitializeCategories(categories);
|
||||
}
|
||||
|
||||
private void InitializeConverters()
|
||||
{
|
||||
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(t => typeof(ICommandParameterConverter).IsAssignableFrom(t)))
|
||||
{
|
||||
CommandParameterConverterAttribute attribute = type.GetCustomAttribute<CommandParameterConverterAttribute>();
|
||||
if (attribute == null)
|
||||
continue;
|
||||
|
||||
ICommandParameterConverter converter = (ICommandParameterConverter)Activator.CreateInstance(type);
|
||||
converters.Add(attribute.Type, converter);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeCategories(params Type[] types)
|
||||
{
|
||||
foreach (Type type in types)
|
||||
{
|
||||
CommandAttribute attribute = type.GetCustomAttribute<CommandAttribute>();
|
||||
if (attribute == null)
|
||||
continue;
|
||||
|
||||
CommandCategory category = (CommandCategory)Activator.CreateInstance(type);
|
||||
category.Build();
|
||||
|
||||
categories.Add(attribute.Name, category);
|
||||
}
|
||||
}
|
||||
|
||||
public ICommandParameterConverter GetConverter(Type type)
|
||||
{
|
||||
return converters.TryGetValue(type, out ICommandParameterConverter converter) ? converter : null;
|
||||
}
|
||||
|
||||
public void Invoke(string command)
|
||||
{
|
||||
var context = new ConsoleCommandContext();
|
||||
Invoke(context, command);
|
||||
}
|
||||
|
||||
public void Invoke(ICommandContext context, string command)
|
||||
{
|
||||
CommandResult result = InvokeResult(context, command);
|
||||
if (result != CommandResult.Ok)
|
||||
context.SendError(GetError());
|
||||
|
||||
string GetError()
|
||||
{
|
||||
return result switch
|
||||
{
|
||||
CommandResult.Invalid => "No valid command was found.",
|
||||
CommandResult.Parameter => "Invalid parameters were provided for the command.",
|
||||
CommandResult.Permission => "You don't have permission to invoke the command.",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private CommandResult InvokeResult(ICommandContext context, string command)
|
||||
{
|
||||
string[] parameters = command.Split(' ');
|
||||
if (categories.TryGetValue(parameters[0], out CommandCategory category))
|
||||
return category.Invoke(context, parameters, 1);
|
||||
|
||||
return CommandResult.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
FreeSR.Shared/Command/CommandResult.cs
Normal file
10
FreeSR.Shared/Command/CommandResult.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
public enum CommandResult
|
||||
{
|
||||
Ok,
|
||||
Invalid,
|
||||
Permission,
|
||||
Parameter
|
||||
}
|
||||
}
|
||||
19
FreeSR.Shared/Command/Context/ConsoleCommandContext.cs
Normal file
19
FreeSR.Shared/Command/Context/ConsoleCommandContext.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace FreeSR.Shared.Command.Context
|
||||
{
|
||||
using NLog;
|
||||
|
||||
public class ConsoleCommandContext : ICommandContext
|
||||
{
|
||||
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void SendMessage(string message)
|
||||
{
|
||||
s_log.Info(message);
|
||||
}
|
||||
|
||||
public void SendError(string message)
|
||||
{
|
||||
s_log.Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
FreeSR.Shared/Command/Context/ICommandContext.cs
Normal file
8
FreeSR.Shared/Command/Context/ICommandContext.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FreeSR.Shared.Command.Context
|
||||
{
|
||||
public interface ICommandContext
|
||||
{
|
||||
void SendMessage(string message);
|
||||
void SendError(string message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace FreeSR.Shared.Command.Convert
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class CommandParameterConverterAttribute : Attribute
|
||||
{
|
||||
public Type Type { get; }
|
||||
|
||||
public CommandParameterConverterAttribute(Type type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FreeSR.Shared.Command.Convert
|
||||
{
|
||||
public interface ICommandParameterConverter
|
||||
{
|
||||
bool TryConvert(string parameter, out object result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace FreeSR.Shared.Command.Convert
|
||||
{
|
||||
[CommandParameterConverter(typeof(string))]
|
||||
public class StringCommandParameterConverter : ICommandParameterConverter
|
||||
{
|
||||
public bool TryConvert(string parameter, out object result)
|
||||
{
|
||||
result = parameter;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace FreeSR.Shared.Command.Convert
|
||||
{
|
||||
[CommandParameterConverter(typeof(uint))]
|
||||
public class UIntCommandParameterConverter : ICommandParameterConverter
|
||||
{
|
||||
public bool TryConvert(string parameter, out object result)
|
||||
{
|
||||
result = null;
|
||||
if (!uint.TryParse(parameter, out uint parseResult))
|
||||
return false;
|
||||
|
||||
result = parseResult;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
FreeSR.Shared/Command/ICommandCategory.cs
Normal file
9
FreeSR.Shared/Command/ICommandCategory.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
using FreeSR.Shared.Command.Context;
|
||||
|
||||
public interface ICommandCategory
|
||||
{
|
||||
CommandResult Invoke(ICommandContext context, string[] parameters, uint depth);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user