Commit 12a395d4 authored by Michael Herndon's avatar Michael Herndon

WORK, protoype console engine, DI modules, and xunit logging

parent 24910861
wiki/**
\ No newline at end of file
wiki/**
wiki
\ No newline at end of file
......@@ -46,6 +46,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NerdyMishka.Agent", "src\Ap
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NerdyMishka.Agent.Core", "src\Apps\Agent.Core\NerdyMishka.Agent.Core.csproj", "{9AB3DAE3-D669-42D4-A3BB-50187F94CDE1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NerdyMishka.Extensions.DependencyInjection.Modules", "src\Extensions\DependencyInjection.Modules\NerdyMishka.Extensions.DependencyInjection.Modules.csproj", "{D7C731AA-3A47-42A8-A9E6-EBAC2D9AC845}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -100,6 +102,10 @@ Global
{9AB3DAE3-D669-42D4-A3BB-50187F94CDE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AB3DAE3-D669-42D4-A3BB-50187F94CDE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AB3DAE3-D669-42D4-A3BB-50187F94CDE1}.Release|Any CPU.Build.0 = Release|Any CPU
{D7C731AA-3A47-42A8-A9E6-EBAC2D9AC845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7C731AA-3A47-42A8-A9E6-EBAC2D9AC845}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7C731AA-3A47-42A8-A9E6-EBAC2D9AC845}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7C731AA-3A47-42A8-A9E6-EBAC2D9AC845}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -125,6 +131,7 @@ Global
{07B4A7C1-57E6-48D2-810D-BB8F234EECFF} = {DB042499-08A3-4C34-9231-1372043915F2}
{9A92EBB7-F59F-46A3-A956-FA00C4093EEC} = {07B4A7C1-57E6-48D2-810D-BB8F234EECFF}
{9AB3DAE3-D669-42D4-A3BB-50187F94CDE1} = {07B4A7C1-57E6-48D2-810D-BB8F234EECFF}
{D7C731AA-3A47-42A8-A9E6-EBAC2D9AC845} = {6444BF0F-2EED-404A-AC00-CC756F4F4ED0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {16666A01-3CC0-4111-8A58-9A6CA34CD76C}
......
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
namespace NerdyMishka.Console
{
public class Command<T> : Command
{
private Func<T, Task<int>> command;
private Type[] argumentTypes = new Type[] { typeof(T)};
public Command(
Func<T, Task<int>> command,
string description = null,
string noun = null)
:base(description, noun)
{
this.command = command;
}
public override IReadOnlyCollection<Type> ArgumentsTypes => this.argumentTypes;
public override async Task<int> ExecuteAsync(object parameters, CancellationToken token = default(CancellationToken))
{
return await this.command((T)parameters);
}
}
public class Command : ICommand
{
private Func<object, Task<int>> command;
public virtual string Noun { get; }
public virtual string Description { get; }
public virtual IReadOnlyCollection<Type> ArgumentsTypes { get; }
public Command(
string description = null,
string noun = null)
{
this.Description = description;
this.Noun =noun;
}
public Command(
Func<object, Task<int>> command,
IReadOnlyCollection<Type> argumentsTypes,
string description = null,
string noun = null) :
this(description, noun)
{
this.command = command;
this.ArgumentsTypes = argumentsTypes;
}
public bool CanExecute(Type argumentType)
{
if(this.ArgumentsTypes == null || this.ArgumentsTypes.Count == 0)
return false;
return this.ArgumentsTypes.Contains(argumentType);
}
public virtual async Task<int> ExecuteAsync(object parameters, CancellationToken token = default)
{
return await this.command(parameters);
}
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using CommandLine;
using NerdyMishka.Text;
using System.Threading.Tasks;
namespace NerdyMishka.Console
{
public class ConsoleEngine
{
private IReadOnlyCollection<Assembly> assemblies;
private IReadOnlyCollection<string> directories;
private CompositionContainer container;
public class ConsoleEngineOptions
{
public IReadOnlyCollection<Assembly> Assemblies { get; set; }
public IReadOnlyCollection<string> Directories { get; set; }
}
public ConsoleEngine(ConsoleEngineOptions options = null, ServiceProvider provider)
{
options = options ?? new ConsoleEngineOptions() {
Assemblies = new List<Assembly>(){ typeof(ConsoleEngine).Assembly }
};
this.assemblies = options.Assemblies;
this.directories = options.Directories;
}
public async Task<int> RunAsync(string[] args)
{
if(this.container == null)
{
var catalog = new AggregateCatalog();
foreach(var assembly in this.assemblies)
catalog.Catalogs.Add(new AssemblyCatalog(assembly));
if(this.directories != null && this.directories.Count > 0)
{
foreach(var dir in directories)
catalog.Catalogs.Add(new DirectoryCatalog(dir));
}
this.container = new CompositionContainer(catalog);
}
string noun = null;
if(args.Length > 0)
noun = args[0];
var actions = this.container.GetExports<ICommand>().Select(o => o.Value);
if(!string.IsNullOrWhiteSpace(noun))
actions = actions.Where(o => o.Noun.Match(noun));
var argumentTypes = actions.SelectMany(o => o.ArgumentsTypes).ToArray();
var result = (Parsed<object>)CommandLine.Parser.Default.ParseArguments(args, argumentTypes);
var argumentValues = result.Value;
var type = argumentValues.GetType();
var action = actions.FirstOrDefault(o => o.CanExecute(type));
if(action != null)
return await action.ExecuteAsync(argumentValues);
return 1;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace NerdyMishka.Console
{
/// <summary>
/// Representings a possible command line action.
/// </summary>
public interface IAction
public interface ICommand
{
/// <summary>
/// Gets a description on what the action does. This is used for
......@@ -16,6 +19,14 @@ namespace NerdyMishka.Console
/// <value></value>
string Description { get; }
string Noun { get; }
IReadOnlyCollection<Type> ArgumentsTypes { get; }
bool CanExecute(Type argumentType);
void ApplyServices(ServiceProvider provider);
/// <summary>
/// Executes a command line action.
/// </summary>
......
......@@ -20,6 +20,18 @@ namespace NerdyMishka.Console
/// </summary>
public class ConsoleArgs
{
/// <summary>
/// Initializes a new instance of <see cref="ConsoleArgs" />
/// </summary>
/// <param name="args">the arguments</param>
public ConsoleArgs(IEnumerable<string> args):
this(new List<string>(args))
{
}
/// <summary>
/// Initializes a new instance of <see cref="ConsoleArgs" />
/// </summary>
......
......@@ -22,7 +22,9 @@
<PackageReference Include="ConsoleTables" Version="2.4.0" />
<PackageReference Include="System.Composition" Version="1.4.0" />
<PackageReference Include="System.ComponentModel.Composition" Version="4.7.0" />
<PackageReference Include="Twilio" Version="5.37.2" />
<PackageReference Include="Sendgrid" Version="9.12.0" />
......
......@@ -15,7 +15,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
<PackageReference Include="Microsoft.Graph.Auth" Version="1.0.0-preview.2" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.7.1" />
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.29.1" />
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.30.0" />
<PackageReference Include="CommandLineParser" Version="2.7.82" />
......@@ -23,7 +23,7 @@
<PackageReference Include="System.Composition" Version="1.4.0" />
<PackageReference Include="Twilio" Version="5.37.1" />
<PackageReference Include="Twilio" Version="5.37.2" />
<PackageReference Include="Sendgrid" Version="9.12.0" />
</ItemGroup>
......
using Microsoft.Extensions.DependencyInjection;
namespace NerdyMishka.Extensions.DependencyInjection
{
public interface IModule
{
void Apply(IServiceCollection services);
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
using System;
namespace NerdyMishka.Extensions.DependencyInjection
{
public static class IServiceCollectionExtensions
{
private static object s_lock = new object();
private static readonly IDictionary<IServiceCollection, IList<IModule>> s_modules =
new Dictionary<IServiceCollection, IList<IModule>>();
/// <summary>
/// Clears the registered modules stored in memory to prevent duplicate registrations. This
/// will not remove the reigistered depedencies when the Apply method is called.
/// </summary>
/// <param name="services">The services collection.</param>
public static void ClearRegisteredModules(this IServiceCollection services)
{
lock(s_lock)
{
if(s_modules.TryGetValue(services, out IList<IModule> set))
set.Clear();
}
}
/// <summary>
/// Registers a module that will modify the <see cref="IServiceCollection" />
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="module">The module to register.</param>
/// <returns></returns>
public static IServiceCollection RegisterModule(this IServiceCollection services, IModule module)
{
if(services == null)
throw new ArgumentNullException(nameof(services));
if(module == null)
throw new ArgumentNullException(nameof(module));
lock(s_lock)
{
if(!s_modules.TryGetValue(services, out IList<IModule> set))
{
set = new List<IModule>();
s_modules.Add(services, set);
}
if(!set.Contains(module))
{
module.Apply(services);
set.Add(module);
}
}
return services;
}
/// <summary>
/// Scans the assembly for any public class that is not abstract that implements the <see cref="IModule" />
/// contract.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="assemblies">The assemblies to scan</param>
/// <returns>The service collection.</returns>
public static IServiceCollection RegisterModuleAssemblies(this IServiceCollection services, IList<Assembly> assemblies)
{
if(services == null)
throw new ArgumentNullException(nameof(services));
if(assemblies == null)
throw new ArgumentNullException(nameof(assemblies));
foreach(var assembly in assemblies)
{
var modules = assembly.GetTypes()
.Where(o => o.IsClass && o.IsPublic && !o.IsAbstract)
.Where(o => o.GetInterfaces().Any(i => i == typeof(IModule)))
.ToList();
if(modules.Count > 0)
{
foreach(var moduleType in modules)
{
var module = (IModule)System.Activator.CreateInstance(moduleType);
services.RegisterModule(module);
}
}
}
return services;
}
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
</ItemGroup>
</Project>
......@@ -6,7 +6,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="Serilog.Sinks.XUnit" Version="1.0.21" />
</ItemGroup>
</Project>
using Xunit.Abstractions;
using Xunit;
using Serilog;
using Serilog.Events;
namespace Mettle
{
public class TestCase
{
protected ILogger Log { get; }
public TestCase(ITestOutputHelper output, LogEventLevel level = LogEventLevel.Verbose)
{
this.Log = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.TestOutput(output, level)
.CreateLogger()
.ForContext(this.GetType());
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment