Skip to content
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
17 changes: 1 addition & 16 deletions Remora.Plugins.Abstractions/Attributes/RemoraPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,4 @@ namespace Remora.Plugins.Abstractions.Attributes;
/// </summary>
[PublicAPI]
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class RemoraPlugin : Attribute
{
/// <summary>
/// Gets the plugin descriptor that the assembly exports.
/// </summary>
public Type PluginDescriptor { get; }

/// <summary>
/// Initializes a new instance of the <see cref="RemoraPlugin"/> class.
/// </summary>
/// <param name="pluginDescriptor">The descriptor type.</param>
public RemoraPlugin(Type pluginDescriptor)
{
this.PluginDescriptor = pluginDescriptor;
}
}
public sealed class RemoraPlugin : Attribute;
3 changes: 1 addition & 2 deletions Remora.Plugins.Abstractions/IMigratablePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ public interface IMigratablePlugin : IPluginDescriptor
/// <summary>
/// Performs any migrations required by the plugin.
/// </summary>
/// <param name="serviceProvider">The available services.</param>
/// <param name="ct">The cancellation token for this operation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task<Result> MigrateAsync(IServiceProvider serviceProvider, CancellationToken ct = default);
Task<Result> MigrateAsync(CancellationToken ct = default);
}
11 changes: 7 additions & 4 deletions Remora.Plugins.Abstractions/IPluginDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
//

using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
Expand Down Expand Up @@ -48,20 +49,22 @@ public interface IPluginDescriptor
/// <summary>
/// Gets the version of the plugin.
/// </summary>
Version Version { get; }
Version Version => Assembly.GetAssembly(GetType())?.GetName().Version ?? new Version(1, 0, 0);

/// <summary>
/// Configures services provided by the plugin in the application's service collection.
/// </summary>
/// <param name="serviceCollection">The service collection.</param>
/// <returns>A result that may or may not have succeeded.</returns>
Result ConfigureServices(IServiceCollection serviceCollection);
static virtual IServiceCollection ConfigureServices(IServiceCollection serviceCollection) => serviceCollection;

/// <summary>
/// Performs any post-registration initialization required by the plugin.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="ct">The cancellation token for this operation.</param>
/// <returns>A result that may or may not have succeeded.</returns>
ValueTask<Result> InitializeAsync(IServiceProvider serviceProvider, CancellationToken ct = default);
ValueTask<Result> InitializeAsync(CancellationToken ct = default);

/// <inheritdoc cref="object.ToString"/>
string ToString() => this.Name;
}
65 changes: 0 additions & 65 deletions Remora.Plugins.Abstractions/PluginDescriptor.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Project Sdk="Remora.Sdk">
<PropertyGroup>
<VersionPrefix>5.0.1</VersionPrefix>
<TargetNetStandard>false</TargetNetStandard>
<LibraryFrameworks>net8.0;net9.0</LibraryFrameworks>
<Description>
Abstract representations of a plugin system.
</Description>
Expand Down
50 changes: 10 additions & 40 deletions Remora.Plugins/PluginTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Remora.Plugins.Abstractions;
using Remora.Plugins.Errors;
using Remora.Results;
Expand All @@ -37,55 +36,27 @@ namespace Remora.Plugins;
/// <summary>
/// Represents a tree of plugins, ordered by their dependencies.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="PluginTree"/> class.
/// </remarks>
/// <param name="branches">The dependency branches.</param>
[PublicAPI]
public sealed class PluginTree
public sealed class PluginTree(List<PluginTreeNode>? branches = null)
{
private readonly List<PluginTreeNode> _branches;
private readonly List<PluginTreeNode> _branches = branches ?? [];

/// <summary>
/// Gets the root nodes of the identified plugin dependency branches. The root node is considered to be the
/// application itself, which is implicitly initialized.
/// </summary>
public IReadOnlyCollection<PluginTreeNode> Branches => _branches;

/// <summary>
/// Initializes a new instance of the <see cref="PluginTree"/> class.
/// </summary>
/// <param name="branches">The dependency branches.</param>
public PluginTree(List<PluginTreeNode>? branches = null)
{
_branches = branches ?? new List<PluginTreeNode>();
}

/// <summary>
/// Configures the services required by the plugins.
/// </summary>
/// <param name="serviceCollection">The service collection to configure.</param>
/// <returns>A result which may or may not have succeeded.</returns>
public Result ConfigureServices(IServiceCollection serviceCollection)
{
var results = Walk
(
node => new PluginConfigurationFailed
(
node.Plugin,
"One or more of the plugin's dependencies failed to configure their services."
),
node => node.Plugin.ConfigureServices(serviceCollection)
).ToList();

return results.Any(r => !r.IsSuccess)
? new AggregateError(results.Where(r => !r.IsSuccess).Cast<IResult>().ToList())
: Result.FromSuccess();
}

/// <summary>
/// Initializes the plugins in the tree.
/// </summary>
/// <param name="services">The available services.</param>
/// <param name="ct">The cancellation token for this operation.</param>
/// <returns>A result which may or may not have succeeded.</returns>
public async Task<Result> InitializeAsync(IServiceProvider services, CancellationToken ct = default)
public async Task<Result> InitializeAsync(CancellationToken ct = default)
{
var results = await WalkAsync
(
Expand All @@ -94,7 +65,7 @@ public async Task<Result> InitializeAsync(IServiceProvider services, Cancellatio
node.Plugin,
"One or more of the plugin's dependencies failed to initialize."
),
async (node, c) => await node.Plugin.InitializeAsync(services, c),
async (node, c) => await node.Plugin.InitializeAsync(c),
ct: ct
).ToListAsync(ct);

Expand All @@ -106,10 +77,9 @@ public async Task<Result> InitializeAsync(IServiceProvider services, Cancellatio
/// <summary>
/// Migrates any persistent data stores of the plugins in the tree.
/// </summary>
/// <param name="services">The available services.</param>
/// <param name="ct">The cancellation token for this operation.</param>
/// <returns>A result which may or may not have succeeded.</returns>
public async Task<Result> MigrateAsync(IServiceProvider services, CancellationToken ct = default)
public async Task<Result> MigrateAsync(CancellationToken ct = default)
{
var results = await WalkAsync
(
Expand All @@ -125,7 +95,7 @@ public async Task<Result> MigrateAsync(IServiceProvider services, CancellationTo
return Result.FromSuccess();
}

return await migratablePlugin.MigrateAsync(services, c);
return await migratablePlugin.MigrateAsync(c);
},
ct: ct
).ToListAsync(ct);
Expand Down
69 changes: 69 additions & 0 deletions Remora.Plugins/PluginTreeBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// PluginTreeBuilder.cs
//
// Author:
// Jarl Gullberg <jarl.gullberg@gmail.com>
//
// Copyright (c) Jarl Gullberg
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Remora.Plugins.Abstractions;

namespace Remora.Plugins
{
/// <summary>
/// A type that facilitates the creation of a <see cref="PluginTree"/>.
/// </summary>
/// <param name="filter">A filter predicate which determines if the plugin should be loaded.</param>
[PublicAPI]
public sealed class PluginTreeBuilder(Predicate<IPluginDescriptor> filter)
{
private readonly List<PluginTreeNodeBuilder> _treeNodeBuilders = [];

/// <summary>
/// Adds a tree node builder to the collection.
/// </summary>
/// <param name="node">The node to add.</param>
public void AddTreeNode(PluginTreeNodeBuilder node)
{
_treeNodeBuilders.Add(node);
}

/// <summary>
/// Builds the <see cref="PluginTree"/>.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <returns>A new <see cref="PluginTree"/>.</returns>
[Pure]
[PublicAPI]
public PluginTree Build(IServiceProvider serviceProvider)
Comment thread Fixed
{
var tree = new PluginTree();
foreach (var node in _treeNodeBuilders)
{
var pluginTreeNode = node.Build(serviceProvider);
if (filter.Invoke(pluginTreeNode.Plugin))
{
tree.AddBranch(pluginTreeNode);
}
}
return tree;
}
}
}
4 changes: 2 additions & 2 deletions Remora.Plugins/PluginTreeNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public PluginTreeNode
)
{
this.Plugin = plugin;
_dependents = dependants ?? new List<PluginTreeNode>();
_dependents = dependants ?? [];
}

/// <summary>
Expand Down Expand Up @@ -94,6 +94,6 @@ public IEnumerable<PluginTreeNode> GetAllDependents()
/// <inheritdoc/>
public override string ToString()
{
return $"{this.Plugin} => ({string.Join(", ", _dependents.Select(d => d.Plugin))})";
return $"{this.Plugin} => ({string.Join(", ", _dependents.Select(d => d.Plugin.ToString()))})";
}
}
Loading
Loading