Created
October 21, 2017 01:40
-
-
Save walterlv/0a2257c30e8c175cae657b0058f5421c to your computer and use it in GitHub Desktop.
Provide a reflection based wrapper for `Microsoft.Extensions.CommandlineUtils` Library.
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 System; | |
namespace Mdmeta.Core | |
{ | |
/// <summary> | |
/// Specify a property to receive argument of command from the user. | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Property)] | |
public sealed class CommandArgumentAttribute : Attribute | |
{ | |
/// <summary> | |
/// Gets the argument name of a command task. | |
/// </summary> | |
public string Name { get; } | |
/// <summary> | |
/// Gets or sets the description of the argument. | |
/// This will be shown when the user typed --help option. | |
/// </summary> | |
public string Description { get; set; } | |
/// <summary> | |
/// Specify a property to receive argument of command from the user. | |
/// </summary> | |
public CommandArgumentAttribute(string argumentName) | |
{ | |
Name = argumentName; | |
} | |
} | |
} |
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 System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using Mdmeta.Core; | |
using Microsoft.Extensions.CommandLineUtils; | |
namespace Mdmeta | |
{ | |
/// <summary> | |
/// Contains a converter that can reflect an assembly to Specified <see cref="CommandLineApplication"/>. | |
/// </summary> | |
internal static class CommandLineReflector | |
{ | |
/// <summary> | |
/// Reflect an assembly and get all <see cref="CommandTask"/>s to the specified <see cref="CommandLineApplication"/>. | |
/// </summary> | |
/// <param name="app">The <see cref="CommandLineApplication"/> to receive configs.</param> | |
/// <param name="assembly">The Assembly to reflect from.</param> | |
internal static void ReflectFrom(this CommandLineApplication app, Assembly assembly) | |
{ | |
foreach (var ct in assembly.GetTypes() | |
.Where(x => typeof(CommandTask).IsAssignableFrom(x))) | |
{ | |
var commandAttribute = ct.GetCustomAttribute<CommandMetadataAttribute>(); | |
if (commandAttribute == null) | |
{ | |
continue; | |
} | |
app.Command(commandAttribute.Name, command => | |
{ | |
ConfigCommand(command, commandAttribute.Description, ct); | |
}); | |
} | |
} | |
/// <summary> | |
/// Convert a <see cref="CommandTask"/> to <see cref="CommandLineApplication"/> configs. | |
/// </summary> | |
private static void ConfigCommand(CommandLineApplication command, string commandDescription, Type taskType) | |
{ | |
// Config basic info. | |
command.Description = commandDescription; | |
command.HelpOption("-?|-h|--help"); | |
// Store argument list and option list. | |
// so that when the command executed, all properties can be initialized from command lines. | |
var argumentPropertyList = new List<(CommandArgument argument, PropertyInfo property)>(); | |
var optionPropertyList = new List<(CommandOption option, PropertyInfo property)>(); | |
// Enumerate command task properties to get enough metadata to config command. | |
foreach (var property in taskType.GetTypeInfo().DeclaredProperties) | |
{ | |
// Try to get argument and option info. | |
var argumentAttribute = property.GetCustomAttribute<CommandArgumentAttribute>(); | |
var optionAttribute = property.GetCustomAttribute<CommandOptionAttribute>(); | |
if (argumentAttribute != null && property.CanWrite) | |
{ | |
// Try to record argument info. | |
var argument = command.Argument( | |
argumentAttribute.Name, | |
argumentAttribute.Description); | |
argumentPropertyList.Add((argument, property)); | |
} | |
else if (optionAttribute != null && property.CanWrite) | |
{ | |
// Try to record option info. | |
CommandOptionType commandOptionType; | |
if (typeof(IEnumerable<string>).IsAssignableFrom(property.PropertyType)) | |
{ | |
// If this property is a List<string>. | |
commandOptionType = CommandOptionType.MultipleValue; | |
} | |
else if (typeof(string).IsAssignableFrom(property.PropertyType)) | |
{ | |
// If this property is a string. | |
commandOptionType = CommandOptionType.SingleValue; | |
} | |
else if (typeof(bool).IsAssignableFrom(property.PropertyType)) | |
{ | |
// If this property is a bool. | |
commandOptionType = CommandOptionType.NoValue; | |
} | |
else | |
{ | |
continue; | |
} | |
var option = command.Option( | |
optionAttribute.Template, | |
optionAttribute.Description, | |
commandOptionType); | |
optionPropertyList.Add((option, property)); | |
} | |
} | |
// Config how to execute the command. | |
command.OnExecute(() => | |
{ | |
// Create a new instance of CommandTask to call the Run method. | |
var commandTask = (CommandTask) Activator.CreateInstance(taskType); | |
// Initialize the instance with prepared arguments and options. | |
foreach (var (argument, property) in argumentPropertyList) | |
{ | |
property.SetValue(commandTask, argument.Value); | |
} | |
foreach (var (option, property) in optionPropertyList) | |
{ | |
switch (option.OptionType) | |
{ | |
case CommandOptionType.MultipleValue: | |
property.SetValue(commandTask, option.Values.ToList()); | |
break; | |
case CommandOptionType.SingleValue: | |
property.SetValue(commandTask, option.Value()); | |
break; | |
case CommandOptionType.NoValue: | |
property.SetValue(commandTask, option.HasValue()); | |
break; | |
default: | |
continue; | |
} | |
} | |
// Call the Run method. | |
return commandTask.Run(); | |
}); | |
} | |
} | |
} |
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 System; | |
namespace Mdmeta.Core | |
{ | |
/// <summary> | |
/// Specify a unique name of a command and when user typped a command | |
/// with this name the Run method of this class will be executed. | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Class)] | |
public sealed class CommandMetadataAttribute : Attribute | |
{ | |
/// <summary> | |
/// Gets the unique name of a command task. | |
/// </summary> | |
public string Name { get; } | |
/// <summary> | |
/// Gets or sets the description of the command task. | |
/// This will be shown when the user typed --help option. | |
/// </summary> | |
public string Description { get; set; } | |
/// <summary> | |
/// Specify a unique name of a command and when user typped a command | |
/// with this name the Run method of this class will be executed. | |
/// </summary> | |
public CommandMetadataAttribute(string commandName) | |
{ | |
Name = commandName; | |
} | |
} | |
} |
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 System; | |
namespace Mdmeta.Core | |
{ | |
/// <summary> | |
/// Specify a property to receive an option of command from the user. | |
/// The option template format can be "-n", "--name" or "-n|--name". | |
/// The property type can be bool, string or List{string} (or any other base types). | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Property)] | |
public sealed class CommandOptionAttribute : Attribute | |
{ | |
/// <summary> | |
/// Gets the option template of this option. | |
/// </summary> | |
public string Template { get; } | |
/// <summary> | |
/// Gets or sets the description of the option. | |
/// This will be shown when the user typed --help option. | |
/// </summary> | |
public string Description { get; set; } | |
/// <summary> | |
/// Specify a property to receive an option of command from the user. | |
/// The option template format can be "-n", "--name" or "-n|--name". | |
/// The property type can be bool, string or List{string} (or any other base types). | |
/// </summary> | |
public CommandOptionAttribute(string template) | |
{ | |
Template = template; | |
} | |
} | |
} |
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
namespace Mdmeta.Core | |
{ | |
/// <summary> | |
/// Provide a base class for all tasks that can run command from command line. | |
/// </summary> | |
public abstract class CommandTask | |
{ | |
/// <summary> | |
/// Run command when derived class override this method. | |
/// </summary> | |
/// <returns> | |
/// Return value of the whole application. | |
/// </returns> | |
public virtual int Run() | |
{ | |
return 0; | |
} | |
} | |
} |
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 Mdmeta.Core; | |
using Microsoft.Extensions.CommandLineUtils; | |
namespace Mdmeta | |
{ | |
internal class Program | |
{ | |
private static int Main(string[] args) | |
{ | |
// Initialize basic command options. | |
var app = new CommandLineApplication | |
{ | |
Name = "mdmeta" | |
}; | |
app.HelpOption("-?|-h|--help"); | |
app.VersionOption("--version", "0.1"); | |
app.OnExecute(() => | |
{ | |
// If the user gives no arguments, show help. | |
app.ShowHelp(); | |
return 0; | |
}); | |
// Config command line from command tasks assembly. | |
app.ReflectFrom(typeof(CommandTask).Assembly); | |
// Execute the app. | |
var exitCode = app.Execute(args); | |
return exitCode; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment