| @@ -5,6 +5,8 @@ VisualStudioVersion = 14.0.25123.0 | |||
| MinimumVisualStudioVersion = 10.0.40219.1 | |||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | |||
| EndProject | |||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" | |||
| EndProject | |||
| Global | |||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
| Debug|Any CPU = Debug|Any CPU | |||
| @@ -15,6 +17,10 @@ Global | |||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| EndGlobalSection | |||
| GlobalSection(SolutionProperties) = preSolution | |||
| HideSolutionNode = FALSE | |||
| @@ -0,0 +1,14 @@ | |||
| using System; | |||
| namespace Discord.Commands | |||
| { | |||
| [AttributeUsage(AttributeTargets.Method)] | |||
| public class CommandAttribute : Attribute | |||
| { | |||
| public string Name { get; } | |||
| public CommandAttribute(string name) | |||
| { | |||
| Name = name; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| using System; | |||
| namespace Discord.Commands | |||
| { | |||
| [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter)] | |||
| public class DescriptionAttribute : Attribute | |||
| { | |||
| public string Text { get; } | |||
| public DescriptionAttribute(string text) | |||
| { | |||
| Text = text; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| using System; | |||
| namespace Discord.Commands | |||
| { | |||
| [AttributeUsage(AttributeTargets.Class)] | |||
| public class GroupAttribute : Attribute | |||
| { | |||
| public string Name { get; } | |||
| public GroupAttribute(string name) | |||
| { | |||
| Name = name; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| using System; | |||
| namespace Discord.Commands | |||
| { | |||
| [AttributeUsage(AttributeTargets.Class)] | |||
| public class ModuleAttribute : Attribute | |||
| { | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| using System; | |||
| namespace Discord.Commands | |||
| { | |||
| [AttributeUsage(AttributeTargets.Parameter)] | |||
| public class UnparsedAttribute : Attribute | |||
| { | |||
| } | |||
| } | |||
| @@ -0,0 +1,106 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Reflection; | |||
| using System.Threading; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.Commands | |||
| { | |||
| public class Module | |||
| { | |||
| public string Name { get; } | |||
| public IEnumerable<Command> Commands { get; } | |||
| internal Module(object module, TypeInfo typeInfo) | |||
| { | |||
| List<Command> commands = new List<Command>(); | |||
| SearchClass(commands); | |||
| Commands = commands; | |||
| } | |||
| private void SearchClass(List<Command> commands) | |||
| { | |||
| //TODO: Implement | |||
| } | |||
| } | |||
| public class Command | |||
| { | |||
| public string SourceName { get; } | |||
| } | |||
| public class CommandParser | |||
| { | |||
| private readonly SemaphoreSlim _moduleLock; | |||
| private readonly Dictionary<object, Module> _modules; | |||
| public CommandParser() | |||
| { | |||
| _modules = new Dictionary<object, Module>(); | |||
| _moduleLock = new SemaphoreSlim(1, 1); | |||
| } | |||
| public async Task<Module> Load(object module) | |||
| { | |||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| if (_modules.ContainsKey(module)) | |||
| throw new ArgumentException($"This module has already been loaded."); | |||
| return LoadInternal(module, module.GetType().GetTypeInfo()); | |||
| } | |||
| finally | |||
| { | |||
| _moduleLock.Release(); | |||
| } | |||
| } | |||
| private Module LoadInternal(object module, TypeInfo typeInfo) | |||
| { | |||
| var loadedModule = new Module(module, typeInfo); | |||
| _modules[module] = loadedModule; | |||
| return loadedModule; | |||
| } | |||
| public async Task<IEnumerable<Module>> LoadAssembly(Assembly assembly) | |||
| { | |||
| List<Module> modules = new List<Module>(); | |||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| foreach (var type in assembly.ExportedTypes) | |||
| { | |||
| var typeInfo = type.GetTypeInfo(); | |||
| if (typeInfo.GetCustomAttribute<ModuleAttribute>() != null) | |||
| { | |||
| var constructor = typeInfo.DeclaredConstructors.Where(x => x.GetParameters().Length == 0).FirstOrDefault(); | |||
| if (constructor == null) | |||
| throw new InvalidOperationException($"Failed to find a valid constructor for \"{typeInfo.FullName}\""); | |||
| object module; | |||
| try { module = constructor.Invoke(null); } | |||
| catch (Exception ex) | |||
| { | |||
| throw new InvalidOperationException($"Failed to create \"{typeInfo.FullName}\"", ex); | |||
| } | |||
| modules.Add(LoadInternal(module, typeInfo)); | |||
| } | |||
| } | |||
| return modules; | |||
| } | |||
| finally | |||
| { | |||
| _moduleLock.Release(); | |||
| } | |||
| } | |||
| public async Task<bool> Unload(object module) | |||
| { | |||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| return _modules.Remove(module); | |||
| } | |||
| finally | |||
| { | |||
| _moduleLock.Release(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
| <PropertyGroup> | |||
| <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | |||
| <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||
| </PropertyGroup> | |||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||
| <PropertyGroup Label="Globals"> | |||
| <ProjectGuid>078dd7e6-943d-4d09-afc2-d2ba58b76c9c</ProjectGuid> | |||
| <RootNamespace>Discord.Commands</RootNamespace> | |||
| <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||
| <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||
| <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> | |||
| </PropertyGroup> | |||
| <PropertyGroup> | |||
| <SchemaVersion>2.0</SchemaVersion> | |||
| </PropertyGroup> | |||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||
| </Project> | |||
| @@ -0,0 +1,34 @@ | |||
| { | |||
| "version": "1.0.0-dev", | |||
| "description": "A Discord.Net extension adding command support.", | |||
| "authors": [ "RogueException" ], | |||
| "packOptions": { | |||
| "tags": [ "discord", "discordapp" ], | |||
| "licenseUrl": "http://opensource.org/licenses/MIT", | |||
| "projectUrl": "https://github.com/RogueException/Discord.Net", | |||
| "repository": { | |||
| "type": "git", | |||
| "url": "git://github.com/RogueException/Discord.Net" | |||
| } | |||
| }, | |||
| "buildOptions": { | |||
| "allowUnsafe": true, | |||
| "warningsAsErrors": false | |||
| }, | |||
| "dependencies": { | |||
| "Discord.Net": "1.0.0-dev" | |||
| }, | |||
| "frameworks": { | |||
| "netstandard1.3": { | |||
| "imports": [ | |||
| "dotnet5.4", | |||
| "dnxcore50", | |||
| "portable-net45+win8" | |||
| ] | |||
| } | |||
| } | |||
| } | |||