CLAP: Command-Line Auto Parser

Automatic command-line verb and argument parsing using smart reflection.
Inspired by the ASP.NET MVC Controller-Actions, finally – a kick-ass command-line parser. The last one you will ever need!

Update: Part 2 of the CLAP introduction is here

Get It

Usage Samples

Verbs

Consider the following method:

class MyApp
{
    [Verb]
    public static void Print(string prefix, string name, int count)
    {
        for (int i = 0; i < count; i++)
        {
            Console.WriteLine("{0} {1}", prefix, name);
        }
    }
}

The Print method is marked with the [Verb] attribute which defines method as valid entry points for the application.

Each of the method’s arguments are automatically mapped to command-line input arguments, regardless of their order.

Notice that all names, both for verbs and parameter names are case-insensitive.

Let’s change our Main method to use the parser:

class Program
{
    static void Main(string[] args)
    {
        Parser<MyApp>.Run(args);
    }
}

After successfully compiling, we can now run our application, providing a verb and arguments:

>myexe print -prefix:Hello -name:World -count:10

Which prints:

Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World

More Verbs for the same program

A class may contain more than one verb.

In the following example, “print” and “login” are two different entry-points for the application, each with a different set of arguments:

class MyApp
{
    [Verb]
    public static void Print(string prefix, string name, int count)
    {
    }

    [Verb]
    public static void Login(string userName, string password)
    {
    }
}

A Default Verb

One of the verbs, or even the single one if no more than one are available, can be defined as the default verb using the [DefaultVerb] attribute above the class.

In the following example, “print” is the default verb, so if the user doesn’t provide a verb in the command-line, “print” will be called:

[DefaultVerb("print")]
class MyApp
{
    [Verb]
    public static void Print(string prefix, string name, int count)
    {
    }

    [Verb]
    public static void Login(string userName, string password)
    {
    }
}

In that case, we can call:

>myexe -prefix:Hello -name:World -count:10

Aliases

A verb can have more than one name. The first name is the method’s name but additional names can be defined using the Aliases property.

In the following example, “p” is an additional name for the “print” verb and both “l” and “log” are additional names for the “login” verb:

[DefaultVerb("print")]
class MyApp
{
    [Verb(Aliases = "p")]
    public static void Print(string prefix, string name, int count)
    {
    }

    [Verb(Aliases = "l,log")]
    public static void Login(string userName, string password)
    {
    }
}

Parameters

Parameters are automatically detected according to their names. Additional options can be defined for parameters using more attributes.

Aliases

As a verb, parameters can also have additional names when marked with the [Parameter] attribute.

In the following example, both “name” and “n” are additional names for the “userName” parameter and both “pass” and “p” are for the “password” parameter:

class MyApp
{
    [Verb]
    public static void Login(
        [Parameter(Aliases = "name,n")]
        string userName,
        [Parameter(Aliases = "pass,p")]
        string password)
    {
    }
}

Default Values

A parameter can be omitted from the input. If a parameter is omitted, it’s value will have the type’s default (null for reference types, zero for value types).

A default value can be defined for a parameter to override the type’s default.

In the following example, “Hello” is the default for the prefix parameter and 5 for count:

class MyApp
{
    [Verb]
    public static void Print(
        [Parameter(Default = "Hello")]
        string prefix,
        string name,
        [Parameter(Default = 5)]
        int count)
    {
        for (int i = 0; i < count; i++)
        {
            Console.WriteLine("{0} {1}", prefix, name);
        }
    }
}

Now we can run:

>myexe print -name:World

And get this result:

Hello World
Hello World
Hello World
Hello World
Hello World

Switches

Boolean types are treated as switches.

A switch is an argument that doesn’t need a value. If the argument exists – the value is true, otherwise – it is false.

In the following example, the “upper” parameter is a boolean, and therefore – a switch. In-addition, it has an additional, shorter name: “u”:

class MyApp
{
    [Verb]
    public static void Print(
        [Parameter(Default = "Hello")]
        string prefix,
        string name,
        [Parameter(Default = 5)]
        int count,
        [Parameter(Aliases = "u")]
        bool upper)
    {
        if (upper)
        {
            prefix = prefix.ToUpper();
            name = name.ToUpper();
        }

        for (int i = 0; i < count; i++)
        {
            Console.WriteLine("{0} {1}", prefix, name);
        }
    }
}

Now we can run:

>myexe print -name:World -u

HELLO WORLD
HELLO WORLD
HELLO WORLD
HELLO WORLD
HELLO WORLD

Parameter Types

The supported parameter types are:

  • Numeric Types (int, float, …)
  • Boolean – can be used as switches but also with “true”/”false” input
  • String
  • Any Enum (input is case-sensitive)
  • Arrays of all the above

Arrays

Arrays are called as comma-separated values. For example:

class MyApp
{
    [Verb]
    public static void Hello(string[] names)
    {
        foreach (var name in names)
        {
            Console.WriteLine("Hello {0}", name);
        }
    }
}

 

>myexe hello -names:Adrian,Yoav,Shlomo

Hello Adrian
Hello Yoav
Hello Shlomo

Required Parameters

A parameter can be marked as required. A missing argument will throw a MissingRequiredArgumentException.

In the following example, the “name” parameter is required:

class MyApp
{
    [Verb]
    public static void Print(
        [Parameter(Default = "Hello")]
        string prefix,
        [Parameter(Required = true)]
        string name)
    {
    }
}

Trying to run the program without providing a value for “name” will throw an exception.

In this example, the exception is not caught and therefore, the application crashes and the stack-trace is printed to the console:

>myexe print

Unhandled Exception: System.ArgumentException: Missing argument for 'name'
   at …

Validation

Validation attributes can be specified for parameters.

Current available attributes are:

  • MoreThan
  • MoreOrEqualTo
  • LessThan
  • LessOrEqualTo
  • RegexMatches

Providing a value that doesn’t pass validation will throw a ValidationException.

In the following example, “count” is validated to be more than 3:

class MyApp
{
    [Verb]
    public static void Print(
        string prefix,
        string name,
        [MoreThan(3)]
        int count)
    {
    }
}

Additional validation attributes can be defined for the same parameter. Validation passes only if all validators pass (AND).

In the following example, “count” is validated to be more than 3 and less-or-equal to 20:

class MyApp
{
    [Verb]
    public static void Print(
        string prefix,
        string name,
        [MoreThan(3)]
        [LessOrEqualTo(20)]
        int count)
    {
    }
}

Exceptions

The following is a list of all the exceptions that might be thrown when failing to parse the command-line.

Notice that all exception derive from the abstract CommandLineException.

MissingDefaultVerbException

  • The input does not contain a verb and no default verb was defined.
  • Solution: Either input a verb or define one using the [DefaultVerb] attribute.

MissingVerbException

  • The input contains a verb but no method matches it.
  • Solution: Check that the method is marked with the [Verb] attribute or that the input verb is one of the verb’s aliases.

MissingRequiredArgumentException

  • A required argument is missing.
  • Solution: If the parameter is marked as [Parameter(Required = true)], check that an argument is actually passed for that parameter.

MissingArgumentValueException

  • A value argument is missing from a parameter input, and the parameter is not a switch (the type is not a boolean).
  • Solution: Make sure the argument value is passed in the form of: -name:value

MissingArgumentPrefixException

  • A parameter does not have a prefix of either “-“ or “/”.
  • Solution: Make sure to prefix all parameters with a valid prefix.

ValidationException

  • An argument value does not pass validation.
  • Solution: Make sure the value passes the defined validation.

TypeConvertionException

  • An argument value cannot be converted to the parameter’s type, for example: A string input for an int type.
  • Solution: Make sure the input value matches the parameter’s type.

Summary

This post introduced the Command-Line Auto Parser and provided examples for:

  • Defining verbs
  • Defining parameters
  • Default verbs and additional names
  • Default values
  • Supported parameter types
  • Using built-in validation
  • Exceptions and how to handle them

In the next post, additional features are introduced:

  • Global parameter handlers
  • Custom validation
  • Providing help to the user
Share, please:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • DotNetKicks
  • DZone
  • StumbleUpon
  • Facebook
  • Tumblr
  • Twitter
This entry was posted in .NET, Command-Line and tagged , . Bookmark the permalink.

25 Responses to CLAP: Command-Line Auto Parser

  1. Barry Carr says:

    Nice work! Looks really good.

    Is CLAP available via nuget?

    Cheers

  2. bdaniel7 says:

    Marvelous! :)

  3. Adrian Aisemberg says:

    I’ll invest some time and make it available via nuget.

  4. Pingback: The Morning Brew - Chris Alcock » The Morning Brew #869

  5. Oded says:

    Sweet library – I didn’t see any example of it producing an automatic “usage/help” message.

    Is this something that already exists, or something for the future?

    • Adrian Aisemberg says:

      It exists, but still in development.
      Next post will introduce it, along with additional features.

  6. Mike says:

    Smart! Does it work in PowerShell as well?

    • Adrian Aisemberg says:

      Yes, but as a regular exe.
      You won’t get any Powershell specific features, such as auto-complete (by pressing tab).

  7. David Robbins says:

    Any chance we could see the source code? This looks like a great opportunity for learning. Nice job!

    • Adrian Aisemberg says:

      Regarding the source code:
      After publishing the second part (which introduces additional features) I will publish the source code.

  8. Alexandre Jobin says:

    Very promising!
    i will give it i try for sure

  9. Alexandre Jobin says:

    i’ve just noticed that your validation doesnt use the DataAnnotations namespace. Is there a reason for that?

    • Adrian Aisemberg says:

      These are my implementations of parameter validations.
      They are not related to DataAnnotations.

  10. Jason Perry says:

    Looks really nice!

    Which versions of .NET will this run under. Most of my command line utilities are still required to run under 2.0 (or later) believe it or not.

  11. Manuel says:

    Very nice work!
    When we can see the source code? I’m hungry, to learn from your ideas…

  12. Jeff Dalton says:

    What license are you using?

  13. Pingback: Found on the Web – 13 June 2011 | Phillpotts Dot Net

  14. Pingback: C#: CLAP, a command-line auto parser | Jesus Was Rasta

  15. Pingback: .NET i jiné ... : Odkazy z prohlíže?e - 24.6.2011

  16. nando says:

    Hi Adrian.
    I’m thinking about possibility to configure commands and parameter markers; now if I want to execute a command with some parameters I have to do something like this:

    C:\>myapp.exe mycommand -myparam1:something -myparam2:somethingelse

    But if my old application have a pattern like this:
    C:\>myapp.exe –mycommand:something,somethingelse (ordered params with comma, semicolon or other separator)
    or
    C:\>myapp.exe /mycommand -myparam1:something -myparam2:somethingelse

    I cannot replace my old command-line parser with CLAP at 1:1, but I need to write a wrapper for it ().

    Please let me know if you like my idea to have a “pattern configuration” feature for CLAP.

    Bests,
    Nando

  17. nando says:

    Well, I will try, thank you! :-)

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Spam protection by WP Captcha-Free