Merge branch 'commands/validate-get-best-match' of https://github.com/siscodeorg/Discord.Net into siscodeorg-commands/validate-get-best-match
This commit is contained in:
@@ -517,57 +517,43 @@ namespace Discord.Commands
|
||||
services ??= EmptyServiceProvider.Instance;
|
||||
|
||||
var searchResult = Search(input);
|
||||
if (!searchResult.IsSuccess)
|
||||
|
||||
var validationResult = await ValidateAndGetBestMatch(searchResult, context, services, multiMatchHandling);
|
||||
|
||||
if (validationResult is SearchResult result)
|
||||
{
|
||||
await _commandExecutedEvent.InvokeAsync(Optional.Create<CommandInfo>(), context, searchResult).ConfigureAwait(false);
|
||||
return searchResult;
|
||||
await _commandExecutedEvent.InvokeAsync(Optional.Create<CommandInfo>(), context, result).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
var commands = searchResult.Commands;
|
||||
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();
|
||||
|
||||
foreach (var match in commands)
|
||||
if (validationResult is MatchResult matchResult)
|
||||
{
|
||||
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false);
|
||||
return await HandleCommandPipeline(matchResult, context, services);
|
||||
}
|
||||
|
||||
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;
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
//If we get this far, at least one precondition was successful.
|
||||
private async Task<IResult> HandleCommandPipeline(MatchResult matchResult, ICommandContext context, IServiceProvider services)
|
||||
{
|
||||
if (!matchResult.IsSuccess)
|
||||
return matchResult;
|
||||
|
||||
var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
|
||||
foreach (var pair in successfulPreconditions)
|
||||
if (matchResult.Pipeline is ParseResult parseResult)
|
||||
{
|
||||
var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false);
|
||||
var executeResult = await matchResult.Match.Value.ExecuteAsync(context, parseResult, services);
|
||||
|
||||
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;
|
||||
}
|
||||
if (!executeResult.IsSuccess && !(executeResult is RuntimeResult || executeResult is ExecuteResult)) // succesful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deffered execution)
|
||||
await _commandExecutedEvent.InvokeAsync(matchResult.Match.Value.Command, context, executeResult);
|
||||
return executeResult;
|
||||
}
|
||||
|
||||
parseResultsDict[pair.Key] = parseResult;
|
||||
if (matchResult.Pipeline is PreconditionResult preconditionResult)
|
||||
{
|
||||
await _commandExecutedEvent.InvokeAsync(matchResult.Match.Value.Command, context, preconditionResult).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return matchResult;
|
||||
}
|
||||
|
||||
// Calculates the 'score' of a command given a parse result
|
||||
@@ -588,30 +574,81 @@ namespace Discord.Commands
|
||||
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));
|
||||
/// <summary>
|
||||
/// Validates and gets the best <see cref="CommandMatch"/> from a specified <see cref="SearchResult"/>
|
||||
/// </summary>
|
||||
/// <param name="matches">The SearchResult.</param>
|
||||
/// <param name="context">The context of the command.</param>
|
||||
/// <param name="provider">The service provider to be used on 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 validation operation. The task result contains the result of the
|
||||
/// command validation as a <see cref="MatchResult"/> or a <see cref="SearchResult"/> if no matches were found.</returns>
|
||||
public async Task<IResult> ValidateAndGetBestMatch(SearchResult matches, ICommandContext context, IServiceProvider provider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
|
||||
{
|
||||
if (!matches.IsSuccess)
|
||||
return matches;
|
||||
|
||||
var successfulParses = parseResults
|
||||
var commands = matches.Commands;
|
||||
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();
|
||||
|
||||
foreach (var command in commands)
|
||||
{
|
||||
preconditionResults[command] = await command.CheckPreconditionsAsync(context, provider);
|
||||
}
|
||||
|
||||
var successfulPreconditions = preconditionResults
|
||||
.Where(x => x.Value.IsSuccess)
|
||||
.ToArray();
|
||||
|
||||
if (successfulParses.Length == 0)
|
||||
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);
|
||||
return MatchResult.FromSuccess(bestCandidate.Key,bestCandidate.Value);
|
||||
}
|
||||
|
||||
var parseResults = new Dictionary<CommandMatch, ParseResult>();
|
||||
|
||||
foreach (var pair in successfulPreconditions)
|
||||
{
|
||||
var parseResult = await pair.Key.ParseAsync(context, matches, pair.Value, provider).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;
|
||||
}
|
||||
}
|
||||
|
||||
parseResults[pair.Key] = parseResult;
|
||||
}
|
||||
|
||||
var weightedParseResults = parseResults
|
||||
.OrderByDescending(x => CalculateScore(x.Key, x.Value));
|
||||
|
||||
var successfulParses = weightedParseResults
|
||||
.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;
|
||||
return MatchResult.FromSuccess(bestMatch.Key,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)) // successful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deferred execution)
|
||||
await _commandExecutedEvent.InvokeAsync(chosenOverload.Key.Command, context, result);
|
||||
return result;
|
||||
|
||||
return MatchResult.FromSuccess(chosenOverload.Key,chosenOverload.Value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
47
src/Discord.Net.Commands/Results/MatchResult.cs
Normal file
47
src/Discord.Net.Commands/Results/MatchResult.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
public class MatchResult : IResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the command that may have matched during the command execution.
|
||||
/// </summary>
|
||||
public CommandMatch? Match { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets on which pipeline stage the command may have matched or failed.
|
||||
/// </summary>
|
||||
public IResult? Pipeline { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public CommandError? Error { get; }
|
||||
/// <inheritdoc />
|
||||
public string ErrorReason { get; }
|
||||
/// <inheritdoc />
|
||||
public bool IsSuccess => !Error.HasValue;
|
||||
|
||||
private MatchResult(CommandMatch? match, IResult? pipeline, CommandError? error, string errorReason)
|
||||
{
|
||||
Match = match;
|
||||
Error = error;
|
||||
Pipeline = pipeline;
|
||||
ErrorReason = errorReason;
|
||||
}
|
||||
|
||||
public static MatchResult FromSuccess(CommandMatch match, IResult pipeline)
|
||||
=> new MatchResult(match,pipeline,null, null);
|
||||
public static MatchResult FromError(CommandError error, string reason)
|
||||
=> new MatchResult(null,null,error, reason);
|
||||
public static MatchResult FromError(Exception ex)
|
||||
=> FromError(CommandError.Exception, ex.Message);
|
||||
public static MatchResult FromError(IResult result)
|
||||
=> new MatchResult(null, null,result.Error, result.ErrorReason);
|
||||
public static MatchResult FromError(IResult pipeline, CommandError error, string reason)
|
||||
=> new MatchResult(null, pipeline, error, reason);
|
||||
|
||||
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||
private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user