diff --git a/Application/Factories/ScriptCommandFactory.cs b/Application/Factories/ScriptCommandFactory.cs index a3453a906..8b6091444 100644 --- a/Application/Factories/ScriptCommandFactory.cs +++ b/Application/Factories/ScriptCommandFactory.cs @@ -25,7 +25,7 @@ namespace IW4MAdmin.Application.Factories } /// - public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, IEnumerable<(string, bool)> args, Action executeAction) + public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, bool isTargetRequired, IEnumerable<(string, bool)> args, Action executeAction) { var permissionEnum = Enum.Parse(permission); var argsArray = args.Select(_arg => new CommandArgument @@ -34,7 +34,7 @@ namespace IW4MAdmin.Application.Factories Required = _arg.Item2 }).ToArray(); - return new ScriptCommand(name, alias, description, permissionEnum, argsArray, executeAction, _config, _transLookup); + return new ScriptCommand(name, alias, description, isTargetRequired, permissionEnum, argsArray, executeAction, _config, _transLookup); } } } diff --git a/Application/Misc/ScriptCommand.cs b/Application/Misc/ScriptCommand.cs index fdf4785de..6f181bba9 100644 --- a/Application/Misc/ScriptCommand.cs +++ b/Application/Misc/ScriptCommand.cs @@ -15,7 +15,7 @@ namespace IW4MAdmin.Application.Misc { private readonly Action _executeAction; - public ScriptCommand(string name, string alias, string description, Permission permission, + public ScriptCommand(string name, string alias, string description, bool isTargetRequired, Permission permission, CommandArgument[] args, Action executeAction, CommandConfiguration config, ITranslationLookup layout) : base(config, layout) { @@ -24,6 +24,7 @@ namespace IW4MAdmin.Application.Misc Name = name; Alias = alias; Description = description; + RequiresTarget = isTargetRequired; Permission = permission; Arguments = args; } diff --git a/Application/Misc/ScriptPlugin.cs b/Application/Misc/ScriptPlugin.cs index 4a692d04f..c8dd97da0 100644 --- a/Application/Misc/ScriptPlugin.cs +++ b/Application/Misc/ScriptPlugin.cs @@ -252,6 +252,7 @@ namespace IW4MAdmin.Application.Misc string alias = dynamicCommand.alias; string description = dynamicCommand.description; string permission = dynamicCommand.permission; + bool targetRequired = false; List<(string, bool)> args = new List<(string, bool)>(); dynamic arguments = null; @@ -266,6 +267,16 @@ namespace IW4MAdmin.Application.Misc // arguments are optional } + try + { + targetRequired = dynamicCommand.targetRequired; + } + + catch (RuntimeBinderException) + { + // arguments are optional + } + if (arguments != null) { foreach (var arg in dynamicCommand.arguments) @@ -290,7 +301,7 @@ namespace IW4MAdmin.Application.Misc } } - commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, args, execute)); + commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, targetRequired, args, execute)); } return commandList; diff --git a/Application/Misc/ScriptPluginServiceResolver.cs b/Application/Misc/ScriptPluginServiceResolver.cs index f8f308ac7..552bbe532 100644 --- a/Application/Misc/ScriptPluginServiceResolver.cs +++ b/Application/Misc/ScriptPluginServiceResolver.cs @@ -18,14 +18,31 @@ namespace IW4MAdmin.Application.Misc public object ResolveService(string serviceName) { - var serviceType = typeof(IScriptPluginServiceResolver).Assembly.GetTypes().FirstOrDefault(_type => _type.Name == serviceName); + var serviceType = DetermineRootType(serviceName); + return _serviceProvider.GetService(serviceType); + } + + public object ResolveService(string serviceName, string[] genericParameters) + { + var serviceType = DetermineRootType(serviceName, genericParameters.Length); + var genericTypes = genericParameters.Select(_genericTypeParam => DetermineRootType(_genericTypeParam)); + var resolvedServiceType = serviceType.MakeGenericType(genericTypes.ToArray()); + return _serviceProvider.GetService(resolvedServiceType); + } + + private Type DetermineRootType(string serviceName, int genericParamCount = 0) + { + var typeCollection = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(t => t.GetTypes()); + string generatedName = $"{serviceName}{(genericParamCount == 0 ? "" : $"`{genericParamCount}")}".ToLower(); + var serviceType = typeCollection.FirstOrDefault(_type => _type.Name.ToLower() == generatedName); if (serviceType == null) { - throw new InvalidOperationException($"No service type '{serviceName}' defined in IW4MAdmin assembly"); + throw new InvalidOperationException($"No object type '{serviceName}' defined in loaded assemblies"); } - return _serviceProvider.GetService(serviceType); + return serviceType; } } } diff --git a/Plugins/ScriptPlugins/SampleScriptPluginCommand.js b/Plugins/ScriptPlugins/SampleScriptPluginCommand.js index b80724dad..b02f92382 100644 --- a/Plugins/ScriptPlugins/SampleScriptPluginCommand.js +++ b/Plugins/ScriptPlugins/SampleScriptPluginCommand.js @@ -7,6 +7,8 @@ let commands = [{ alias: "pp", // required permission: "User", + // optional (defaults to false) + targetRequired: false, // optional arguments: [{ name: "times to ping", diff --git a/SharedLibraryCore/Interfaces/IScriptCommandFactory.cs b/SharedLibraryCore/Interfaces/IScriptCommandFactory.cs index b59d0876d..f6c3cf994 100644 --- a/SharedLibraryCore/Interfaces/IScriptCommandFactory.cs +++ b/SharedLibraryCore/Interfaces/IScriptCommandFactory.cs @@ -15,9 +15,10 @@ namespace SharedLibraryCore.Interfaces /// alias of command /// description of command /// minimum required permission + /// target required or not /// command arguments (name, is required) /// action to peform when commmand is executed /// - IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, IEnumerable<(string, bool)> args, Action executeAction); + IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, bool isTargetRequired, IEnumerable<(string, bool)> args, Action executeAction); } } diff --git a/SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs b/SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs index 08883074d..4a977703c 100644 --- a/SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs +++ b/SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs @@ -1,10 +1,25 @@ -namespace SharedLibraryCore.Interfaces +using System.Collections.Generic; + +namespace SharedLibraryCore.Interfaces { /// /// interface used to dynamically resolve services by string name /// public interface IScriptPluginServiceResolver { + /// + /// resolves a service with the given name + /// + /// class name of service + /// object ResolveService(string serviceName); + + /// + /// resolves a service with the given name and generic params + /// + /// class name of service + /// generic class names + /// + object ResolveService(string serviceName, string[] genericParameters); } } diff --git a/Tests/ApplicationTests/Mocks/ScriptResolverGenericMock.cs b/Tests/ApplicationTests/Mocks/ScriptResolverGenericMock.cs new file mode 100644 index 000000000..677bae215 --- /dev/null +++ b/Tests/ApplicationTests/Mocks/ScriptResolverGenericMock.cs @@ -0,0 +1,23 @@ +namespace ApplicationTests.Mocks +{ + public interface IScriptResolverMock + { + string Value { get; set; } + } + + public class ScriptResolverMock : IScriptResolverMock + { + public string Value { get; set; } + } + public interface IScriptResolverGenericMock + { + T Value { get; set; } + V Value2 { get; set; } + } + + public class ScriptResolverGenericMock : IScriptResolverGenericMock + { + public T Value { get; set; } + public V Value2 { get; set; } + } +} diff --git a/Tests/ApplicationTests/PluginTests.cs b/Tests/ApplicationTests/PluginTests.cs index 00ecc8128..893c8e221 100644 --- a/Tests/ApplicationTests/PluginTests.cs +++ b/Tests/ApplicationTests/PluginTests.cs @@ -33,6 +33,7 @@ namespace ApplicationTests serviceProvider = new ServiceCollection().BuildBase() .AddSingleton(A.Fake()) .AddSingleton() + .AddSingleton(A.Fake()) .BuildServiceProvider(); fakeManager = serviceProvider.GetRequiredService(); mockEventHandler = serviceProvider.GetRequiredService(); @@ -66,7 +67,7 @@ namespace ApplicationTests A.CallTo(() => fakeManager.GetClientService()) .Returns(fakeClientService); - await plugin.Initialize(serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()); + await plugin.Initialize(serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()); var gameEvent = new GameEvent() { diff --git a/Tests/ApplicationTests/ScriptPluginServiceResolverTests.cs b/Tests/ApplicationTests/ScriptPluginServiceResolverTests.cs new file mode 100644 index 000000000..e6c08bb66 --- /dev/null +++ b/Tests/ApplicationTests/ScriptPluginServiceResolverTests.cs @@ -0,0 +1,66 @@ +using ApplicationTests.Mocks; +using IW4MAdmin.Application.Misc; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using System; + +namespace ApplicationTests +{ + public class ScriptPluginServiceResolverTests + { + private IServiceProvider serviceProvider; + + [SetUp] + public void Setup() + { + serviceProvider = new ServiceCollection() + .BuildBase() + .AddSingleton() + .AddSingleton() + .AddSingleton(new ScriptResolverMock { Value = "test" }) + .AddSingleton, ScriptResolverGenericMock>() + .AddSingleton(new ScriptResolverGenericMock { Value = 123, Value2 = "test" }) + .BuildServiceProvider(); + } + + [Test] + public void Test_ResolveType() + { + var resolver = serviceProvider.GetService(); + var expectedResolvedService = serviceProvider.GetService(); + var resolvedService = resolver.ResolveService(nameof(ScriptResolverMock)); + + Assert.AreEqual(expectedResolvedService, resolvedService); + } + + [Test] + public void Test_ResolveType_Interface() + { + var resolver = serviceProvider.GetService(); + var expectedResolvedService = serviceProvider.GetService(); + var resolvedService = resolver.ResolveService(nameof(IScriptResolverMock)); + + Assert.AreEqual(expectedResolvedService, resolvedService); + } + + [Test] + public void Test_ResolveGenericType() + { + var resolver = serviceProvider.GetService(); + var expectedResolvedService = serviceProvider.GetService>(); + var resolvedService = resolver.ResolveService("ScriptResolverGenericMock", new[] { "Int32", "String" }); + + Assert.AreEqual(expectedResolvedService, resolvedService); + } + + [Test] + public void Test_ResolveGenericType_Interface() + { + var resolver = serviceProvider.GetService(); + var expectedResolvedService = serviceProvider.GetService>(); + var resolvedService = resolver.ResolveService("IScriptResolverGenericMock", new[] { "Int32", "String" }); + + Assert.AreEqual(expectedResolvedService, resolvedService); + } + } +}