From 593ba46f1c0ade42e486a5a9a21107b694377798 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 18 Nov 2016 07:28:30 -0400 Subject: [PATCH] Cleaned up command builders and async func names --- .../Builders/CommandBuilder.cs | 96 ++++++++-------- .../Builders/ModuleBuilder.cs | 105 ++++++++++-------- .../ModuleClassBuilder.cs | 19 ++-- .../Builders/ParameterBuilder.cs | 101 +++++++---------- src/Discord.Net.Commands/CommandService.cs | 22 ++-- src/Discord.Net.Commands/Info/CommandInfo.cs | 22 ++-- src/Discord.Net.Commands/Info/ModuleInfo.cs | 7 +- .../Info/ParameterInfo.cs | 10 +- src/Discord.Net.Commands/Map/CommandMap.cs | 84 +++----------- .../Map/CommandMapNode.cs | 27 ++++- src/Discord.Net.Core/Utils/Preconditions.cs | 74 ++++++------ 11 files changed, 261 insertions(+), 306 deletions(-) rename src/Discord.Net.Commands/{Utilities => Builders}/ModuleClassBuilder.cs (93%) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 921e5df0a..9b983fd1f 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -7,107 +7,109 @@ namespace Discord.Commands.Builders { public class CommandBuilder { - private List preconditions; - private List parameters; - private List aliases; + private readonly List _preconditions; + private readonly List _parameters; + private readonly List _aliases; - internal CommandBuilder(ModuleBuilder module) - { - preconditions = new List(); - parameters = new List(); - aliases = new List(); - - Module = module; - } + public ModuleBuilder Module { get; } + internal Func Callback { get; set; } public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } public RunMode RunMode { get; set; } public int Priority { get; set; } - public Func Callback { get; set; } - public ModuleBuilder Module { get; } - public List Preconditions => preconditions; - public List Parameters => parameters; - public List Aliases => aliases; + public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Parameters => _parameters; + public IReadOnlyList Aliases => _aliases; + + //Automatic + internal CommandBuilder(ModuleBuilder module) + { + Module = module; + + _preconditions = new List(); + _parameters = new List(); + _aliases = new List(); + } + //User-defined + internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func callback) + : this(module) + { + Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); + Discord.Preconditions.NotNull(callback, nameof(callback)); + + Callback = callback; + _aliases.Add(primaryAlias); + } - public CommandBuilder SetName(string name) + public CommandBuilder WithName(string name) { Name = name; return this; } - - public CommandBuilder SetSummary(string summary) + public CommandBuilder WithSummary(string summary) { Summary = summary; return this; } - - public CommandBuilder SetRemarks(string remarks) + public CommandBuilder WithRemarks(string remarks) { Remarks = remarks; return this; } - - public CommandBuilder SetRunMode(RunMode runMode) + public CommandBuilder WithRunMode(RunMode runMode) { RunMode = runMode; return this; } - - public CommandBuilder SetPriority(int priority) + public CommandBuilder WithPriority(int priority) { Priority = priority; return this; } - public CommandBuilder SetCallback(Func callback) + public CommandBuilder AddAliases(params string[] aliases) { - Callback = callback; + _aliases.AddRange(aliases); return this; } - public CommandBuilder AddPrecondition(PreconditionAttribute precondition) { - preconditions.Add(precondition); + _preconditions.Add(precondition); return this; } - - public CommandBuilder AddParameter(Action createFunc) + public CommandBuilder AddParameter(string name, Type type, Action createFunc) { - var param = new ParameterBuilder(); + var param = new ParameterBuilder(this, name, type); createFunc(param); - parameters.Add(param); + _parameters.Add(param); return this; } - - public CommandBuilder AddAliases(params string[] newAliases) + internal CommandBuilder AddParameter(Action createFunc) { - aliases.AddRange(newAliases); + var param = new ParameterBuilder(this); + createFunc(param); + _parameters.Add(param); return this; } internal CommandInfo Build(ModuleInfo info, CommandService service) { - if (aliases.Count == 0) - throw new InvalidOperationException("Commands require at least one alias to be registered"); - - if (Callback == null) - throw new InvalidOperationException("Commands require a callback to be built"); - + //Default name to first alias if (Name == null) - Name = aliases[0]; + Name = _aliases[0]; - if (parameters.Count > 0) + if (_parameters.Count > 0) { - var lastParam = parameters[parameters.Count - 1]; + var lastParam = _parameters[_parameters.Count - 1]; - var firstMultipleParam = parameters.FirstOrDefault(x => x.Multiple); + var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple); if ((firstMultipleParam != null) && (firstMultipleParam != lastParam)) throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag."); - var firstRemainderParam = parameters.FirstOrDefault(x => x.Remainder); + var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder); if ((firstRemainderParam != null) && (firstRemainderParam != lastParam)) throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag."); } diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 148eedfcf..083de8e81 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -1,96 +1,107 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; +using System.Threading.Tasks; namespace Discord.Commands.Builders { public class ModuleBuilder { - private List commands; - private List submodules; - private List preconditions; - private List aliases; - - public ModuleBuilder() - { - commands = new List(); - submodules = new List(); - preconditions = new List(); - aliases = new List(); - } - - internal ModuleBuilder(ModuleBuilder parent) - : this() - { - ParentModule = parent; - } + private readonly List _commands; + private readonly List _submodules; + private readonly List _preconditions; + private readonly List _aliases; + public CommandService Service { get; } + public ModuleBuilder Parent { get; } public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } - public ModuleBuilder ParentModule { get; } - public List Commands => commands; - public List Modules => submodules; - public List Preconditions => preconditions; - public List Aliases => aliases; + public IReadOnlyList Commands => _commands; + public IReadOnlyList Modules => _submodules; + public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Aliases => _aliases; + + //Automatic + internal ModuleBuilder(CommandService service, ModuleBuilder parent) + { + Service = service; + Parent = parent; - public ModuleBuilder SetName(string name) + _commands = new List(); + _submodules = new List(); + _preconditions = new List(); + _aliases = new List(); + } + //User-defined + internal ModuleBuilder(CommandService service, ModuleBuilder parent, string primaryAlias) + : this(service, parent) + { + Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); + + _aliases = new List { primaryAlias }; + } + + public ModuleBuilder WithName(string name) { Name = name; return this; } - - public ModuleBuilder SetSummary(string summary) + public ModuleBuilder WithSummary(string summary) { Summary = summary; return this; } - - public ModuleBuilder SetRemarks(string remarks) + public ModuleBuilder WithRemarks(string remarks) { Remarks = remarks; return this; } - public ModuleBuilder AddAliases(params string[] newAliases) + public ModuleBuilder AddAlias(params string[] newAliases) { - aliases.AddRange(newAliases); + _aliases.AddRange(newAliases); return this; } - public ModuleBuilder AddPrecondition(PreconditionAttribute precondition) { - preconditions.Add(precondition); + _preconditions.Add(precondition); return this; } - - public ModuleBuilder AddCommand(Action createFunc) + public ModuleBuilder AddCommand(string primaryAlias, Func callback, Action createFunc) + { + var builder = new CommandBuilder(this, primaryAlias, callback); + createFunc(builder); + _commands.Add(builder); + return this; + } + internal ModuleBuilder AddCommand(Action createFunc) { var builder = new CommandBuilder(this); createFunc(builder); - commands.Add(builder); + _commands.Add(builder); return this; } - - public ModuleBuilder AddSubmodule(Action createFunc) + public ModuleBuilder AddModule(string primaryAlias, Action createFunc) { - var builder = new ModuleBuilder(this); + var builder = new ModuleBuilder(Service, this, primaryAlias); createFunc(builder); - submodules.Add(builder); + _submodules.Add(builder); + return this; + } + internal ModuleBuilder AddModule(Action createFunc) + { + var builder = new ModuleBuilder(Service, this); + createFunc(builder); + _submodules.Add(builder); return this; } public ModuleInfo Build(CommandService service) { - if (aliases.Count == 0) - throw new InvalidOperationException("Modules require at least one alias to be registered"); - - if (commands.Count == 0 && submodules.Count == 0) - throw new InvalidOperationException("Tried to build empty module"); - + //Default name to first alias if (Name == null) - Name = aliases[0]; + Name = _aliases[0]; return new ModuleInfo(this, service); } diff --git a/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs similarity index 93% rename from src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs rename to src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 85cd9a664..9884f18db 100644 --- a/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -40,11 +40,11 @@ namespace Discord.Commands foreach (var typeInfo in topLevelGroups) { - // this shouldn't be the case; may be safe to remove? + // TODO: This shouldn't be the case; may be safe to remove? if (result.ContainsKey(typeInfo.AsType())) continue; - var module = new ModuleBuilder(); + var module = new ModuleBuilder(service, null); BuildModule(module, typeInfo, service); BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); @@ -65,7 +65,7 @@ namespace Discord.Commands if (builtTypes.Contains(typeInfo)) continue; - builder.AddSubmodule((module) => { + builder.AddModule((module) => { BuildModule(module, typeInfo, service); BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); }); @@ -88,17 +88,20 @@ namespace Discord.Commands else if (attribute is RemarksAttribute) builder.Remarks = (attribute as RemarksAttribute).Text; else if (attribute is AliasAttribute) - builder.AddAliases((attribute as AliasAttribute).Aliases); + builder.AddAlias((attribute as AliasAttribute).Aliases); else if (attribute is GroupAttribute) { var groupAttr = attribute as GroupAttribute; builder.Name = builder.Name ?? groupAttr.Prefix; - builder.AddAliases(groupAttr.Prefix); + builder.AddAlias(groupAttr.Prefix); } else if (attribute is PreconditionAttribute) builder.AddPrecondition(attribute as PreconditionAttribute); } + if (builder.Name == null) + builder.Name = typeInfo.Name; + var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); foreach (var method in validCommands) @@ -168,7 +171,7 @@ namespace Discord.Commands builder.Name = paramInfo.Name; - builder.Optional = paramInfo.IsOptional; + builder.IsOptional = paramInfo.IsOptional; builder.DefaultValue = paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null; foreach (var attribute in attributes) @@ -178,7 +181,7 @@ namespace Discord.Commands builder.Summary = (attribute as SummaryAttribute).Text; else if (attribute is ParamArrayAttribute) { - builder.Multiple = true; + builder.IsMultiple = true; paramType = paramType.GetElementType(); } else if (attribute is RemainderAttribute) @@ -186,7 +189,7 @@ namespace Discord.Commands if (position != count-1) throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); - builder.Remainder = true; + builder.IsRemainder = true; } } diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index a6c8a5c23..801a10080 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -1,100 +1,79 @@ using System; -using System.Threading.Tasks; -using System.Collections.Generic; +using System.Reflection; namespace Discord.Commands.Builders { public class ParameterBuilder { - public ParameterBuilder() - { } - - public ParameterBuilder(string name) - { - Name = name; - } - - public string Name { get; set; } - public string Summary { get; set; } - public object DefaultValue { get; set; } - public Type ParameterType { get; set; } + public CommandBuilder Command { get; } + public string Name { get; internal set; } + public Type ParameterType { get; internal set; } public TypeReader TypeReader { get; set; } + public bool IsOptional { get; set; } + public bool IsRemainder { get; set; } + public bool IsMultiple { get; set; } + public object DefaultValue { get; set; } + public string Summary { get; set; } - public bool Optional { get; set; } - public bool Remainder { get; set; } - public bool Multiple { get; set; } - - public ParameterBuilder SetName(string name) + //Automatic + internal ParameterBuilder(CommandBuilder command) { - Name = name; - return this; + Command = command; } - - public ParameterBuilder SetSummary(string summary) + //User-defined + internal ParameterBuilder(CommandBuilder command, string name, Type type) + : this(command) { - Summary = summary; - return this; + Preconditions.NotNull(name, nameof(name)); + + Name = name; + SetType(type); } - public ParameterBuilder SetDefault(T defaultValue) + internal void SetType(Type type) { - Optional = true; - DefaultValue = defaultValue; - ParameterType = typeof(T); + TypeReader = Command.Module.Service.GetTypeReader(type); - if (ParameterType.IsArray) - ParameterType = ParameterType.GetElementType(); - - return this; + if (type.GetTypeInfo().IsValueType) + DefaultValue = Activator.CreateInstance(type); + else if (type.IsArray) + type = ParameterType.GetElementType(); + ParameterType = type; } - - public ParameterBuilder SetType(Type parameterType) + + public ParameterBuilder WithSummary(string summary) { - ParameterType = parameterType; + Summary = summary; return this; } - - public ParameterBuilder SetTypeReader(TypeReader reader) + public ParameterBuilder WithDefault(object defaultValue) { - TypeReader = reader; + DefaultValue = defaultValue; return this; } - - public ParameterBuilder SetOptional(bool isOptional) + public ParameterBuilder WithIsOptional(bool isOptional) { - Optional = isOptional; + IsOptional = isOptional; return this; } - - public ParameterBuilder SetRemainder(bool isRemainder) + public ParameterBuilder WithIsRemainder(bool isRemainder) { - Remainder = isRemainder; + IsRemainder = isRemainder; return this; } - - public ParameterBuilder SetMultiple(bool isMultiple) + public ParameterBuilder WithIsMultiple(bool isMultiple) { - Multiple = isMultiple; + IsMultiple = isMultiple; return this; } - internal ParameterInfo Build(CommandInfo info, CommandService service) + internal ParameterInfo Build(CommandInfo info) { - // TODO: should we throw when we don't have a name? - if (Name == null) - Name = "[unknown parameter]"; - - if (ParameterType == null) - throw new InvalidOperationException($"Could not build parameter {Name} from command {info.Name} - An invalid parameter type was given"); - - if (TypeReader == null) - TypeReader = service.GetTypeReader(ParameterType); - if (TypeReader == null) - throw new InvalidOperationException($"Could not build parameter {Name} from command {info.Name} - A valid TypeReader could not be found"); + throw new InvalidOperationException($"No default TypeReader found, one must be specified"); - return new ParameterInfo(this, info, service); + return new ParameterInfo(this, info, Command.Module.Service); } } } \ No newline at end of file diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index ee18157dc..3c3760908 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -67,12 +67,12 @@ namespace Discord.Commands } //Modules - public async Task BuildModule(Action buildFunc) + public async Task CreateModuleAsync(string primaryAlias, Action buildFunc) { await _moduleLock.WaitAsync().ConfigureAwait(false); try { - var builder = new ModuleBuilder(); + var builder = new ModuleBuilder(this, null, primaryAlias); buildFunc(builder); var module = builder.Build(this); @@ -83,7 +83,7 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task AddModule() + public async Task AddModuleAsync() { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -107,7 +107,7 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task> AddModules(Assembly assembly) + public async Task> AddModulesAsync(Assembly assembly) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -141,7 +141,7 @@ namespace Discord.Commands return module; } - public async Task RemoveModule(ModuleInfo module) + public async Task RemoveModuleAsync(ModuleInfo module) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -153,7 +153,7 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task RemoveModule() + public async Task RemoveModuleAsync() { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -217,9 +217,9 @@ namespace Discord.Commands return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } - public Task Execute(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) - => Execute(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); - public async Task Execute(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public Task ExecuteAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + => ExecuteAsync(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); + public async Task ExecuteAsync(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { dependencyMap = dependencyMap ?? DependencyMap.Empty; @@ -230,7 +230,7 @@ namespace Discord.Commands var commands = searchResult.Commands; for (int i = commands.Count - 1; i >= 0; i--) { - var preconditionResult = await commands[i].CheckPreconditions(context, dependencyMap).ConfigureAwait(false); + var preconditionResult = await commands[i].CheckPreconditionsAsync(context, dependencyMap).ConfigureAwait(false); if (!preconditionResult.IsSuccess) { if (commands.Count == 1) @@ -239,7 +239,7 @@ namespace Discord.Commands continue; } - var parseResult = await commands[i].Parse(context, searchResult, preconditionResult).ConfigureAwait(false); + var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false); if (!parseResult.IsSuccess) { if (parseResult.Error == CommandError.MultipleMatches) diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 8ece1cc2c..4d546b6fa 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -39,17 +39,21 @@ namespace Discord.Commands RunMode = builder.RunMode; Priority = builder.Priority; + + if (module.Aliases.Count != 0) + Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => first + " " + second).ToImmutableArray(); + else + Aliases = builder.Aliases.ToImmutableArray(); - Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => first + " " + second).ToImmutableArray(); Preconditions = builder.Preconditions.ToImmutableArray(); - Parameters = builder.Parameters.Select(x => x.Build(this, service)).ToImmutableArray(); - HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].Multiple : false; + Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); + HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; _action = builder.Callback; } - public async Task CheckPreconditions(CommandContext context, IDependencyMap map = null) + public async Task CheckPreconditionsAsync(CommandContext context, IDependencyMap map = null) { if (map == null) map = DependencyMap.Empty; @@ -71,13 +75,13 @@ namespace Discord.Commands return PreconditionResult.FromSuccess(); } - public async Task Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) + public async Task ParseAsync(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) { if (!searchResult.IsSuccess) return ParseResult.FromError(searchResult); if (preconditionResult != null && !preconditionResult.Value.IsSuccess) return ParseResult.FromError(preconditionResult.Value); - + string input = searchResult.Text; var matchingAliases = Aliases.Where(alias => input.StartsWith(alias)); @@ -114,9 +118,9 @@ namespace Discord.Commands paramList[i] = parseResult.ParamValues[i].Values.First().Value; } - return Execute(context, argList, paramList, map); + return ExecuteAsync(context, argList, paramList, map); } - public async Task Execute(CommandContext context, IEnumerable argList, IEnumerable paramList, IDependencyMap map) + public async Task ExecuteAsync(CommandContext context, IEnumerable argList, IEnumerable paramList, IDependencyMap map) { if (map == null) map = DependencyMap.Empty; @@ -163,7 +167,7 @@ namespace Discord.Commands if (HasVarArgs) { - var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].ParameterType, t => + var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t => { var method = _convertParamsMethod.MakeGenericMethod(t); return (Func, object>)method.CreateDelegate(typeof(Func, object>)); diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index f874f5540..64fa29ea2 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; @@ -41,11 +40,11 @@ namespace Discord.Commands Stack builderStack = new Stack(); builderStack.Push(builder); - ModuleBuilder parent = builder.ParentModule; + ModuleBuilder parent = builder.Parent; while (parent != null) { builderStack.Push(parent); - parent = parent.ParentModule; + parent = parent.Parent; } while (builderStack.Count() > 0) @@ -82,7 +81,7 @@ namespace Discord.Commands while (parent != null) { result.AddRange(parent.Preconditions); - parent = parent.ParentModule; + parent = parent.Parent; } return result; diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 812fec572..18c5e653c 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -16,11 +16,11 @@ namespace Discord.Commands Name = builder.Name; Summary = builder.Summary; - IsOptional = builder.Optional; - IsRemainder = builder.Remainder; - IsMultiple = builder.Multiple; + IsOptional = builder.IsOptional; + IsRemainder = builder.IsRemainder; + IsMultiple = builder.IsMultiple; - ParameterType = builder.ParameterType; + Type = builder.ParameterType; DefaultValue = builder.DefaultValue; _reader = builder.TypeReader; @@ -32,7 +32,7 @@ namespace Discord.Commands public bool IsOptional { get; } public bool IsRemainder { get; } public bool IsMultiple { get; } - public Type ParameterType { get; } + public Type Type { get; } public object DefaultValue { get; } public async Task Parse(CommandContext context, string input) diff --git a/src/Discord.Net.Commands/Map/CommandMap.cs b/src/Discord.Net.Commands/Map/CommandMap.cs index e809c1b70..3a5239878 100644 --- a/src/Discord.Net.Commands/Map/CommandMap.cs +++ b/src/Discord.Net.Commands/Map/CommandMap.cs @@ -1,95 +1,39 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; namespace Discord.Commands { internal class CommandMap { - static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' }; - private readonly object _lockObj = new object(); - - private readonly ConcurrentDictionary _nodes; + private readonly CommandMapNode _root; + private static readonly string[] _blankAliases = new[] { "" }; public CommandMap() { - _nodes = new ConcurrentDictionary(); + _root = new CommandMapNode(""); } public void AddCommand(CommandInfo command) { - foreach (string text in command.Aliases) - { - int nextSpace = NextWhitespace(text); - string name; - - if (nextSpace == -1) - name = text; - else - name = text.Substring(0, nextSpace); - - lock (_lockObj) - { - var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(x)); - nextNode.AddCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); - } - } + foreach (string text in GetAliases(command)) + _root.AddCommand(text, 0, command); } public void RemoveCommand(CommandInfo command) { - foreach (string text in command.Aliases) - { - int nextSpace = NextWhitespace(text); - string name; - - if (nextSpace == -1) - name = text; - else - name = text.Substring(0, nextSpace); - - lock (_lockObj) - { - CommandMapNode nextNode; - if (_nodes.TryGetValue(name, out nextNode)) - { - nextNode.RemoveCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); - if (nextNode.IsEmpty) - _nodes.TryRemove(name, out nextNode); - } - } - } + foreach (string text in GetAliases(command)) + _root.RemoveCommand(text, 0, command); } public IEnumerable GetCommands(string text) { - int nextSpace = NextWhitespace(text); - string name; - - if (nextSpace == -1) - name = text; - else - name = text.Substring(0, nextSpace); - - lock (_lockObj) - { - CommandMapNode nextNode; - if (_nodes.TryGetValue(name, out nextNode)) - return nextNode.GetCommands(text, nextSpace + 1); - else - return Enumerable.Empty(); - } + return _root.GetCommands(text, 0); } - private static int NextWhitespace(string text) + private IReadOnlyList GetAliases(CommandInfo command) { - int lowest = int.MaxValue; - for (int i = 0; i < _whitespaceChars.Length; i++) - { - int index = text.IndexOf(_whitespaceChars[i]); - if (index != -1 && index < lowest) - lowest = index; - } - return (lowest != int.MaxValue) ? lowest : -1; + var aliases = command.Aliases; + if (aliases.Count == 0) + return _blankAliases; + return aliases; } } } diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index a348a8cb4..a86c0643d 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -6,6 +7,8 @@ namespace Discord.Commands { internal class CommandMapNode { + private static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' }; + private readonly ConcurrentDictionary _nodes; private readonly string _name; private readonly object _lockObj = new object(); @@ -22,13 +25,17 @@ namespace Discord.Commands public void AddCommand(string text, int index, CommandInfo command) { - int nextSpace = text.IndexOf(' ', index); + int nextSpace = NextWhitespace(text, index); string name; lock (_lockObj) { if (text == "") + { + if (_name == "") + throw new InvalidOperationException("Cannot add commands to the root node."); _commands = _commands.Add(command); + } else { if (nextSpace == -1) @@ -43,7 +50,7 @@ namespace Discord.Commands } public void RemoveCommand(string text, int index, CommandInfo command) { - int nextSpace = text.IndexOf(' ', index); + int nextSpace = NextWhitespace(text, index); string name; lock (_lockObj) @@ -70,7 +77,7 @@ namespace Discord.Commands public IEnumerable GetCommands(string text, int index) { - int nextSpace = text.IndexOf(' ', index); + int nextSpace = NextWhitespace(text, index); string name; var commands = _commands; @@ -92,5 +99,17 @@ namespace Discord.Commands } } } + + private static int NextWhitespace(string text, int startIndex) + { + int lowest = int.MaxValue; + for (int i = 0; i < _whitespaceChars.Length; i++) + { + int index = text.IndexOf(_whitespaceChars[i], startIndex); + if (index != -1 && index < lowest) + lowest = index; + } + return (lowest != int.MaxValue) ? lowest : -1; + } } } diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 92713368a..44d4e381b 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -5,47 +5,51 @@ namespace Discord internal static class Preconditions { //Objects - public static void NotNull(T obj, string name, string msg = null) where T : class { if (obj == null) throw new ArgumentNullException(name); } - public static void NotNull(Optional obj, string name, string msg = null) where T : class { if (obj.IsSpecified && obj.Value == null) throw new ArgumentNullException(name); } + public static void NotNull(T obj, string name, string msg = null) where T : class { if (obj == null) throw CreateNotNullException(name, msg); } + public static void NotNull(Optional obj, string name, string msg = null) where T : class { if (obj.IsSpecified && obj.Value == null) throw CreateNotNullException(name, msg); } + + private static ArgumentNullException CreateNotNullException(string name, string msg) + { + if (msg == null) return new ArgumentNullException(name); + else return new ArgumentNullException(name, msg); + } //Strings - public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } - public static void NotEmpty(Optional obj, string name, string msg = null) { if (obj.IsSpecified && obj.Value.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } + public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw CreateNotEmptyException(name, msg); } + public static void NotEmpty(Optional obj, string name, string msg = null) { if (obj.IsSpecified && obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); } public static void NotNullOrEmpty(string obj, string name, string msg = null) { - if (obj == null) - throw new ArgumentNullException(name); - if (obj.Length == 0) - throw new ArgumentException("Argument cannot be empty.", name); + if (obj == null) throw CreateNotNullException(name, msg); + if (obj.Length == 0) throw CreateNotEmptyException(name, msg); } public static void NotNullOrEmpty(Optional obj, string name, string msg = null) { if (obj.IsSpecified) { - if (obj.Value == null) - throw new ArgumentNullException(name); - if (obj.Value.Length == 0) - throw new ArgumentException("Argument cannot be empty.", name); + if (obj.Value == null) throw CreateNotNullException(name, msg); + if (obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); } } public static void NotNullOrWhitespace(string obj, string name, string msg = null) { - if (obj == null) - throw new ArgumentNullException(name); - if (obj.Trim().Length == 0) - throw new ArgumentException("Argument cannot be blank.", name); + if (obj == null) throw CreateNotNullException(name, msg); + if (obj.Trim().Length == 0) throw CreateNotEmptyException(name, msg); } public static void NotNullOrWhitespace(Optional obj, string name, string msg = null) { if (obj.IsSpecified) { - if (obj.Value == null) - throw new ArgumentNullException(name); - if (obj.Value.Trim().Length == 0) - throw new ArgumentException("Argument cannot be blank.", name); + if (obj.Value == null) throw CreateNotNullException(name, msg); + if (obj.Value.Trim().Length == 0) throw CreateNotEmptyException(name, msg); } } + private static ArgumentException CreateNotEmptyException(string name, string msg) + { + if (msg == null) return new ArgumentException(name, "Argument cannot be blank."); + else return new ArgumentException(name, msg); + } + //Numerics public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } public static void NotEqual(byte obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } @@ -82,10 +86,8 @@ namespace Discord private static ArgumentException CreateNotEqualException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value may not be equal to {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value may not be equal to {value}", name); + else return new ArgumentException(msg, name); } public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } @@ -107,10 +109,8 @@ namespace Discord private static ArgumentException CreateAtLeastException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value must be at least {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value must be at least {value}", name); + else return new ArgumentException(msg, name); } public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } @@ -132,10 +132,8 @@ namespace Discord private static ArgumentException CreateGreaterThanException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value must be greater than {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value must be greater than {value}", name); + else return new ArgumentException(msg, name); } public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } @@ -157,10 +155,8 @@ namespace Discord private static ArgumentException CreateAtMostException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value must be at most {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value must be at most {value}", name); + else return new ArgumentException(msg, name); } public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } @@ -182,10 +178,8 @@ namespace Discord private static ArgumentException CreateLessThanException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value must be less than {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value must be less than {value}", name); + else return new ArgumentException(msg, name); } } }