class ClapApp
{
[Verb]
public static void Foo(string bar, int count)
{
for (int i = 0; i < count; i++)
{
Console.WriteLine("This parser kicks {0}", bar);
}
}
}
class Program
{
static void Main(string[] args)
{
Parser.Run<ClapApp>(args);
}
}
C:\> myexe foo -bar=Ass! -count=10
This parser kicks Ass!
This parser kicks Ass!
This parser kicks Ass!
This parser kicks Ass!
This parser kicks Ass!
This parser kicks Ass!
This parser kicks Ass!
This parser kicks Ass!
This parser kicks Ass!
This parser kicks Ass!
Getting Started
- Get CLAP (either download the binaries or install it with NuGet. See the Download section)
- Add a reference to the dll from your Console or Windows application
- Add a
using
statement: using CLAP;
- Implement a verbs class (see the Attributes section)
-
From your
void Main
method, add a call to CLAP's parser using your class:
static void Main(string[] args)
{
Parser.Run<TheApp>(args);
}
Static vs. Instance
If all the verbs of a class are static, using the above example is enough.
However, if the verbs are not static, calling the parser requires an instance of the class.
The following example shows how to the parser with a instance of the class:
static void Main(string[] args)
{
var theApp = new TheApp();
Parser.Run(args, theApp);
}
Installing CLAP using NuGet
If you have NuGet installed, steps 1-2 can be skipped.
Either find CLAP from the NuGet manager, or run the following command from NuGet's console:
PM> Install-Package clap
Attributes
Using CLAP with attributes is the easiest way to create your app's functionality.
CLAP's attributes allow you to define various verbs, parameters, global parameters, even error-handling and more.
There's no reason for your void Main()
method to be more than a single line of code.
Verb
Marks a method as an available verb.
Properties:
IsDefault (Boolean)
Whether this verb is the class's default.
A default verb is called when no verb argument was entered.
Only a single verb can be set as default. Having more than one default verb
will throw a MoreThanOneDefaultVerbException
.
Aliases (String)
Additional aliases for the verb.
Description (String)
Used to describe the verb when displaying help.
Example:
class Program
{
[Verb(IsDefault = true)]
void Foo()
{
}
}
Parameter
Adds additional properties to verb's parameters.
Using the Parameter attribute is only required if any of the properties is needed, otherwise, it is optional.
Supported types:
- String
- Numeric (int, float etc)
- Boolean (can also used as switches:
-p=true
is the same as -p
)
- Enums
- Guid
- Uri
- Arrays of all the above (as CSV)
- Any other type as a JSON/XML string. See File Input
Properties:
Default (Object)
The default value of the parameter, in case no input was provided
Required (Boolean)
Whether the parameter is required
Aliases (String)
Additional aliases for the parameter
Description (String)
Used to describe the verb when displaying help
Example:
class Program
{
[Verb]
void Foo(
[Parameter(Required = true)] string name,
[Parameter(Default = 5)] int count,
bool upperCase,
int[] numbers)
{
}
}
Empty
Marks a method to execute when no input was provided by the user.
Only a single method can be marked with [Empty]
in a class.
Trying to mark more than one [Empty]
will throw a MoreThanOneEmptyHandlerException
.
Example:
class Program
{
[Verb]
void Foo()
{
}
[Empty]
void NoInput()
{
}
}
Error
Marks a method to execute when an error occurs.
Only a single method can be marked with [Error]
and it must match the following signature:
void Error(ExceptionContext context) {}
Notice that there is no restriction for the name of the method, only the signature.
Trying to mark more than one [Error]
will throw a MoreThanOneErrorHandlerException
.
Example:
class Program
{
[Verb]
void Foo()
{
}
[Error]
void HandleError(ExceptionContext context)
{
if ( // something ...
{
context.ReThrow = true;
}
}
}
ExceptionContext
Exception (Exception)
The exception that was thrown during execution
ReThrow (Boolean)
Whether to re-throw the exception after handling it
Help
Marks a method to execute when the user requests for help.
The method must match the following signature:
void Help(string help) {}
Properties:
Name (String)
The parameter name for the help. If none specified - the method name is used instead.
Aliases (String)
Additional aliases for the parameter name
Example:
class Program
{
[Verb]
void Foo()
{
}
[Help]
void ShowHelp(string help)
{
}
}
Global
Marks a method as a global parameter. A global parameter is available for all verbs within a class.
The global attribute can be applied only to methods that accept no more than a single supported parameter.
If a method has no parameters - it is treated as a boolean switch.
All globals are called before the executing verb.
Properties:
Name (String)
The parameter name. If none specified - the method name is used instead.
Aliases (String)
Additional aliases for the parameter name
Description (String)
Used to describe the parameter when displaying help
Example:
class Program
{
[Verb]
void Foo()
{
}
[Global]
void Log(string message)
{
}
[Global(Aliases="d")]
void Debug()
{
Debugger.Launch();
}
}
File Input
In-addition to passing values to parameters in the regular way (-param=value
),
CLAP supports passing the content of text files as input to parameters.
A good motivation for using this feature is when using a custom class as an input argument.
A custom class requires a JSON/XML string as an argument value and passing such a string is easier within
a text file.
Passing the content of a file to a parameter is by adding a @
after the parameter name using the following syntax:
-param@=file_path
:
vs =
As a parameter-value separator, CLAP supports both :
and
=
.
It is better to use =
because the console (cmd.exe) does not
support tab-completion after the :
character.
Tab-completion is supported when using the =
character.
For example:
> myexe -persons@=..\data\persons.txt
Keep in mind that if the path contains spaces - you'll have to add quotation marks, as in:
> myexe -persons@="c:\input data\persons.txt"
Custom Types
Primitive types are converted from strings and are easily handled by CLAP.
In-addition to primitive types, enums and single-dimensional arrays, CLAP also supports
any type you can represent using a JSON/XML string.
Example
Suppose we have the following class which contains several properties and a list of another class:
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
}
public class PhoneNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
We can now create a verb that accepts an instance of that class:
[Verb]
void SavePerson(Person p)
{
// do something with the Person instance
}
Now we can pass an instance of a Person class from the command-line.
We need a JSON (or XML) representation of such an instance:
{
Age: 34,
Name: 'Adrian Aisemberg',
Email: 'adrianaisemberg@gmail.com',
PhoneNumbers: [
{
Type: 'Home',
Number: '001-2232322'
},
{
Type: 'Mobile',
Number: '002-7787787'
},
{
Type: 'Office',
Number: '003-4463565'
}
]
}
As an alternative, CLAP also supports XML:
<Person>
<Age>34</Age>
<Name>Adrian Aisemberg</Name>
<Email>adrianaisemberg@gmail.com</Email>
<PhoneNumbers>
<PhoneNumber>
<Type>Home</Type>
<Number>001-2232322</Number>
</PhoneNumber>
<PhoneNumber>
<Type>Mobile</Type>
<Number>002-7787787</Number>
</PhoneNumber>
<PhoneNumber>
<Type>Office</Type>
<Number>003-4463565</Number>
</PhoneNumber>
</PhoneNumbers>
</Person>
XML Beautifying
Using some .NET XML tweaks, such as marking properties with [XmlAttribute]
,
The above XML sample can be represented as the following:
<Person Age="34" Name="Adrian Aisemberg" Email="adrianaisemberg@gmail.com">
<PhoneNumbers>
<PhoneNumber Type="Home" Number="001-2232322"/>
<PhoneNumber Type="Mobile" Number="002-7787787"/>
<PhoneNumber Type="Office" Number="003-4463565"/>
</PhoneNumbers>
</Person>
Note that this topic is unrelated to CLAP and not discussed in this web-site.
Instead of passing this whole string (in a single line) directly from the command-line, we better
put it in a file and pass the file as an input:
> myexe saveperson -p@=..\data\person.txt
Collections are also supported, as in the following example:
[Verb]
void SavePersons(IEnumerable<Person> p)
{
// do something with the Persons collection
}
A collection of Persons as a JSON string looks like this:
[{
Age: 34,
Name: 'Adrian Aisemberg',
Email: 'adrianaisemberg@gmail.com',
PhoneNumbers: [
{ Type: 'Home', Number: '001-2232322' },
{ Type: 'Mobile', Number: '002-7787787' },
{ Type: 'Office', Number: '003-4463565' }
]
},
{
Age: 5,
Name: 'Yuval Aisemberg',
Email: 'yuval@email.com',
PhoneNumbers: [
{ Type: 'Home', Number: '001-2232322' },
{ Type: 'Mobile', Number: '002-9918172' }
]
}]
Validation
Validation attributes validate the input before executing the verb.
CLAP comes with some built-in validation attributes and custom validation can be easily implemented.
LessThan(double number)
Validates that a numeric value is less than a specified value.
Example:
class Program
{
[Verb]
void Foo([LessThan(10)] int count)
{
}
}
LessOrEqualTo(double number)
Validates that a numeric value is less or equals a specified value.
Example:
class Program
{
[Verb]
void Foo([LessOrEqualTo(30.5)] double angle)
{
}
}
MoreThan(double number)
Validates that a numeric value is more than a specified value.
Example:
class Program
{
[Verb]
void Foo([MoreThan(10)] int count)
{
}
}
MoreOrEqualTo(double number)
Validates that a numeric value is more or equals a specified value.
Example:
class Program
{
[Verb]
void Foo([MoreOrEqualTo(30.5)] double angle)
{
}
}
RegexMatches(string pattern)
Validates that a string value matches a specified regular-expression.
Example:
class Program
{
[Verb]
void Foo([RegexMatches(@"[0-9a-zA-Z]*")] string name)
{
}
}
FileExists
Validates that a file exists.
Example:
class Program
{
[Verb]
void Foo([FileExists] string filePath)
{
}
}
DirectoryExists
Validates that a directory exists.
Example:
class Program
{
[Verb]
void Foo([DirectoryExists] string path)
{
}
}
PathExists
Validates that a file or a directory exists.
Example:
class Program
{
[Verb]
void Foo([PathExists] string path)
{
}
}
Validate(Expression)
Examples:
class Program
{
[Verb]
[Validate("num1 > num2")]
void Foo1(int num1, int num2)
{
}
[Verb]
[Validate("num1 > num2 AND num2 > 100")]
void Foo2(int num1, int num2)
{
}
[Verb]
[Validate("name LIKE 'John*'", CaseSensitive = true)]
void Foo3(string name)
{
}
[Verb]
[Validate("name IN ('ping','pong')")]
void Foo4(string name)
{
}
[Verb]
[Validate("num1 + num2 >= num3")]
void Foo5(int num1, int num2, int num3)
{
}
}
Properties Validation
All validation attributes can be applied to properties of a custom type.
In-addition, the expression validation attribute can be applied to a custom class.
Following is a sample class having both properties and expression validations.
Example:
// Validate properties of this class
[Validate("Name NOT LIKE 'Banana*'")]
public class Product
{
// Validate the value of this property
[RegexMatches("#PP[0-9]*")]
public string ID { get; set; }
// Validate the value of this property
[MoreOrEqualTo(0)]
public int Index { get; set; }
public string Name { get; set; }
// Validate the properties of the value of this property
[Validate("Name NOT IN 'Adrian','Alon'")]
public ProductReview Review { get; set; }
}
// Validate the properties of all instances of this class
[Validate("Name NOT LIKE 'Bob*'")]
public class ProductReview
{
public string Name { get; set; }
// Validate the value of this property
[RegexMatches(@"^([0-9a-zA-Z]([-.\w]* ... ")]
public string Email { get; set; }
}
Class vs Instance Expression Validation
The Validate(Expression)
attribute can be applied
to both properties and also classes (in-addition to verbs).
If the attribute is placed over a class - all instances of that class will have to pass the validation.
If the attribute is placed over a property - it will validate the instance of the property, in-addition to the
class's validations.
In the above example, the Name property of a Product's review must pass both validations:
Name NOT IN 'Adrian','Alon'
Name NOT LIKE 'Bob*'
In any other instance of the Review class, not related to Product, the Name property must pass:
Custom Validation
CLAP comes with a set of built-in validation attributes, however,
additional custom validation attributes can be easily implemented and used.
ValidationAttribute, IParameterValidator
A single parameter custom validation
Start with implementing the IParameterValidator
interface:
class LengthValidator : IParameterValidator
{
public int Length { get; private set; }
public LengthValidator(int length)
{
Length = length;
}
public void Validate(ParameterInfo parameter, object value)
{
var str = (string)value;
if (str.Length < Length)
{
throw new ValidationException(
string.Format("Length should be at least {0}", Length));
}
}
}
Notice that the parameter
argument is not used in this example.
The important part is the actual value being validated. The additional
parameter
argument is provided in case validation needs to be against
reflected information about the parameter type, or, if that information is required for creating a
validation error message.
Anyway, it is provided and can be ignored.
Then implement the attribute by deriving from ValidationAttribute
:
[AttributeUsage(AttributeTargets.Parameter)]
class LengthAtLeastAttribute : ValidationAttribute
{
public int Length { get; private set; }
public LengthAtLeastAttribute(int length)
{
Length = length;
}
public override IParameterValidator GetValidator()
{
return new LengthValidator(Length);
}
public override string Description
{
get
{
return string.Format("Length is at least {0}", Length);
}
}
}
In this example, the attribute is used to validate that an input string has a length of
at least a specified number of characters.
For example:
[Verb]
void Foo([LengthAtLeast(10)] string text)
{
}
The
text
argument must be at least 10 characters long, otherwise,
an exception will be thrown.
The Description
property is used when generating the help string.
ParametersValidationAttribute, IInfoValidator<ParameterInfo>
Multiple parameters custom validation.
Single parameters validators are used to validate parameters against their own values.
With multiple parameters validatiors we can validate the values of all the parameters at once.
This allows us to validate dependencies between parameters or even complex scenarios.
Notice that multiple parameters validations are executed after all the single validators.
Start with implementing the IInfoValidator<ParameterInfo>
interface:
Example:
Having the following verb:
[Verb]
void Foo(int[] numbers, string[] names)
{
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine("{0}:{1}", numbers[i], names[i]);
}
}
We want to validate that both input arrays contain the same number of items.
Using single parameter validators is impossible to validate such a scenario.
We will use a custom multiple validator.
private class SameLengthValidator : IInfoValidator<ParameterInfo>
{
public void Validate(InfoAndValue<ParameterInfo>[] parameters)
{
// At this point - we already know that all the parameters
// have a value that matches their types.
// This validator works only for arrays so we'll add
// a simple assertion for that.
//
Debug.Assert(parameters.All(p => p.Value is Array));
// Cast them all to arrays
//
var arrays = parameters.Select(p => p.Value).Cast<Array>();
// Take the first length
//
var length = arrays.First().Length;
// Validate that all arrays have the same length
//
if (!arrays.All(a => a.Length == length))
{
throw new ValidationException(
"Not all arrays have the same length.");
}
}
}
Then implement the attribute by deriving from ParametersValidationAttribute
:
[AttributeUsage(AttributeTargets.Method)]
public class SameLengthAttribute : ParametersValidationAttribute
{
public override IInfoValidator<ParameterInfo> GetValidator()
{
return new SameLengthValidator();
}
public override string Description
{
get { return "All arrays are of the same length"; }
}
}
Now we can use the validation attribute:
[Verb]
[SameLength]
void Foo(int[] numbers, string[] names)
{
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine("{0}:{1}", numbers[i], names[i]);
}
}
And if the arrays are not of the same length - a validation exception will be thrown:
> myexe foo -numbers=1,2,3 -names=a,b
CLAP.ValidationException: Not all arrays have the same length.
> myexe foo -numbers=1,2,3 -names=a,b,c
1:a
2:b
3:c
Interception
Interception allows running code before and/or after each verb is executed.
Interception can be used for logging, exception handling, verb cancellation and more.
PreVerbExecution
Marks a method to execute before any verb is executed.
Only a single mathod can be marked as a [PreVerbExecution]
and it must match the following signature:
void Before(PreVerbExecutionContext context) {}
Notice that there is no restriction for the name of the method, only the signature.
Trying to mark more than one [PreVerbExecution]
will throw a MoreThanOnePreVerbInterceptorException
.
Example:
class Program
{
[PreVerbExecution]
void Before(PreVerbExecutionContext context)
{
}
}
PostVerbExecution
Marks a method to execute after any verb is executed.
Only a single mathod can be marked as a [PostVerbExecution]
and it must match the following signature:
void Before(PostVerbExecutionContext context) {}
Notice that there is no restriction for the name of the method, only the signature.
Trying to mark more than one [PostVerbExecution]
will throw a MoreThanOnePostVerbInterceptorException
.
Example:
class Program
{
[PostVerbExecution]
void After(PostVerbExecutionContext context)
{
}
}
VerbInterception
Marks a class with a type that implements IVerbInterceptor
.
The IVerbInterceptor
interface requires implementing both
IPreVerbInterceptor
and IPostVerbInterceptor
.
If only pre or post interception is required - implementing only one of the above interfaces is enough.
Example:
[VerbInterception(typeof(MyInterception))]
class Program
{
[Verb]
void Foo()
{
}
}
class MyInterception : IVerbInterceptor
{
public void BeforeVerbExecution(PreVerbExecutionContext context)
{
}
public void AfterVerbExecution(PostVerbExecutionContext context)
{
}
}
PreVerbExecutionContext
A context object that is passed during verb interception before the verb is executed.
Properties:
Method (CLAP.Method)
The verb method that will be executed.
Target (object)
Holds the instance of the class that owns the executed verb. In-case the verb is static, Target is null.
Parameters (ParameterAndValue[])
A collection of the parameters and their values.
UserContext (Dictionary<object, object>)
A dictionary that can be used to pass data from the pre-interceptor to the post-interceptor.
Cancel (Boolean)
A flag that informs the parser to cancel the execution of the verb. The post-interceptor will still be executed even if the verb is cancelled.
PostVerbExecutionContext
A context object that is passed during verb interception after the verb was executed.
Properties:
Method (CLAP.Method)
The verb method that was executed.
Target (object)
Holds the instance of the class that owns the executed verb. In-case the verb is static, Target is null.
Parameters (ParameterAndValue[])
A collection of the parameters and their values.
UserContext (Dictionary<object, object>)
A dictionary that can be used to pass data from the pre-interceptor to the post-interceptor.
Cancelled (Boolean)
Holds a flag indicating whether the verb execution was cancelled by the pre-interceptor.
Exception (Exception)
Holds an exception instance that was thrown in case an error occured during the verb execution.
Failed (Boolean)
In case an error occured an Exception
is not null, Failed is true.
Expressions
Creating a verbs class is easy and the result is a beautiful class with clear separation of concerns.
Defining error, help and empty handlers, as well as global parameters
can be also implemented using methods on an existing parser.
These methods use lambda expressions syntax to define the behavior.
If you own all the application's code, there is no real motivation for using the expressions syntax.
However, if you don't have the source of the program (you only have the binary)
and you can't derive from the program's class (it is sealed) - then adding the extra functionality is possible
only using expressions.
Register.EmptyHandler(Action handler)
Registers an action that will be executed when no input was provided by the user.
Only a single empty handler can be registered. Trying to register more than one
handler will throw a MoreThanOneEmptyHandlerException
.
Example:
static void Main(string[] args)
{
var p = new Parser<TheApp>();
p.Register.EmptyHandler(() => Console.WriteLine("No Input"));
p.Run(args);
}
This will print "No Input" whenever the exe is executed without any input.
Notice that anonymous delegates can be used instead of the lambda expression:
static void Main(string[] args)
{
var p = new Parser<TheApp>();
p.Register.EmptyHandler(delegate
{
Console.WriteLine("No Input");
});
p.Run(args);
}
This is more usefull when more than a single statement is to be executed.
Register.HelpHandler(string names, Action<string> helpHandler)
Registers an action that will be executed when help is requested by the user.
Example:
static void Main(string[] args)
{
var p = new Parser<TheApp>();
p.Register.HelpHandler("?,h,help", help => Console.WriteLine(help));
p.Run(args);
}
This will print the help string when either -?
,
-h
or -help
are provided.
The help string is automatically generated.
Register.EmptyHelpHandler(Action<string> handler)
Print the help string when no input is provided by the user.
Only a single empty help handler can be registered. Trying to register more than one
handler will throw a MoreThanOneEmptyHandlerException
.
Example:
static void Main(string[] args)
{
var p = new Parser<TheApp>();
p.Register.EmptyHelpHandler(help => Console.WriteLine(help));
p.Run(args);
}
This will print the help string when the program is executed without any arguments.
Register.ErrorHandler(Action<ExceptionContext> handler)
Registers an action that will be executed when an error occurs.
The action can assign a bool value to the ReThrow
property of the context indicating whether the exception should be re-thrown after handling.
If not assigning any value - false
is the default
Only a single error handler can be registered. Trying to register more than one
handler will throw a MoreThanOneErrorHandlerException
.
Example:
The following will print the error message in red:
static void Main(string[] args)
{
var p = new Parser<TheApp>();
p.Register.ErrorHandler(context =>
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(context.Exception.Message);
Console.ResetColor();
});
p.Run(args);
}
The following will print the error message in red and throw the exception after that:
static void Main(string[] args)
{
var p = new Parser<TheApp>();
p.Register.ErrorHandler(context =>
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(context.Exception.Message);
Console.ResetColor();
context.ReThrow = true;
});
p.Run(args);
}
Register.ParameterHandler(string names, Action action, [string description])
Register.ParameterHandler<T>(string names, Action<T> action, [string description])
Registers a parameter handler.
A parameter handler can be either an action that accepts no arguments
or an action that accepts one of the support argument types.
If the action accept no arguments (Action
) it is treated as a boolean switch.
If the action accpets one parameter of the supported types, the value of the argument is passed to the handler.
See the Parameter
attribute section for a list of supported types.
The description
parameter is used to generate the help string.
Example:
static void Main(string[] args)
{
var p = new Parser<TheApp>();
p.Register.ParameterHandler("d,debug", () => Debugger.Launch());
p.Register.ParameterHandler<int>(
"p,pause",
seconds => Thread.Sleep(1000 * seconds));
p.Run(args);
}
This will launch the debugger when the user enters either -d
or -debug
.
In-addition, when using -p
or -pause
,
the execution will pause for the specified seconds, for example: -p=5
.
Arguments are handled in the input order.
For example, entering -d -p=5
will first trigger the debugger and will then pause.
Entering -p=5 -d
will first pause and then trigger the debugger.
Register.PreVerbInterceptor(Action<PreVerbExecutionContext> interceptor)
Registers a verb interceptor that will be executed before any verb.
See the PreVerbExecutionContext
section for more info about the context object.
Only a single interceptor can be registered. Trying to register more than one
interceptor will throw a MoreThanOnePreVerbInterceptorException
.
Example:
static void Main(string[] args)
{
var p = new Parser<TheApp>();
p.Register.PreVerbInterceptor(context =>
{
// Log the method name
//
Log.Write("START: " + context.Method.MethodInfo.Name);
// Add the current time to the context
//
context.UserContext.Add("START", DateTime.Now);
});
p.Run(args);
}
Register.PostVerbInterceptor(Action<PostVerbExecutionContext> interceptor)
Registers a verb interceptor that will be executed after any verb.
See the PostVerbExecutionContext
section for more info about the context object.
Only a single interceptor can be registered. Trying to register more than one
interceptor will throw a MoreThanOnePostVerbInterceptorException
.
Example:
static void Main(string[] args)
{
var p = new Parser<TheApp>();
p.Register.PostVerbInterceptor(context =>
{
// Log the method name
//
Log.Write("END: " + context.Method.MethodInfo.Name);
// Take the "START" user context value
//
var start = (DateTime)context.UserContext["START"];
// Log the time it took to execute the verb
//
Log.Write(DateTime.Now.Subtract(start).ToString());
});
p.Run(args);
}
Multi Parsers
A parser can be executed on multiple verb-classes.
When a parser is executed on multiple classes, the user must add the class name (case-insensitive) as a prefix to each verb.
Example:
static void Main(string[] args)
{
Parser.Run<App1, App2, App3>(args);
}
This will run a static parser (having only static verbs) of the three specified types.
The same rules as a single parser regarding instance vs. static parsers apply also here:
static void Main(string[] args)
{
var a1 = new App1();
var a2 = new App2();
var a3 = new App3();
Parser.Run<App1, App2, App3>(args, a1, a2, a3);
}
Having the verb foo
in each of the above classes,
the user may call each one of them with the class name as a prefix:
> myexe app1.foo -param1 -param2 ...
Notes
-
Global, Error, Help, Empty: They are all available only in the class's context and not
for the whole multi-parser.
Having a global parameter in app1 will not make it available for app2.
-
Registered Expressions: Are available for the whole multi-parser.
Having a registered global handler makes it available for each class.
The same goes for all expressions, such as interceptors and help handlers.
Short Names
When possible, CLAP will create additional aliases for verbs and parameters
using only the first character of their names.
This happens automatically when the first letter is distinct between all
parameters of a verb, or between all verbs in a class.
Example:
class App
{
[Verb]
static void Foo(string name, int count)
{
}
[Verb]
static void Bar(string name, int number)
{
}
}
Foo
and Bar
have distinct first characters,
so Foo
will have an additional alias of "f" and Bar
will have "b".
The same goes for name
and count
parameters of Foo
,
so name
will have also "n" and count
will have "c".
The parameters of Bar
don't have a distinct first character so unless having
explicit aliases (using [Parameter(Aliases=...)]
, they will not have any additional alias.
RunConsole/RunWinForms
CLAP Provides two additional Run
methods:
Each runs a parser which already includes built-in support for some common functionality:
- Help, by using "-help", "-h" or "-?"
- Attach a Debugger, by using "-debug"
- Authomatic Error Handling, by printing the error message
RunConsole prints the help and the error messages to the console, while RunWinForms uses a message-box.
Examples
In this section you can see some sample classes.
At the bottom you can see execution samples with the resulting output.
A class with some verbs and validation:
class TheApp
{
[Verb(IsDefault = true, Description = "Prints 'Hello' and a name")]
void Hello(
[Parameter(Aliases = "n", Required = true, Description = "The name")]
string name,
[LessOrEqualTo(30)]
[Parameter(Aliases = "c", Default = 10,
Description = "The number of lines to print")]
int count)
{
for (int i = 0; i < count; i++)
{
Console.WriteLine("Hello {0}", name);
}
}
[Verb(Aliases = "cart")]
void Cartesian(int[] numbers, string[] names)
{
foreach (var number in numbers)
{
foreach (var name in names)
{
Console.WriteLine("{0}:{1}", number, name);
}
}
}
[Verb]
void Count(
[LessOrEqualTo(50), MoreOrEqualTo(0)] int start,
[LessOrEqualTo(100), MoreThan(50)] int end,
[Parameter(Aliases = "p,pause", Default = 1.5)] double pauseSeconds,
[Parameter(Aliases = "align")] Alignment alignment)
{
var sign = alignment == Alignment.Left ? "-" : "";
var format = "{0," + sign + "5}";
for (int i = start; i <= end; i++)
{
Console.WriteLine(format, i);
Thread.Sleep((int)(pauseSeconds * 1000));
}
}
public enum Alignment
{
Right,
Left,
}
}
The first verb, Hello
, is the default verb and it has two parameters:
name
: Required and has an alias "n".
-
count
: Has a default of 10 and an alias "c".
The value has to be less or equal to 30 to be valid.
The second verb, Cartesian
, prints all possible combinations of two given arrays
(a Cartesian product). The verb has two parameters:
numbers
: A list of numbers.
names
: A list of names.
The third verb, Count
, prints a list of numbers
with left or right alignment and a pause between each line. The verb has four parameters:
start
: The value has to be between 0 and 50.
end
: The value has to between 50 and 100.
pauseSeconds
: Default of 1.5 and aliases: "p" and "pause".
alignment
: An enum with an alias "align".
Sample run:
> myexe hello -n=CLAP -c=5
Hello CLAP
Hello CLAP
Hello CLAP
Hello CLAP
Hello CLAP
> myexe cart -numbers=1,5,8 -names=a,g,u,t
1:a
1:g
1:u
1:t
5:a
5:g
5:u
5:t
8:a
8:g
8:u
8:t
> myexe count -start=1 -end=55 -p=0.1 -align=Left
1
2
3
4
...
55
> myexe count -start=10 -end=55 -p=0.1 -align=Right
10
11
12
13
...
55
Notice that all names, including the verbs and parameters,
are case-insensitive but enum values are case-sensitive.
About
CLAP: A free and open-source project by Adrian Aisemberg (adrianaisemberg@gmail.com).
More by Adrian:
License
MIT License
Copyright (c) 2011 Adrian Aisemberg, SharpRegion
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.