-
-
Save wtyneb/b7a79ffad3cae2ab908dba1dc37f5e49 to your computer and use it in GitHub Desktop.
Subcommand with Lazy subcommand example - natemcmaster/CommandLineUtils McMaster.Extensions.CommandLineUtils
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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