refactor: new active command provider semantics

This allows for code to access the active IServiceCommand object, which
represents a subcommand that provides an implementation for a service
like Radarr or Sonarr.
recyclarr
Robert Dailey 3 years ago
parent 1a02cc0d4a
commit 173011b320

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using Autofac;
using FluentAssertions;
using NUnit.Framework;
using Trash.Command;
namespace Trash.Tests.Command
{
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class CliTypeActivatorTest
{
private class NonServiceCommandType
{
}
public class StubCommand : IServiceCommand
{
public bool Preview { get; }
public bool Debug { get; }
public List<string>? Config { get; }
}
[Test]
public void Resolve_NonServiceCommandType_NoActiveCommandSet()
{
var builder = new ContainerBuilder();
builder.RegisterType<NonServiceCommandType>();
var container = CompositionRoot.Setup(builder);
var createdType = CliTypeActivator.ResolveType(container, typeof(NonServiceCommandType));
Action act = () => _ = container.Resolve<IActiveServiceCommandProvider>().ActiveCommand;
createdType.Should().BeOfType<NonServiceCommandType>();
act.Should()
.Throw<InvalidOperationException>()
.WithMessage("The active command has not yet been determined");
}
[Test]
public void Resolve_ServiceCommandType_ActiveCommandSet()
{
var builder = new ContainerBuilder();
builder.RegisterType<StubCommand>();
var container = CompositionRoot.Setup(builder);
var createdType = CliTypeActivator.ResolveType(container, typeof(StubCommand));
var activeCommand = container.Resolve<IActiveServiceCommandProvider>().ActiveCommand;
activeCommand.Should().BeSameAs(createdType);
activeCommand.Should().BeOfType<StubCommand>();
}
}
}

@ -0,0 +1,16 @@
using System;
namespace Trash.Command
{
public class ActiveServiceCommandProvider : IActiveServiceCommandProvider
{
private IServiceCommand? _activeCommand;
public IServiceCommand ActiveCommand
{
get => _activeCommand ??
throw new InvalidOperationException("The active command has not yet been determined");
set => _activeCommand = value;
}
}
}

@ -0,0 +1,20 @@
using System;
using Autofac;
namespace Trash.Command
{
internal static class CliTypeActivator
{
public static object ResolveType(IContainer container, Type typeToResolve)
{
var instance = container.Resolve(typeToResolve);
if (instance.GetType().IsAssignableTo<IServiceCommand>())
{
var activeServiceProvider = container.Resolve<IActiveServiceCommandProvider>();
activeServiceProvider.ActiveCommand = (IServiceCommand)instance;
}
return instance;
}
}
}

@ -0,0 +1,7 @@
namespace Trash.Command
{
public interface IActiveServiceCommandProvider
{
IServiceCommand ActiveCommand { get; set; }
}
}

@ -2,7 +2,7 @@
namespace Trash.Command
{
public interface IBaseCommand
public interface IServiceCommand
{
bool Preview { get; }
bool Debug { get; }

@ -17,11 +17,11 @@ using YamlDotNet.Core;
namespace Trash.Command
{
public abstract class BaseCommand : ICommand, IBaseCommand
public abstract class ServiceCommand : ICommand, IServiceCommand
{
private readonly LoggingLevelSwitch _loggingLevelSwitch;
protected BaseCommand(ILogger logger, LoggingLevelSwitch loggingLevelSwitch)
protected ServiceCommand(ILogger logger, LoggingLevelSwitch loggingLevelSwitch)
{
_loggingLevelSwitch = loggingLevelSwitch;
Log = logger;

@ -4,6 +4,7 @@ using Autofac;
using CliFx;
using Serilog;
using Serilog.Core;
using Trash.Command;
using Trash.Config;
using Trash.Radarr.Api;
using Trash.Radarr.QualityDefinition;
@ -52,23 +53,43 @@ namespace Trash
builder.RegisterType<RadarrQualityDefinitionGuideParser>().As<IRadarrQualityDefinitionGuideParser>();
}
public static IContainer Setup()
private static void ConfigurationRegistrations(ContainerBuilder builder)
{
var builder = new ContainerBuilder();
builder.RegisterType<FileSystem>().As<IFileSystem>();
// Configuration
builder.RegisterType<ObjectFactory>().As<IObjectFactory>();
builder.RegisterGeneric(typeof(ConfigurationLoader<>)).As(typeof(IConfigurationLoader<>));
builder.RegisterGeneric(typeof(ConfigurationProvider<>))
.As(typeof(IConfigurationProvider<>))
.SingleInstance();
}
private static void CommandRegistrations(ContainerBuilder builder)
{
// Register all types deriving from CliFx's ICommand. These are all of our supported subcommands.
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.IsAssignableTo(typeof(ICommand)));
// Used to access the chosen command class. This is assigned from CliTypeActivator
builder.RegisterType<ActiveServiceCommandProvider>()
.As<IActiveServiceCommandProvider>()
.SingleInstance();
builder.Register(c => c.Resolve<IActiveServiceCommandProvider>().ActiveCommand)
.As<IServiceCommand>();
}
public static IContainer Setup()
{
return Setup(new ContainerBuilder());
}
public static IContainer Setup(ContainerBuilder builder)
{
builder.RegisterType<FileSystem>()
.As<IFileSystem>();
ConfigurationRegistrations(builder);
CommandRegistrations(builder);
SetupLogging(builder);
SonarrRegistrations(builder);
RadarrRegistrations(builder);

@ -29,7 +29,7 @@ namespace Trash.CreateConfig
"Path where the new YAML file should be created. Must include the filename (e.g. path/to/config.yml). " +
"File must not already exist. If not specified, uses the default path of `trash.yml` right next to the " +
"executable.")]
public string Path { get; [UsedImplicitly] set; } = BaseCommand.DefaultConfigPath;
public string Path { get; [UsedImplicitly] set; } = ServiceCommand.DefaultConfigPath;
public ValueTask ExecuteAsync(IConsole console)
{

@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Autofac;
using CliFx;
using Trash.Command;
namespace Trash
{
@ -15,7 +16,7 @@ namespace Trash
.AddCommandsFromThisAssembly()
.SetExecutableName(ThisAssembly.AssemblyName)
.SetVersion($"v{ThisAssembly.AssemblyInformationalVersion}")
.UseTypeActivator(type => _container.Resolve(type))
.UseTypeActivator(type => CliTypeActivator.ResolveType(_container, type))
.Build()
.RunAsync();
}

@ -2,7 +2,7 @@
namespace Trash.Radarr
{
public interface IRadarrCommand : IBaseCommand
public interface IRadarrCommand : IServiceCommand
{
}
}

@ -13,7 +13,7 @@ namespace Trash.Radarr
{
[Command("radarr", Description = "Perform operations on a Radarr instance")]
[UsedImplicitly]
public class RadarrCommand : BaseCommand, IRadarrCommand
public class RadarrCommand : ServiceCommand, IRadarrCommand
{
private readonly IConfigurationLoader<RadarrConfiguration> _configLoader;
private readonly Func<RadarrQualityDefinitionUpdater> _qualityUpdaterFactory;

@ -2,7 +2,7 @@
namespace Trash.Sonarr
{
public interface ISonarrCommand : IBaseCommand
public interface ISonarrCommand : IServiceCommand
{
}
}

@ -14,7 +14,7 @@ namespace Trash.Sonarr
{
[Command("sonarr", Description = "Perform operations on a Sonarr instance")]
[UsedImplicitly]
public class SonarrCommand : BaseCommand, ISonarrCommand
public class SonarrCommand : ServiceCommand, ISonarrCommand
{
private readonly IConfigurationLoader<SonarrConfiguration> _configLoader;
private readonly Func<ReleaseProfileUpdater> _profileUpdaterFactory;

Loading…
Cancel
Save