CLI:C# 应用程序的命令行接口工具
厌倦了为您的应用程序生成命令行解析界面?这里又有一个CLI工具可以帮助您做到这一点。
引言
您经常需要创建一个控制台应用程序,该应用程序需要参数才能执行不同的函数。从位置参数开始,直到您意识到如果可以解析命令行参数中的标志会更好,这并不罕见。当您到达这一点时,您有两个选择。您可以自己编写,或者使用为此目的编写的工具。我建议您从现在开始使用我的C#应用程序CLI工具,并节省每次自己编写所需的时间。我正在将此开发为一个开源项目,网址为cli.codeplex.com。
背景:反射、属性和XML序列化
我在CLI的实现中大量使用了两个思路。
Reflection(反射)
如果您不熟悉C#反射,请放心,这是一个很大的主题。但是,我认为它可以最好地概括为以编程方式找出变量名称而不是仅找出其值的能力。另一件常见的事情是以编程方式找出类的成员,其中成员可以是字段、属性或方法,而不管其保护级别(公共、受保护、私有、内部)如何。这通常只有程序员知道。使用反射通常会付出相当高的代价。但是,如果您选择性地使用它,它可能会非常强大。
属性
属性在某种程度上是反射讨论的一个子集。因为您可以像我在CLI中那样通过反射引用自定义属性。更多信息可以在这里找到这里。属性使您可以装饰或标记代码并获得额外的功能。序列化就是一个常见的例子。
XML 序列化
XML序列化是一个众所周知的主题,可以找到许多涵盖该主题的文章。我在这里总结一下,它是将内存中的对象保存到桌面上的XML文件形式的能力。反之亦然,可以通过从表示它的XML文件实例化对象来实现。
使用CLI库
代码概述
以下是CLI库中最重要的类的简要说明。这就是用户将如何与自动命令行解析和赋值提供的功能交互。请花一点时间查看下面列出的接口,以了解可能的用途和工作方式。
ICliAttribute接口
自定义属性CliAttribute
和CliElement
都实现了此接口,事实上,使用CliElement
的唯一原因是出于XML序列化的考虑,此处未涵盖。
public interface ICliAttribute
{
/// <summary>
/// Boolean property to determine if the cliattribute
/// is an Option as opposed to a BuiltIn or Required.
/// </summary>
bool Option { get; }
/// <summary>
/// Boolean property to determin if the cliattribute
/// is decorating the CliBuilder class instead of a user-defined one.
/// </summary>
bool BuiltIn { get; }
/// <summary>
/// Optional boolean property to mark a cliattribute as required.
/// This will cause an error to happen and the usage
/// to print if it is not set during CliBuilder.Create.
/// </summary>
bool Required { get; set; }
/// <summary>
/// Optional property to override the default flag types as defined
/// during the call to CliBuilder.Create or its default value.
/// This governs how and how many of the flags are auto-generated.
/// </summary>
FlagTypes FlagTypes { get; set; }
/// <summary>
/// Internal property that determines what kind of values
/// are appropriate for the ICliAttribute decorated field or property.
/// </summary>
ValueTypes ValueTypes { get; }
/// <summary>
/// Internal property used to arg matches including the name and the values.
/// </summary>
object[] Args { get; set; }
/// <summary>
/// Optional property to override the name of the property.
/// This overridden Name is then used when auto-generating the flags to match against.
/// </summary>
string Name { get; set; }
/// <summary>
/// Optional property used when the help usage prints
/// to the console on bad input are when invoked.
/// </summary>
string Description { get; set; }
/// <summary>
/// Optional string array of flags to be used to match against.
/// This turns off the auto-generated flags irrespectives of the FlagTypes etc.
/// </summary>
string[] Flags { get; set; }
/// <summary>
/// Internal property used to store the values once they have been
/// parsed out of the command line args. Do not attempt to set this property.
/// </summary>
object[] Values { get; set; }
/// <summary>
/// Optional property to supply default values to be used when no values
/// are found during parsing. Note that this will not be used
/// for cliattributes marked as Required.
/// </summary>
object Default { get; set; }
/// <summary>
/// Internal property to store the MemberInfo loaded during
/// the CliBuilder.Create execution. Do not attempt to set this property.
/// </summary>
MemberInfo MemberInfo { get; set; }
/// <summary>
/// Internal property to get the type of the property|field
/// that the MemberInfo property points to.
/// Will throw an exception if MemberInfo is not set.
/// </summary>
Type MemberInfoType { get; }
/// <summary>
/// Internal method to determine if a cliattribute has been assigned.
/// Checks against the default values; this will probably be improved to be more accurate.
/// </summary>
/// <param name="target">The target object that
/// the MethodInfo property is point to.</param>
/// <returns></returns>
bool IsAssigned(object target);
/// <summary>
/// Internal method to determin if either the Name
/// or any of the Values match the given arg.
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
bool MatchesArg(string arg);
}
CilBuilder公共属性
CliBuilder
是实例化您的CliAttribute
修饰类的静态类。它提供了一些默认值和覆盖它们的能力。它完成了将命令行参数与您的类属性匹配和分配的所有工作。它本身也具有“内置”的CliAttribute
修饰属性,例如Help
和Version
。
public static class CliBuilder
{
#region props
public static bool ExitOnError { get; set; }
/// <summary>
/// Produces the name of current process' main module filename
/// </summary>
public static string Filename { get { return Path.GetFileName(
System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); } }
/// <summary>
/// Produces the Filename with the .xml extension instead
/// </summary>
public static string XmlFilename { get { return Path.ChangeExtension(Filename, ".xml"); } }
/// <summary>
/// Produces the Filename with the .ini extension instead
/// </summary>
public static string IniFilename { get { return Path.ChangeExtension(Filename, ".ini"); } }
/// <summary>
/// Produces the Filename with the .cli extension instead
/// </summary>
public static string CliFilename { get { return Path.ChangeExtension(Filename, ".cli"); } }
/// <summary>
/// The title to be used in help usage printing
/// </summary>
public static string Title { get; set; }
/// <summary>
/// The decsription to be used in help usage printing
/// </summary>
public static string Description { get; set; }
/// <summary>
/// The default flags value to be used on CliAttributes that don't otherwise specify
/// </summary>
public static FlagTypes Flags { get; internal set; }
/// <summary>
/// The args to be used in parsing, either passed in with
/// the Create method or assumed to be Environment.GetCommandLineArgs()
/// </summary>
public static string[] Args { get; internal set; }
/// <summary>
/// Is set to the remaining args that were not found
/// in the class when instantiating an object via CliBuilder.Create
/// </summary>
public static string[] RemainingArgs { get; internal set; }
/// <summary>
/// Dictionary of flag values and expression generators to be
/// used for automatically divining flag tokens to match against in parsing
/// </summary>
public static Generators Generators { get; internal set; }
/// <summary>
/// Method to be used when 'help' is invoked; can be overriden
/// by replacing this function with your own printing function
/// </summary>
public static Func<string, string, ICliAttribute[], string> Usage { get; set; }
/// <summary>
/// Built-In property to match against Help and print the usage text.
/// Will default to the common -h|--help unless other flags specified override this behavior
/// </summary>
[CliAttribute(Description = "show this help message and exit")]
public static bool Help { get; set; }
/// <summary>
/// Built-In property to match against Version and print the version text.
/// Will default to the common -v|--version unless other flags specified override this behavior
/// </summary>
[CliAttribute(Description = "return the version of the program")]
public static bool Version { get; set; }
/// <summary>
/// Built-In property to match against CliVersion and print the version
/// of cli text. Will default to the common -c|--cliversion unless
/// other flags specified override this behavior
/// </summary>
[CliAttribute(Description = "return the version of the Cli assembly")]
public static bool CliVersion { get; set; }
/// <summary>
/// Built-In property to match against SaveSonfig which is a path, filename
/// and extension that specifies where, with what name and what type
/// of config file to save from the parameters that are loaded during Create
/// </summary>
[CliAttribute(Description = "save the parameters as an xml file")]
public static string SaveConfig { get; set; }
/// <summary>
/// Built-In property to match against LoadConfigs which is an array
/// of paths to configs files (.xml, .ini, .cli) that will load values
/// PRIOR to the other commandline options that may be included
/// </summary>
[CliElement(Description = "list of cli config files to load")]
public static string[] LoadConfigs { get; set; }
#endregion props
}
简单的使用示例
现在我将展示一个简单直接的使用CLI库的示例。它只需要几秒钟即可使应用程序拥有一个快速、简洁、连贯的界面,并具有--help功能。
1. 添加引用
// 1) Add reference to the cli library and include the namespace
using Idlesoft.Cli;
2. 定义一个类
// 2) Start by defining a class with custom cli attributes (CliAttribute or CliElement)
public class Person
{
[CliAttribute(Description = "your name?")]
public string Name { get; set; }
[CliAttribute(Description = "your age?")]
public int Age { get; set; }
[CliElement(Description = "list of pets?")]
public string[] Pets { get; set; }
}
3. 调用CliBuilder.Create<TTarget>
// 3) Make call to CliBuilder.Create early on in your code
// to parse commandline args and populate your decorated class object
var person = CliBuilder.Create<Person>(PERSON_TITLE, PERSON_DESC,
FlagTypes.Default, PERSON_ARGS);
// try replacing PERSON_ARGS with args or removing it all together
4. 使用类对象
// 4) Use the class object with populated properties
Console.WriteLine(string.Format("My name is {0} and I am {1} years old. I have {2} pets: {3}.",
person.Name, person.Age, count, pets));
关注点
我真的很喜欢使用C#编程语言令人印象深刻的灵活性来使我的生活更轻松。反射是一个强大但代价昂贵的工具,可用于真正扩展任何用C#编写的程序的功能。我正在将此作为开源项目进行开发,网址为cli.codeplex.com。任何反馈、想法、建议、错误修正都非常受欢迎。
剩余工作
我还需要实现和测试一些我想启用的XML序列化功能。我还想清理帮助打印屏幕。我最近一直在工作中使用Python库argparse,并且非常喜欢使用它。也许我会借用一些那里的想法。我确实喜欢C#以更声明的方式装饰属性的能力,所以我不会改变这一点。;)
历史
首次提交。