|
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Collections.Immutable;
- using System.Linq;
- using System.Reflection;
- using System.Threading;
- using System.Threading.Tasks;
- using Discord.Commands.Builders;
- using Discord.Logging;
-
- namespace Discord.Commands
- {
- /// <summary>
- /// Provides a framework for building Discord commands.
- /// </summary>
- /// <remarks>
- /// <para>
- /// The service provides a framework for building Discord commands both dynamically via runtime builders or
- /// statically via compile-time modules. To create a command module at compile-time, see
- /// <see cref="ModuleBase" /> (most common); otherwise, see <see cref="ModuleBuilder" />.
- /// </para>
- /// <para>
- /// This service also provides several events for monitoring command usages; such as
- /// <see cref="Discord.Commands.CommandService.Log" /> for any command-related log events, and
- /// <see cref="Discord.Commands.CommandService.CommandExecuted" /> for information about commands that have
- /// been successfully executed.
- /// </para>
- /// </remarks>
- public class CommandService : IDisposable
- {
- /// <summary>
- /// Occurs when a command-related information is received.
- /// </summary>
- public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } }
- internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>();
-
- /// <summary>
- /// Occurs when a command is successfully executed without any error.
- /// </summary>
- /// <remarks>
- /// <para>
- /// This event is fired when a command has been successfully executed without any of the following errors:
- /// </para>
- /// <para>* Parsing error</para>
- /// <para>* Precondition error</para>
- /// <para>* Runtime exception</para>
- /// <para>
- /// Should the command encounter any of the aforementioned error, this event will not be raised.
- /// </para>
- /// </remarks>
- public event Func<Optional<CommandInfo>, ICommandContext, IResult, Task> CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } }
- internal readonly AsyncEvent<Func<Optional<CommandInfo>, ICommandContext, IResult, Task>> _commandExecutedEvent = new AsyncEvent<Func<Optional<CommandInfo>, ICommandContext, IResult, Task>>();
-
- private readonly SemaphoreSlim _moduleLock;
- private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs;
- private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders;
- private readonly ConcurrentDictionary<Type, TypeReader> _defaultTypeReaders;
- private readonly ImmutableList<Tuple<Type, Type>> _entityTypeReaders; //TODO: Candidate for C#7 Tuple
- private readonly HashSet<ModuleInfo> _moduleDefs;
- private readonly CommandMap _map;
-
- internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs;
- internal readonly char _separatorChar;
- internal readonly RunMode _defaultRunMode;
- internal readonly Logger _cmdLogger;
- internal readonly LogManager _logManager;
- internal readonly IReadOnlyDictionary<char, char> _quotationMarkAliasMap;
-
- internal bool _isDisposed;
-
- /// <summary>
- /// Represents all modules loaded within <see cref="CommandService"/>.
- /// </summary>
- public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x);
-
- /// <summary>
- /// Represents all commands loaded within <see cref="CommandService"/>.
- /// </summary>
- public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands);
-
- /// <summary>
- /// Represents all <see cref="TypeReader" /> loaded within <see cref="CommandService"/>.
- /// </summary>
- public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value);
-
- /// <summary>
- /// Initializes a new <see cref="CommandService"/> class.
- /// </summary>
- public CommandService() : this(new CommandServiceConfig()) { }
-
- /// <summary>
- /// Initializes a new <see cref="CommandService"/> class with the provided configuration.
- /// </summary>
- /// <param name="config">The configuration class.</param>
- /// <exception cref="InvalidOperationException">
- /// The <see cref="RunMode"/> cannot be set to <see cref="RunMode.Default"/>.
- /// </exception>
- public CommandService(CommandServiceConfig config)
- {
- _caseSensitive = config.CaseSensitiveCommands;
- _throwOnError = config.ThrowOnError;
- _ignoreExtraArgs = config.IgnoreExtraArgs;
- _separatorChar = config.SeparatorChar;
- _defaultRunMode = config.DefaultRunMode;
- _quotationMarkAliasMap = (config.QuotationMarkAliasMap ?? new Dictionary<char, char>()).ToImmutableDictionary();
- if (_defaultRunMode == RunMode.Default)
- throw new InvalidOperationException("The default run mode cannot be set to Default.");
-
- _logManager = new LogManager(config.LogLevel);
- _logManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
- _cmdLogger = _logManager.CreateLogger("Command");
-
- _moduleLock = new SemaphoreSlim(1, 1);
- _typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>();
- _moduleDefs = new HashSet<ModuleInfo>();
- _map = new CommandMap(this);
- _typeReaders = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>>();
-
- _defaultTypeReaders = new ConcurrentDictionary<Type, TypeReader>();
- foreach (var type in PrimitiveParsers.SupportedTypes)
- {
- _defaultTypeReaders[type] = PrimitiveTypeReader.Create(type);
- _defaultTypeReaders[typeof(Nullable<>).MakeGenericType(type)] = NullableTypeReader.Create(type, _defaultTypeReaders[type]);
- }
-
- var tsreader = new TimeSpanTypeReader();
- _defaultTypeReaders[typeof(TimeSpan)] = tsreader;
- _defaultTypeReaders[typeof(TimeSpan?)] = NullableTypeReader.Create(typeof(TimeSpan), tsreader);
-
- _defaultTypeReaders[typeof(string)] =
- new PrimitiveTypeReader<string>((string x, out string y) => { y = x; return true; }, 0);
-
- var entityTypeReaders = ImmutableList.CreateBuilder<Tuple<Type, Type>>();
- entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IMessage), typeof(MessageTypeReader<>)));
- entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IChannel), typeof(ChannelTypeReader<>)));
- entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IRole), typeof(RoleTypeReader<>)));
- entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IUser), typeof(UserTypeReader<>)));
- _entityTypeReaders = entityTypeReaders.ToImmutable();
- }
-
- //Modules
- public async Task<ModuleInfo> CreateModuleAsync(string primaryAlias, Action<ModuleBuilder> buildFunc)
- {
- await _moduleLock.WaitAsync().ConfigureAwait(false);
- try
- {
- var builder = new ModuleBuilder(this, null, primaryAlias);
- buildFunc(builder);
-
- var module = builder.Build(this, null);
-
- return LoadModuleInternal(module);
- }
- finally
- {
- _moduleLock.Release();
- }
- }
-
- /// <summary>
- /// Add a command module from a <see cref="Type" />.
- /// </summary>
- /// <example>
- /// <para>The following example registers the module <c>MyModule</c> to <c>commandService</c>.</para>
- /// <code language="cs">
- /// await commandService.AddModuleAsync<MyModule>(serviceProvider);
- /// </code>
- /// </example>
- /// <typeparam name="T">The type of module.</typeparam>
- /// <param name="services">The <see cref="IServiceProvider"/> for your dependency injection solution if using one; otherwise, pass <c>null</c>.</param>
- /// <exception cref="ArgumentException">This module has already been added.</exception>
- /// <exception cref="InvalidOperationException">
- /// The <see cref="ModuleInfo"/> fails to be built; an invalid type may have been provided.
- /// </exception>
- /// <returns>
- /// A task that represents the asynchronous operation for adding the module. The task result contains the
- /// built module.
- /// </returns>
- public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services);
-
- /// <summary>
- /// Adds a command module from a <see cref="Type" />.
- /// </summary>
- /// <param name="type">The type of module.</param>
- /// <param name="services">The <see cref="IServiceProvider" /> for your dependency injection solution if using one; otherwise, pass <c>null</c> .</param>
- /// <exception cref="ArgumentException">This module has already been added.</exception>
- /// <exception cref="InvalidOperationException">
- /// The <see cref="ModuleInfo"/> fails to be built; an invalid type may have been provided.
- /// </exception>
- /// <returns>
- /// A task that represents the asynchronous operation for adding the module. The task result contains the
- /// built module.
- /// </returns>
- public async Task<ModuleInfo> AddModuleAsync(Type type, IServiceProvider services)
- {
- services = services ?? EmptyServiceProvider.Instance;
-
- await _moduleLock.WaitAsync().ConfigureAwait(false);
- try
- {
- var typeInfo = type.GetTypeInfo();
-
- if (_typedModuleDefs.ContainsKey(type))
- throw new ArgumentException("This module has already been added.");
-
- var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault();
-
- if (module.Value == default(ModuleInfo))
- throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?");
-
- _typedModuleDefs[module.Key] = module.Value;
-
- return LoadModuleInternal(module.Value);
- }
- finally
- {
- _moduleLock.Release();
- }
- }
- /// <summary>
- /// Add command modules from an <see cref="Assembly"/>.
- /// </summary>
- /// <param name="assembly">The <see cref="Assembly"/> containing command modules.</param>
- /// <param name="services">The <see cref="IServiceProvider"/> for your dependency injection solution if using one; otherwise, pass <c>null</c>.</param>
- /// <returns>
- /// A task that represents the asynchronous operation for adding the command modules. The task result
- /// contains an enumerable collection of modules added.
- /// </returns>
- public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly, IServiceProvider services)
- {
- services = services ?? EmptyServiceProvider.Instance;
-
- await _moduleLock.WaitAsync().ConfigureAwait(false);
- try
- {
- var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false);
- var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this, services).ConfigureAwait(false);
-
- foreach (var info in moduleDefs)
- {
- _typedModuleDefs[info.Key] = info.Value;
- LoadModuleInternal(info.Value);
- }
-
- return moduleDefs.Select(x => x.Value).ToImmutableArray();
- }
- finally
- {
- _moduleLock.Release();
- }
- }
- private ModuleInfo LoadModuleInternal(ModuleInfo module)
- {
- _moduleDefs.Add(module);
-
- foreach (var command in module.Commands)
- _map.AddCommand(command);
-
- foreach (var submodule in module.Submodules)
- LoadModuleInternal(submodule);
-
- return module;
- }
- /// <summary>
- /// Removes the command module.
- /// </summary>
- /// <param name="module">The <see cref="ModuleInfo" /> to be removed from the service.</param>
- /// <returns>
- /// A task that represents the asynchronous removal operation. The task result contains a value that
- /// indicates whether the <paramref name="module"/> is successfully removed.
- /// </returns>
- public async Task<bool> RemoveModuleAsync(ModuleInfo module)
- {
- await _moduleLock.WaitAsync().ConfigureAwait(false);
- try
- {
- return RemoveModuleInternal(module);
- }
- finally
- {
- _moduleLock.Release();
- }
- }
- /// <summary>
- /// Removes the command module.
- /// </summary>
- /// <typeparam name="T">The <see cref="Type"/> of the module.</typeparam>
- /// <returns>
- /// A task that represents the asynchronous removal operation. The task result contains a value that
- /// indicates whether the module is successfully removed.
- /// </returns>
- public Task<bool> RemoveModuleAsync<T>() => RemoveModuleAsync(typeof(T));
- /// <summary>
- /// Removes the command module.
- /// </summary>
- /// <param name="type">The <see cref="Type"/> of the module.</param>
- /// <returns>
- /// A task that represents the asynchronous removal operation. The task result contains a value that
- /// indicates whether the module is successfully removed.
- /// </returns>
- public async Task<bool> RemoveModuleAsync(Type type)
- {
- await _moduleLock.WaitAsync().ConfigureAwait(false);
- try
- {
- if (!_typedModuleDefs.TryRemove(type, out var module))
- return false;
-
- return RemoveModuleInternal(module);
- }
- finally
- {
- _moduleLock.Release();
- }
- }
- private bool RemoveModuleInternal(ModuleInfo module)
- {
- if (!_moduleDefs.Remove(module))
- return false;
-
- foreach (var cmd in module.Commands)
- _map.RemoveCommand(cmd);
-
- foreach (var submodule in module.Submodules)
- {
- RemoveModuleInternal(submodule);
- }
-
- return true;
- }
-
- //Type Readers
- /// <summary>
- /// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
- /// type.
- /// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will
- /// also be added.
- /// If a default <see cref="TypeReader" /> exists for <typeparamref name="T" />, a warning will be logged
- /// and the default <see cref="TypeReader" /> will be replaced.
- /// </summary>
- /// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam>
- /// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param>
- public void AddTypeReader<T>(TypeReader reader)
- => AddTypeReader(typeof(T), reader);
- /// <summary>
- /// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
- /// type.
- /// If <paramref name="type" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> for the
- /// value type will also be added.
- /// If a default <see cref="TypeReader" /> exists for <paramref name="type" />, a warning will be logged and
- /// the default <see cref="TypeReader" /> will be replaced.
- /// </summary>
- /// <param name="type">A <see cref="Type" /> instance for the type to be read.</param>
- /// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param>
- public void AddTypeReader(Type type, TypeReader reader)
- {
- if (_defaultTypeReaders.ContainsKey(type))
- _ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." +
- "To suppress this message, use AddTypeReader<T>(reader, true).");
- AddTypeReader(type, reader, true);
- }
- /// <summary>
- /// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
- /// type.
- /// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will
- /// also be added.
- /// </summary>
- /// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam>
- /// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param>
- /// <param name="replaceDefault">
- /// Defines whether the <see cref="TypeReader"/> should replace the default one for
- /// <see cref="Type" /> if it exists.
- /// </param>
- public void AddTypeReader<T>(TypeReader reader, bool replaceDefault)
- => AddTypeReader(typeof(T), reader, replaceDefault);
- /// <summary>
- /// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
- /// type.
- /// If <paramref name="type" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> for the
- /// value type will also be added.
- /// </summary>
- /// <param name="type">A <see cref="Type" /> instance for the type to be read.</param>
- /// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param>
- /// <param name="replaceDefault">
- /// Defines whether the <see cref="TypeReader"/> should replace the default one for <see cref="Type" /> if
- /// it exists.
- /// </param>
- public void AddTypeReader(Type type, TypeReader reader, bool replaceDefault)
- {
- if (replaceDefault && HasDefaultTypeReader(type))
- {
- _defaultTypeReaders.AddOrUpdate(type, reader, (k, v) => reader);
- if (type.GetTypeInfo().IsValueType)
- {
- var nullableType = typeof(Nullable<>).MakeGenericType(type);
- var nullableReader = NullableTypeReader.Create(type, reader);
- _defaultTypeReaders.AddOrUpdate(nullableType, nullableReader, (k, v) => nullableReader);
- }
- }
- else
- {
- var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>());
- readers[reader.GetType()] = reader;
-
- if (type.GetTypeInfo().IsValueType)
- AddNullableTypeReader(type, reader);
- }
- }
- internal bool HasDefaultTypeReader(Type type)
- {
- if (_defaultTypeReaders.ContainsKey(type))
- return true;
-
- var typeInfo = type.GetTypeInfo();
- if (typeInfo.IsEnum)
- return true;
- return _entityTypeReaders.Any(x => type == x.Item1 || typeInfo.ImplementedInterfaces.Contains(x.Item2));
- }
- internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader)
- {
- var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary<Type, TypeReader>());
- var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader);
- readers[nullableReader.GetType()] = nullableReader;
- }
- internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
- {
- if (_typeReaders.TryGetValue(type, out var definedTypeReaders))
- return definedTypeReaders;
- return null;
- }
- internal TypeReader GetDefaultTypeReader(Type type)
- {
- if (_defaultTypeReaders.TryGetValue(type, out var reader))
- return reader;
- var typeInfo = type.GetTypeInfo();
-
- //Is this an enum?
- if (typeInfo.IsEnum)
- {
- reader = EnumTypeReader.GetReader(type);
- _defaultTypeReaders[type] = reader;
- return reader;
- }
-
- //Is this an entity?
- for (int i = 0; i < _entityTypeReaders.Count; i++)
- {
- if (type == _entityTypeReaders[i].Item1 || typeInfo.ImplementedInterfaces.Contains(_entityTypeReaders[i].Item1))
- {
- reader = Activator.CreateInstance(_entityTypeReaders[i].Item2.MakeGenericType(type)) as TypeReader;
- _defaultTypeReaders[type] = reader;
- return reader;
- }
- }
- return null;
- }
-
- //Execution
- /// <summary>
- /// Searches for the command.
- /// </summary>
- /// <param name="context">The context of the command.</param>
- /// <param name="argPos">The position of which the command starts at.</param>
- /// <returns>The result containing the matching commands.</returns>
- public SearchResult Search(ICommandContext context, int argPos)
- => Search(context.Message.Content.Substring(argPos));
- /// <summary>
- /// Searches for the command.
- /// </summary>
- /// <param name="context">The context of the command.</param>
- /// <param name="input">The command string.</param>
- /// <returns>The result containing the matching commands.</returns>
- public SearchResult Search(ICommandContext context, string input)
- => Search(input);
- public SearchResult Search(string input)
- {
- string searchInput = _caseSensitive ? input : input.ToLowerInvariant();
- var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray();
-
- if (matches.Length > 0)
- return SearchResult.FromSuccess(input, matches);
- else
- return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.");
- }
-
- /// <summary>
- /// Executes the command.
- /// </summary>
- /// <param name="context">The context of the command.</param>
- /// <param name="argPos">The position of which the command starts at.</param>
- /// <param name="services">The service to be used in the command's dependency injection.</param>
- /// <param name="multiMatchHandling">The handling mode when multiple command matches are found.</param>
- /// <returns>
- /// A task that represents the asynchronous execution operation. The task result contains the result of the
- /// command execution.
- /// </returns>
- public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
- => ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling);
- /// <summary>
- /// Executes the command.
- /// </summary>
- /// <param name="context">The context of the command.</param>
- /// <param name="input">The command string.</param>
- /// <param name="services">The service to be used in the command's dependency injection.</param>
- /// <param name="multiMatchHandling">The handling mode when multiple command matches are found.</param>
- /// <returns>
- /// A task that represents the asynchronous execution operation. The task result contains the result of the
- /// command execution.
- /// </returns>
- public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
- {
- services = services ?? EmptyServiceProvider.Instance;
-
- var searchResult = Search(input);
- if (!searchResult.IsSuccess)
- {
- await _commandExecutedEvent.InvokeAsync(Optional.Create<CommandInfo>(), context, searchResult).ConfigureAwait(false);
- return searchResult;
- }
-
-
- var commands = searchResult.Commands;
- var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();
-
- foreach (var match in commands)
- {
- preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false);
- }
-
- var successfulPreconditions = preconditionResults
- .Where(x => x.Value.IsSuccess)
- .ToArray();
-
- if (successfulPreconditions.Length == 0)
- {
- //All preconditions failed, return the one from the highest priority command
- var bestCandidate = preconditionResults
- .OrderByDescending(x => x.Key.Command.Priority)
- .FirstOrDefault(x => !x.Value.IsSuccess);
-
- await _commandExecutedEvent.InvokeAsync(bestCandidate.Key.Command, context, bestCandidate.Value).ConfigureAwait(false);
- return bestCandidate.Value;
- }
-
- //If we get this far, at least one precondition was successful.
-
- var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
- foreach (var pair in successfulPreconditions)
- {
- var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false);
-
- if (parseResult.Error == CommandError.MultipleMatches)
- {
- IReadOnlyList<TypeReaderValue> argList, paramList;
- switch (multiMatchHandling)
- {
- case MultiMatchHandling.Best:
- argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
- paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
- parseResult = ParseResult.FromSuccess(argList, paramList);
- break;
- }
- }
-
- parseResultsDict[pair.Key] = parseResult;
- }
-
- // Calculates the 'score' of a command given a parse result
- float CalculateScore(CommandMatch match, ParseResult parseResult)
- {
- float argValuesScore = 0, paramValuesScore = 0;
-
- if (match.Command.Parameters.Count > 0)
- {
- var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
- var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
-
- argValuesScore = argValuesSum / match.Command.Parameters.Count;
- paramValuesScore = paramValuesSum / match.Command.Parameters.Count;
- }
-
- var totalArgsScore = (argValuesScore + paramValuesScore) / 2;
- return match.Command.Priority + totalArgsScore * 0.99f;
- }
-
- //Order the parse results by their score so that we choose the most likely result to execute
- var parseResults = parseResultsDict
- .OrderByDescending(x => CalculateScore(x.Key, x.Value));
-
- var successfulParses = parseResults
- .Where(x => x.Value.IsSuccess)
- .ToArray();
-
- if (successfulParses.Length == 0)
- {
- //All parses failed, return the one from the highest priority command, using score as a tie breaker
- var bestMatch = parseResults
- .FirstOrDefault(x => !x.Value.IsSuccess);
-
- await _commandExecutedEvent.InvokeAsync(bestMatch.Key.Command, context, bestMatch.Value).ConfigureAwait(false);
- return bestMatch.Value;
- }
-
- //If we get this far, at least one parse was successful. Execute the most likely overload.
- var chosenOverload = successfulParses[0];
- var result = await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false);
- if (!result.IsSuccess && !(result is RuntimeResult || result is ExecuteResult)) // succesful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deffered execution)
- await _commandExecutedEvent.InvokeAsync(chosenOverload.Key.Command, context, result);
- return result;
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_isDisposed)
- {
- if (disposing)
- {
- _moduleLock?.Dispose();
- }
-
- _isDisposed = true;
- }
- }
-
- void IDisposable.Dispose()
- {
- Dispose(true);
- }
- }
- }
|