Skip to content

Instantly share code, notes, and snippets.

@wtyneb
Last active January 6, 2020 19:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wtyneb/b7a79ffad3cae2ab908dba1dc37f5e49 to your computer and use it in GitHub Desktop.
Save wtyneb/b7a79ffad3cae2ab908dba1dc37f5e49 to your computer and use it in GitHub Desktop.
Subcommand with Lazy subcommand example - natemcmaster/CommandLineUtils McMaster.Extensions.CommandLineUtils
using McMaster.Extensions.CommandLineUtils;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading.Tasks;
namespace CustomSubcommand
{
public interface IServiceC
{
public string RunServiceC(string[] ss);
}
public class ServiceC : IServiceC
{
public ServiceC()
{
Console.WriteLine("Initializing ServiceC");
}
public string RunServiceC(string[] ss)
{
var s = String.Join(',', ss);
Console.WriteLine($"ServiceC concats: {s}");
return s;
}
}
public interface ICliCommand
{
public void RegisterSubcommands<TModel>(CommandLineApplication<TModel> app) where TModel : class, ICliCommand;
public int Handle<TModel>(CommandLineApplication<TModel> app) where TModel : class, ICliCommand;
public void Register<TModel>(CommandLineApplication<TModel> app) where TModel : class, ICliCommand;
public ImmutableHashSet<string> Aliases { get; }
public string Name { get; }
}
public interface ICliCommand2
{
public int Handle<TWrapper, TModel>(CommandLineApplication<TWrapper> app) where TWrapper : class, ICommandWrapper<TModel> where TModel : class, ICliCommand2;
public bool InjectOptions<TWrapper, TModel>(TWrapper wrapper) where TWrapper : class, ICommandWrapper<TModel> where TModel : class, ICliCommand2;
}
public interface ICommandWrapper<TCommand> where TCommand : class, ICliCommand2
{
public void RegisterSubcommands<TWrapper, TModel>(CommandLineApplication<TWrapper> app) where TWrapper : class, ICommandWrapper<TModel> where TModel : class, ICliCommand2;
public void Register<TWrapper, TModel>(CommandLineApplication<TWrapper> app) where TWrapper : class, ICommandWrapper<TModel> where TModel : class, ICliCommand2;
public ImmutableHashSet<string> Aliases { get; }
public string Name { get; }
public TCommand Command { get; set; }
}
public static class AppExtensions
{
public static CommandLineApplication<TWrapper> LazySubcommand<TWrapper, TModel, TBase>(this CommandLineApplication<TBase> app)
where TWrapper : class, ICommandWrapper<TModel>
where TModel : class, ICliCommand2
where TBase : class
{
var sc = new CommandLineApplication<TWrapper>();
sc.Conventions.UseDefaultConventions().UseConstructorInjection(app);
sc.Name = sc.Model.Name;
//Using explicit construction instead of CommandLineApplication.Command<TModel>
//var sc = app.Command<TModel>(TModel.Name, sc =>
//{
foreach (var n in sc.Model.Aliases)
{
if (n != sc.Name)
{
sc.AddName(n);
}
}
//});
sc.Model.Register<TWrapper, TModel>(sc);
sc.Model.RegisterSubcommands<TWrapper, TModel>(sc);
sc.OnExecute(() => {
Console.WriteLine($"OnExecute() {sc.Name}");
var model = ActivatorUtilities.CreateInstance<TModel>(sc);
model.InjectOptions<TWrapper, TModel>(sc.Model);
return model.Handle<TWrapper, TModel>(sc);
});
app.AddSubcommand(sc);
return sc;
}
}
class C3 : ICliCommand2
{
private readonly IServiceC _serviceC;
public C3(IServiceC serviceC)
{
_serviceC = serviceC;
}
public C3() { }
public CommandOption opt_y;
public bool InjectOptions<TWrapper, TModel>(TWrapper wrapper) where TWrapper : class, ICommandWrapper<TModel> where TModel : class, ICliCommand2
{
if (wrapper is C3Wrapper w)
{
opt_y = w.opt_y;
return true;
}
return false;
}
public int Handle<TWrapper, TModel>(CommandLineApplication<TWrapper> app) where TWrapper : class, ICommandWrapper<TModel> where TModel : class, ICliCommand2
{
Console.WriteLine("C3 Handel(...)");
var s = _serviceC.RunServiceC(new string[] { "foo", "bar", "baz" });
var s2 = "opt y is MISSING";
if (opt_y.HasValue())
{
s2 = "opt y is SET";
}
Console.WriteLine($"hello from command 3! {s2} _serviceC.RunServiceC(...) = \"{s}\" ");
return 0;
}
}
class C3Wrapper : ICommandWrapper<C3>
{
private static readonly ImmutableHashSet<string> ALIASES = ImmutableHashSet.Create("c3", "command3");
public ImmutableHashSet<string> Aliases => ALIASES;
private const string NAME = "command3";
public string Name => NAME;
public CommandOption opt_y;
public CommandOption opt_z;
public C3 Command { get; set; }
void ICommandWrapper<C3>.Register<TWrapper, TModel>(CommandLineApplication<TWrapper> app)
{
opt_y = app.Option("-y", "testing option y", CommandOptionType.NoValue);
opt_y.ValueName = "ValYouNam";
opt_z = app.Option("-z", "'-z [z value]' for testing options", CommandOptionType.SingleValue);
opt_z.ValueName = "zed value";
opt_z.LongName = "zed";
}
void ICommandWrapper<C3>.RegisterSubcommands<TWrapper, TModel>(CommandLineApplication<TWrapper> app) { }
}
class BaseApp : ICliCommand
{
private static readonly ImmutableHashSet<string> ALIASES = ImmutableHashSet.Create(Array.Empty<string>());
public ImmutableHashSet<string> Aliases => ALIASES;
private const string NAME = "BaseApp";
public string Name => NAME;
public BaseApp() { }
public int Handle<TModel>(CommandLineApplication<TModel> app) where TModel : class, ICliCommand
{
app.Option("-x", "test", CommandOptionType.NoValue);
Console.WriteLine("BaseApp - Handle()");
return 0;
}
public void Register<TModel>(CommandLineApplication<TModel> app) where TModel : class, ICliCommand
{
//bind options and perform other initialization. Consider moving this to constructor?
app.HelpOption();
}
public void RegisterSubcommands<TBase>(CommandLineApplication<TBase> app)
where TBase : class, ICliCommand
{
//app.Subcommand<C1, TBase>(); // supporting code removed for brevity, see earlier gist revisions to see
//app.Subcommand<C2, TBase>(); // supporting code removed for brevity, see earlier gist revisions to see
app.LazySubcommand<C3Wrapper, C3, TBase>();
}
}
// compile and invoke while in the build output directory
// lazy "loaded" subcommand example
// .\CustomSubcommand.exe c3 -y foo
// other commands and subcommands
// .\CustomSubcommand.exe c1 sc1
// .\CustomSubcommand.exe c1 sc2
public class Program
{
static ServiceProvider services =
new ServiceCollection()
//.AddSingleton<IServiceA>(sp => { Console.WriteLine("making serviceA singleton"); return new ServiceA(); }) // supporting code removed for brevity, see earlier gist revisions to see
//.AddSingleton<IServiceB>(sp => { Console.WriteLine("making serviceB singleton"); return new ServiceB(); }) // supporting code removed for brevity, see earlier gist revisions to see
.AddSingleton<IServiceC>(sp => { Console.WriteLine("making serviceC singleton"); return new ServiceC(); })
.BuildServiceProvider();
public static async Task<int> Main(string[] args)
{
Console.WriteLine("MAIN");
var app = new CommandLineApplication<BaseApp>();
app.Conventions
.UseDefaultConventions()
.UseConstructorInjection(Program.services);
app.Model.RegisterSubcommands(app);
app.OnExecute(() => app.Model.Handle(app));
return app.Execute(args);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment