CLAP: NuGet Available

>Install-Package CLAP

Package Homepage:

http://nuget.org/List/Packages/CLAP

Posted in .NET, Command-Line | Tagged , | 2 Comments

CLAP: Command-Line Auto Parser–Part 2

Get It

Intro

Previous 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

In this post, additional features will be introduced:

  • Global parameter handlers
  • Custom validation
  • Providing help to the user
  • Handling no input

Global Parameter Handlers

As seen in the base usage samples, each verb defines it’s own set of parameters.

Sometimes we need to handle some parameters which are not necessarily related to a specific verb.

Global Switches

In the following example, the “debug” parameter makes the application wait for a debugger to be attached:

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

    [Global]
    public static void Debug()
    {
        Debugger.Launch();
    }
}

Notice the syntax of the Global parameter definition:

In the class that contain the verb(s), we add a new method and mark it with the [Global] attribute.

If the user provides the “-debug” switch, that method will be called.

In this example – the method doesn’t have any arguments. A method without arguments is a switch.

Name Override

The parameter name can be overridden by specifying a name that will be used instead (not in-addition) to the method’s name.

In the following example, “break” is used instead of “debug”:

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

    [Global("break")]
    public static void Debug()
    {
        Debugger.Launch();
    }
}

Global Argument Handlers

Global handlers can also handle user’s input.

In the following example, various handlers are defined for different argument types:

[Global("thread")]
public static void SetThreadName(string name)
{
    Thread.CurrentThread.Name = name;
}

[Global("priority")]
public static void SetThreadPriority(ThreadPriority priority)
{
    Thread.CurrentThread.Priority = priority;
}

[Global("number")]
public static void DoSomethingWithAnumber(int number)
{
    Console.WriteLine("A number: {0}", number);
}

So we can run:

>myexe print -thread:Main -priority:Highest -number:6

Validation

Global handlers’ parameters support Validation, such as verb parameters.

For example:

[Global("number")]
public static void DoSomethingWithAnumber([LessThan(10)] int number)
{
    Console.WriteLine("A number: {0}", number);
}

Lambda Expressions

Global handlers can be registered via a delegate.

In the following example, the “debug” switch is registered as a delegate:

class Program
{
    static void Main(string[] args)
    {
        var p = Parser.Create<MyApp>();

        p.RegisterParameterHandler("debug", delegate
        {
            Debugger.Launch();
        });

        p.Run(args);
    }
}

Additional parameter names can be registered with a comma-separated list of names:

class Program
{
    static void Main(string[] args)
    {
        var p = Parser.Create<MyApp>();

        p.RegisterParameterHandler("debug,d,break", delegate
        {
            Debugger.Launch();
        });

        p.Run(args);
    }
}

In the following example, all the three previous samples are rewritten using lambda expressions:

class Program
{
    static void Main(string[] args)
    {
        var p = Parser.Create<MyApp>();

        p.RegisterParameterHandler<string>(
            "thread",
            name => Thread.CurrentThread.Name = name);

        p.RegisterParameterHandler<ThreadPriority>(
            "priority",
            priority => Thread.CurrentThread.Priority = priority);

        p.RegisterParameterHandler<int>(
            "number",
            number => Console.WriteLine("A number: {0}", number));

        p.Run(args);
    }
}

Custom Validation

CLAP comes with the following built-in validators:

  • MoreThan
  • MoreOrEqualTo
  • LessThan
  • LessOrEqualTo
  • RegexMatches

Additional validators can be easily implemented by deriving from ValidationAttribute and implementing IParameterValidator.

The following is an example of a validation attribute that validates that a string input has a length of at least a specified value:

class LengthValidationAttribute : ValidationAttribute
{
    public int Length { get; private set; }

    public LengthValidationAttribute(int length)
        : base(new LengthValidator(length))
    {
        Length = length;
    }

    public override string Description
    {
        get
        {
            return string.Format(
                "Length is at least {0}", Length);
        }
    }

    class LengthValidator : IParameterValidator
    {
        public int Length { get; private set; }

        public LengthValidator(int length)
        {
            Length = length;
        }

        public void Validate(object value)
        {
            var str = (string)value;

            if (str.Length < Length)
            {
                throw new ValidationException(
                    string.Format(
                        "Length should be at least {0}",
                        Length));
            }
        }
    }
}

Notice the Description property. It is used when providing help to the user, as described in the next chapter.

Using the attribute:

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

Help

Help can be automatically provided to the user when the user asks for it using a registered parameter.

In the following example, we register the “help” and “?” parameters to write the help string to the console:

class Program
{
    static void Main(string[] args)
    {
        var p = Parser.Create<MyApp>();

        p.RegisterHelpHandler("help,?", help => Console.WriteLine(help));

        p.Run(args);
    }
}

When the user enters either “help” or “?” as the first argument, the auto-generated help will be printed to the console.

The help contains a list of verbs, for each verb – a list of parameters, their types and additional properties, such as whether the argument is required and has validation.

Descriptions

The help can contain, in-addition to all the automatic generated information (types, required, validation, defaults) also a friendly description.

Verbs and Parameters can have a description defined using the Description property of each attribute.

This is true also for defined global arguments, for example:

[Global("priority")]
public static void SetThreadPriority(
    [Parameter(Description = "The thread's priority")]
    ThreadPriority priority)
{
    Thread.CurrentThread.Priority = priority;
}

In-order to provide a description for registered (vs. defined) global parameters handlers, use the overload that accepts a description string, for example:

class Program
{
    static void Main(string[] args)
    {
        var p = Parser.Create<MyApp>();

        p.RegisterParameterHandler(
            "debug,d,break",
            delegate { Debugger.Launch(); },
            "Attach a debugger to the process");

        p.Run(args);
    }
}

Handling No Input

An empty handler can be registered to be executed when no input has been entered.

In the following example, if no input is entered, a “What?” string will be printed to the console:

class Program
{
    static void Main(string[] args)
    {
        var p = Parser.Create<MyApp>();

        p.RegisterEmptyHandler(delegate
        {
            Console.WriteLine("What?");
        });

        p.Run(args);
    }
}

We can also register a handler that will print the help string to the console, using the special RegisterEmptyHelpHandler method:

class Program
{
    static void Main(string[] args)
    {
        var p = Parser.Create<MyApp>();

        p.RegisterEmptyHelpHandler(help => Console.WriteLine(help));

        p.Run(args);
    }
}

The following is a recommended complete set of help handlers, both ?,help,h and an empty handler:

class Program
{
    static void Main(string[] args)
    {
        var p = Parser.Create<MyApp>();

        p.RegisterEmptyHelpHandler(help => Console.WriteLine(help));
        p.RegisterHelpHandler("?,help,h", help => Console.WriteLine(help));

        p.Run(args);
    }
}

Summary

Previous 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

This post introduced some additional features:

  • Global parameter handlers
  • Custom validation
  • Providing help to the user
  • Handling no input

For additional information, sample usages and suggestions, please email me at: adrianaisemberg@gmail.com

Posted in .NET, Command-Line | Tagged , | 20 Comments

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
Posted in .NET, Command-Line | Tagged , | 25 Comments