Skip to content

Command arguments #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Miki.Framework.Arguments/ArgumentParseProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public ArgumentParseProvider()
SeedAssembly(GetType().Assembly);
}

public IReadOnlyList<IArgumentParser> Parsers => parsers;

public object Peek(IArgumentPack p, Type type)
{
int cursor = p.Cursor;
Expand Down
2 changes: 1 addition & 1 deletion src/Miki.Framework.Commands/Attributes/CommandAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
public class CommandAttribute : Attribute
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 'CommandAttribute.Aliases'
public IReadOnlyCollection<string> Aliases { get; }
public IReadOnlyList<string> Aliases { get; }
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 'CommandAttribute.Aliases'

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Miki.Framework.Commands/CommandPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Threading.Tasks;
using Miki.Discord.Common;
using Miki.Framework.Models;
using Miki.Framework.Commands.Pipelines;
using Miki.Logging;

Expand All @@ -28,7 +29,7 @@ internal CommandPipeline(
public async ValueTask ExecuteAsync(IDiscordMessage data)
{
var sw = Stopwatch.StartNew();
using ContextObject contextObj = new ContextObject(services);
using ContextObject contextObj = new ContextObject(services, new DiscordMessage(data));
int index = 0;

Func<ValueTask> nextFunc = null;
Expand Down
13 changes: 12 additions & 1 deletion src/Miki.Framework.Commands/CommandPipelineBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
/// </summary>
public class CommandPipelineBuilder
{
private readonly List<IPipelineStage> stages = new List<IPipelineStage>();

/// <summary>
/// Services that can be used throughout the command pipeline.
/// </summary>
public IServiceProvider Services { get; }

private readonly List<IPipelineStage> stages = new List<IPipelineStage>();
public IReadOnlyList<IPipelineStage> Stages => stages;

/// <summary>
/// Creates a new CommandPipelineBuilder.
Expand Down Expand Up @@ -42,5 +44,14 @@ public CommandPipelineBuilder UseStage(IPipelineStage stage)
stages.Add(stage);
return this;
}

/// <summary>
/// Initializes a pipeline stage as a runnable stage in the pipeline.
/// </summary>
public CommandPipelineBuilder UseStage<T>()
where T : class, IPipelineStage
{
return UseStage(Services.GetOrCreateService<T>());
}
}
}
35 changes: 29 additions & 6 deletions src/Miki.Framework.Commands/CommandTree.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Miki.Framework.Commands
using System.Collections.Generic;
using System.Linq;

namespace Miki.Framework.Commands
{
using Miki.Framework.Arguments;
using Miki.Framework.Commands.Nodes;
Expand All @@ -12,12 +15,32 @@ public CommandTree()
Root = new NodeRoot();
}

public Node GetCommand(IArgumentPack pack)
public Node GetCommand(IArgumentPack pack)
{
var result = GetCommands(pack).FirstOrDefault();

if (result.Node == null)
{
return null;
}

pack.SetCursor(result.CursorPosition);
return result.Node;
}

public Node GetCommand(string name)
{
return GetCommand(new ArgumentPack(name.Split(' ')));
}

public IEnumerable<NodeResult> GetCommands(IArgumentPack pack)
{
return Root.FindCommand(pack);
return Root.FindCommands(pack);
}

public Node GetCommand(string name)
=> GetCommand(new ArgumentPack(name.Split(' ')));
}
public IEnumerable<NodeResult> GetCommands(string name)
{
return GetCommands(new ArgumentPack(name.Split(' ')));
}
}
}
166 changes: 41 additions & 125 deletions src/Miki.Framework.Commands/CommandTreeBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,136 +1,52 @@
namespace Miki.Framework.Commands
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Miki.Framework.Commands
{
using Miki.Framework.Commands.Nodes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

public class CommandTreeBuilder
{
[Obsolete("use 'CommandTreeBuilder::AddCommandBuildStep()' intead")]
public event Action<NodeContainer, IServiceProvider> OnContainerLoaded;
private readonly List<Type> types = new List<Type>();

private readonly IServiceProvider services;

private List<ICommandBuildStep> buildSteps;

public CommandTreeBuilder(IServiceProvider services)
public CommandTreeBuilder(IServiceCollection services)
{
this.services = services;
Services = services;
}

public CommandTreeBuilder AddCommandBuildStep(ICommandBuildStep buildStep)
{
if(buildSteps == null)
{
buildSteps = new List<ICommandBuildStep>();
}
buildSteps.Add(buildStep);
return this;
}

public CommandTree Create(Assembly assembly)

public IServiceCollection Services { get; }

public CommandTreeBuilder AddType(Type type)
{
var allTypes = assembly.GetTypes()
.Where(x => x.GetCustomAttribute<ModuleAttribute>() != null);
var root = new CommandTree();
foreach(var t in allTypes)
{
var module = LoadModule(t, root.Root);
if(module != null)
{
root.Root.Children.Add(module);
}
}
return root;
}

private NodeContainer LoadModule(Type t, NodeContainer parent)
{
var moduleAttrib = t.GetCustomAttribute<ModuleAttribute>();
if(moduleAttrib == null)
{
throw new InvalidOperationException("Modules must have a valid ModuleAttribute.");
}

NodeContainer module = new NodeModule(moduleAttrib.Name, parent, services, t);
OnContainerLoaded?.Invoke(module, services);

var allCommands = t.GetNestedTypes(BindingFlags.NonPublic | BindingFlags.Public)
.Where(x => x.GetCustomAttribute<CommandAttribute>() != null);
foreach(var c in allCommands)
{
module.Children.Add(LoadCommand(c, module));
}

var allSingleCommands = t.GetMethods()
.Where(x => x.GetCustomAttribute<CommandAttribute>() != null);
foreach(var c in allSingleCommands)
{
module.Children.Add(LoadCommand(c, module));
}

return module;
types.Add(type);
return this;
}
private Node LoadCommand(Type t, NodeContainer parent)
{
var commandAttrib = t.GetCustomAttribute<CommandAttribute>();
if(commandAttrib == null)
{
throw new InvalidOperationException(
$"Multi command of type '{t.ToString()}' must have a valid CommandAttribute.");
}

if(commandAttrib.Aliases?.Count() == 0)
{
throw new InvalidOperationException(
$"Multi commands cannot have an invalid name.");
}

var multiCommand = new NodeNestedExecutable(commandAttrib.AsMetadata(), parent, services, t);
OnContainerLoaded?.Invoke(multiCommand, services);

var allCommands = t.GetNestedTypes()
.Where(x => x.GetCustomAttribute<CommandAttribute>() != null);
foreach(var c in allCommands)
{
multiCommand.Children.Add(LoadCommand(c, multiCommand));
}

var allSingleCommands = t.GetMethods()
.Where(x => x.GetCustomAttribute<CommandAttribute>() != null);
foreach(var c in allSingleCommands)
{
var attrib = c.GetCustomAttribute<CommandAttribute>();
if(attrib.Aliases == null
|| attrib.Aliases.Count() == 0)
{
var node = LoadCommand(c, multiCommand);
if(node is IExecutable execNode)
{
multiCommand.SetDefaultExecution(async (e)
=> await execNode.ExecuteAsync(e));
}
}
else
{
multiCommand.Children.Add(LoadCommand(c, multiCommand));
}
}
return multiCommand;
}
private Node LoadCommand(MethodInfo m, NodeContainer parent)
{
var commandAttrib = m.GetCustomAttribute<CommandAttribute>();
var command = new NodeExecutable(commandAttrib.AsMetadata(), parent, m);

public CommandTreeBuilder AddAssembly(Assembly assembly)
{
types.AddRange(assembly.GetTypes().Where(x => x.GetCustomAttribute<ModuleAttribute>() != null));
return this;
}

public CommandTree Build(IServiceProvider provider)
{
var root = new CommandTree();
var compiler = ActivatorUtilities.CreateInstance<CommandTreeCompiler>(provider);

foreach (var type in types)
{
var module = compiler.LoadModule(type, root.Root);

if (module != null)
{
root.Root.Children.Add(module);
}
}

return root;
}

if(m.ReturnType != typeof(Task))
{
throw new Exception("Methods with attribute 'Command' require to be Tasks.");
}
return command;
}
}
}
Loading