Skip to content

Added Microsoft.Extensions.Hosting support #18

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
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>());
}
}
}
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; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these public now? they shouldn't be used externally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you can easily add services when you make an extension method, e.g.:

public static class CommandExtensions
{
    public static CommandTreeBuilder AddWarningsModule(this CommandTreeBuilder builder)
    {
        builder.Services.AddScoped<IWarningService, WarningService>();
        builder.AddAssembly(typeof(CommandExtensions).Assembly);
        return builder;
    }
}

Now you can call the extension method AddWarningsModule and all the required services are added:

services.AddCommands(builder =>
{
    builder.AddWarningsModule();
});


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