Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CLI: Command Line Interface Tool for C# Apps

0.00/5 (No votes)
6 Mar 2012 1  
Tired of generating your command line parsing interface to your apps? Here is yet another CLI tool for helping you do just that.

Introduction

You often need to create a console application that takes parameters to execute different functions. It is not uncommon to start with positional parameters until you realize it would be better if you could parse the commandline args for flags. When you get to this point, you have two options. You can roll your own, or use something that has been written for this purpose. I am suggesting you use my CLI tool for C# apps from now on and save yourself the time it takes to roll your own each time. I am developing this as an Open-Source project at cli.codeplex.com.

Background: Reflection, Attributes, and XML Serialization

I use two ideas heavily in the implementation of CLI.

Reflection

If you are not familiar with C# Reflection, rest assured it is a big subject. However, I think it can be best summarized as the ability to programmatically find out the names of variables instead of just their values. Another common thing is to programmatically find out members of a class where members can be fields, properties, or methods irrespective of their protection level (public, protected, private, internal). This is something that usually only the programmer knows. There is usually a fairly high penalty for using Reflection. However, if you selectively use it, it can be quite powerful.

Attributes

Attributes are somewhat a subset of the Reflection discussion. In so much as you can reference custom attributes via Reflection as I do in CLI. More information can be found here. Attributes give you the ability to decorate or mark up your code and gain extra functionality. Serialization is a common example.

XML Serialization

XML Serialization is a well trodden topic and many articles can be found covering the subject. I will summarize here by saying that it is the ability to take an object in memory and save it to desk in the form of an XML file. The reverse is also possible by instantiating an object from an XML file representing it.

Using the CLI Library

Code Overview

Here is a brief explanation of the most important classes in the CLI library. This is how the user will interface with the features offered by automatic commandline parsing and assignment. Please take a moment to look through the interfaces listed below to get a feel for what is possible and how it works.

ICliAttribute interface

Both custom attributes CliAttribute and CliElement implement this interface and in fact the only reason to use CliElement is for XML serialization considerations, not covered here.

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 public properties

CliBuilder is the static class that instantiates your CliAttribute-decorate class. It provides some defaults and abilities to override them. It does all the work to match and assign command line args to your class properties. It also, itself, has "built-in" CliAttribute-decorated properties such as Help and 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
}

Simple Usage Example

Now I will show a simple straightforward example of how to use the CLI library. It should only take seconds to enable an application to have a quick, concise, coherent interface complete with --help functionality.

1. Add reference

// 1)  Add reference to the cli library and include the namespace
using Idlesoft.Cli;

2. Define a class

// 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. Call 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. Use the class object

// 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));

Points of Interest

I really like using the impressive flexibility of the C# programming language to make my life easier. Reflection is a powerful, if expensive tool, that can be used to really extend the functionality of any program written in C#. I am working on this as an Open Source project at cli.codeplex.com. Any feedback, ideas, suggestions, bug fixes are always welcome.

Remaining Work to be Done

I still need to implement and test some of the XML serializaion features I want to enable. I would also like to cleanup the help print screen. I have been using the Python library argparse at work lately and really like working with it. Maybe I will borrow some of the ideas there. I do like C#'s ability to decorate properties in a more declarative way, so I wouldn't be changing that. ;)

History

Initial submission.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here