Library for parsing commandline arguments into much more managable StartOptions and StartOptionGroups for .net and .net-core applications (.net-standard 1.3). For detailed information take a look this repository's wiki.
The first step to using this library is to install the lates version of the nuget package for your project, for example by using the package manager console in Visual Studio:
Install-Package LunarDoggo.StartOptions [-ProjectName <your project name>]
This library provides two distinct ways for customizing your application's command line arguments:
- by utilizing attributes
- by building the StartOptions yourself
At first create some classes that implement the interface IApplicationCommand
, make sure to define at least one constructor per class that is decorated with the StartOptionGroupAttribute and contains parameters that are decorated with the
StartOptionGroupValueAttribute, StartOptionAttribute, GrouplessStartOptionAttribute or GrouplessStartOptionReferenceAttribute. If you want to add constructor parameters that aren't decorated with any of these attributes, you have to provide an IDependencyProvider. The method Execute()
contains the code that will run the command:
public class AddCommand : IApplicationCommand
{
private readonly int firstValue, secondValue;
[StartOptionGroup("add", "a", Description = "Adds two integers together")]
public AddCommand([StartOption("value-1", "1", Description = "First value", Mandatory = true, ValueType = StartOptionValueType.Single, ParserType = typeof(Int32OptionValueParser))] int firstValue,
[StartOption("value-2", "2", Description = "Second value", Mandatory = true, ValueType = StartOptionValueType.Single, ParserType = typeof(Int32OptionValueParser))] int secondValue,
[GrouplessStartOption("verbose", "v")]bool verbose)
{
this.secondValue = secondValue;
this.firstValue = firstValue;
//do something with the verbose switch
}
public void Execute()
{
Console.WriteLine("{0} + {1} = {2}", this.firstValue, this.secondValue, this.firstValue + this.secondValue);
}
}
Note: If you want to use the same groupless StartOption
across multiple constructors/commands, take a look at the section Final notes and hints
below.
After you created all the commands you want to use, create a new class that inherits from CommandApplication
and override its abstract methods:
class DemoApplication : CommandApplication
Set the commands the application will be able to run, all available command line arguments will be extracted from:
protected override Type[] GetCommandTypes()
{
return new[] { typeof(AddCommand), typeof(ReadFileCommand) };
}
Set the IDependencyProvider to be used to resolve constructor parameters that aren't decorated with a StartOptionAttribute. If you don't want to use this feature, simply return null
. The library is shipped with the SimpleDependencyProvider, you can also use dependency injection frameworks, like Ninject, but you'll have to implement a proxy class implementing IDependencyProvider
to allow access to the dependency framework. This example will use the SimpleDependencyProvider:
protected override IDependencyProvider GetDependencyProvider()
{
//true: throw an exception if a dependency can't be resolved
SimpleDependencyProvider provider = new SimpleDependencyProvider(true);
//Add dependencies to the cache like so, keep in mind that every Type can only be registered once:
provider.AddSingleton<IDatabase>(new MySqlDatabase("connection string here"));
return provider;
}
And finally implement the method that will print the help page of your application, you can implement a custom help page printing mechanism or simply use the included class ConsoleHelpPrinter:
protected override void PrintHelpPage(StartOptionParserSettings settings, IEnumerable<HelpOption> helpOptions, IEnumerable<StartOptionGroup> groups, IEnumerable<StartOption> grouplessOptions)
{
new ConsoleHelpPrinter('\t').Print(settings, helpOptions, groups, grouplessOptions);
}
If you picked the builder-based approach, create a new class that inherits from AbstractApplication
and override its abstract methods:
class DemoApplication : AbstractApplication
Set your ApplicationStartOptions inside of GetApplicationStartOptions
, note that valid StartOption names must start with either a letter or a number and can only contain letters, numbers, underscores and hyphens/dashes. Also note, that you can also provide custom HelpOptions and StartOptionParserSettings in this method (see Demo-Project):
protected override ApplicationStartOptions GetApplicationStartOptions()
{
StartOptionGroup[] groups = new StartOptionGroup[]
{
new StartOptionGroupBuilder("add", "a").SetDescription("Adds two integers together")
.AddOption("value-1", "1", (_option) => _option.SetDescription("First value").SetValueType(StartOptionValueType.Single).SetValueParser(new Int32OptionValueParser()).SetRequired())
.AddOption("value-2", "2", (_option) => _option.SetDescription("Second value").SetValueType(StartOptionValueType.Single).SetValueParser(new Int32OptionValueParser()).SetRequired())
.Build()
};
StartOption[] grouplessOptions = new StartOption[]
{
new StartOptionBuilder("verbose", "v").SetDescription("Enable verbose output").Build()
};
return new ApplicationStartOptions(groups, grouplessOptions);
}
Implement the logic for printing help-pages inside of PrintHelpPage
. You can use the predefined class ConsoleHelpPrinter
as shown below or define your own method of displaying a help page to your users:
protected override void PrintHelpPage(StartOptionParserSettings settings, IEnumerable<HelpOption> helpOptions, IEnumerable<StartOptionGroup> groups, IEnumerable<StartOption> grouplessOptions)
{
new ConsoleHelpPrinter('\t').Print(settings, helpOptions, groups, grouplessOptions);
}
Implement the logic of the execution of your application inside of Run
:
protected override void Run(StartOptionGroup selectedGroup, IEnumerable<StartOption> selectedGrouplessOptions)
{
if(selectedGrouplessOptions.Any(_option => _option.ShortName.Equals("v")))
{
Console.WriteLine("Verbose option was toggled");
}
switch(selectedGroup.ShortName)
{
case "a":
StartOption firstOption = group.GetOptionByShortName("1");
StartOption secondOption = group.GetOptionByShortName("2");
if (firstOption.HasValue && secondOption.HasValue)
{
int first = firstOption.GetValue<int>();
int second = secondOption.GetValue<int>();
Console.WriteLine("{0} + {1} = {2}", first, second, first + second);
}
else
{
if (!firstOption.HasValue)
{
Console.WriteLine("Please provide the first number for the addition");
}
if(!secondOption.HasValue)
{
Console.WriteLine("Please provide the second number for the addition");
}
}
break;
default: throw new NotImplementedException();
}
}
Regardless of whether you chose the attribute-based or builder-based approach, you must finally instantiate your application class in the Main(string[] args)
-method and call the Run(string[] args)
-method on your application class:
static void Main(string[] args)
{
DemoApplication application = new DemoApplication();
application.Run(args);
}
Afterwards you will be able to call your application from the commandline like so:
/> .\DemoApplication.exe --add -1=10 -2=5 --verbose -h
In this example, your application will be using the "add"-StartOptionGroup with the subordinate StartOptions "value-1" (value = 10) and "value-2" (value = 5), the verbose-flag is set. As the HelpOption "h" is also used, your application will display the help-page for the provided commandline arguments without actually executing the operation, in order to run the addition, just omit the "-h" option.
By default your StartOptions
will parse arguments as strings
. If you wish to parse the provided command line argument into another type, you have to specify the parser type as shown above. The library ships with these predefined parsers:
BoolOptionValueParser
ByteOptionValueParser
DoubleOptionValueParser
FloatOptionValueParser
Int16OptionValueParser
Int32OptionValueParser
Int64OptionValueParser
Note: FloatOptionValueParser
as well as DoubleOptionValueParser
use the current system culture as the source of the floating point number format.
You can create custom IStartOptionValueParsers for any type you like by inheriting from AbstractStartOptionValueParser
If you are using the command-based approach, make sure to register your custom value parser by calling before executing the Run
method of your application:
var instance = new CustomOptionValueParser();
StartOptionValueParserRegistry.Register(instance);
- Please note, that the help printer by default will display all available
StartOptions
only if the commandline arguments provided by the user only containedHelpOptions
and nothing else. If there areStartOptions
orStartOptionGroups
contained in the command line arguments, the help page will only contain these options. In order to change that, override the following method and provide thePrintHelpPage
method with all availableStartOptions
andStartOptionGroups
:
protected override void PrintHelpPage(ParsedStartOptions parsed)
{
//The following four lines assume, that you defined methods for getting your StartOptionParserSettings, StartOptionGroups, StartOptions and HelpOptions
StartOptionParserSettings settings = this.GetStartOptionParserSettings();
IEnumerable<StartOptionGroup> groups = this.GetStartOptionGroups();
IEnumerable<HelpOption> helpOptions = this.getHelpOptions();
IEnumerable<StartOption> options = this.GetStartOptions();
this.PrintHelpPage(settings, helpOptions, groups, options);
}
- If you wish to use the same groupless
StartOption
with the command-based approach across multiple different constructors or classes, at first implement one command with a constructor parameter decorated withGrouplessStartOptionAttribute
and another one (another constructor in the same or a different class) decorated withGrouplessStartOptionReferenceAttribute
. Make sure that no groupless start options use duplicate names:
class FirstCommand : ApplicationCommand
{
[StartOptionGroup("first", "f")]
public FirstCommand(..., [GrouplessStartOption("verbose", "v", Description = "Enable verbose output", IsGrouplessOption = true)]bool verbose)
{
...
}
...
}
class SecondCommand : ApplicationCommand
{
[StartOptionGroup("second", "s")]
public SecondCommand(..., [GrouplessStartOptionReference("verbose")]bool verbose)
{
...
}
...
}
- 1 You can also decorate your
CommandApplication
or any of yourIApplicationCommand
implementations withGrouplessStartOptionAttribute
if you prefer your grouless StartOptions to be declared in a central location:
[GrouplessStartOption("verbose", "v", Description = "Enable verbose output")]
public class DemoApplication : CommandApplication
{
...
}
[GrouplessStartOption("debug", "d", Description = "Enable debug mode")]
public class DemoCommand : IApplicationCommand
{
public DemoCommand(..., [GrouplessStartOptionReference("verbose")]bool verbose,
[GrouplessStartOptionReference("debug")]bool debug)
{
...
}
...
}
- If you want to handle groupless StartOptions of your
CommandApplication
in a central location, you can attach handlers to your application (note: right now you can only attach one handler per groupless StartOption) by using these methods either in your application's constructor or from outside your application class:
CommandApplication app = new DemoApplication();
//for groupless StartOptions of type Multiple
app.AddGlobalGrouplessStartOptionHandler<T>("numbers", (int[] _numbers) => this.Numbers = _numbers);
//for groupless StartOptions of type Single
app.AddGlobalGrouplessStartOptionHandler<T>("user", (string _username) => this.Username = _username);
//for groupless StartOptions of type Switch
app.AddGlobalGrouplessStartOptionHandler("verbose", () => this.Verbose = true);
- Caveat when using "/" as a prefix: Please keep in mind that the prefix "/" for short or long StartOption names can conflict with certain CLI environments, such as git bash for Windows, which could interpret options starting with a "/" as an absolute path to a file or application instead of just a simple string that should be passed to your application.