Command Line Parser
Motivation
Every now and then, we write a little console application to quickly get a job done. But the parsing of the arguments is always a pain. We don't invest any precious time into good exception behaviour because we just want it to do a simple job. Of course, this results in a mess after some time. Therefore, we built this little command line parser to make parsing command line arguments a simple and quick task.
Features
- fluent definition syntax using lambdas
- named, positional and switch arguments
- required and optional arguments
- value restrictions
- alias
- type conversion
- semi-automatic composition of a usage help message
Sample
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | namespace Appccelerate.CommandLineParser.Sample { using System; public class Program { public static void Main( string [] args) { const string ShortOutput = "short" ; const string LongOutput = "long" ; // set default values here string output = null ; bool debug = false ; string path = null ; string value = null ; int threshold = 0; // specify the known arguments var configuration = CommandLineParserConfigurator .Create() .WithNamed( "o" , v => output = v) .HavingLongAlias( "output" ) .Required() .RestrictedTo(ShortOutput, LongOutput) .DescribedBy( "method" , "specifies the output method." ) .WithNamed( "t" , ( int v) => threshold = v) .HavingLongAlias( "threshold" ) .DescribedBy( "value" , "specifies the threshold used in output." ) .WithSwitch( "d" , () => debug = true ) .HavingLongAlias( "debug" ) .DescribedBy( "enables debug mode" ) .WithPositional(v => path = v) .Required() .DescribedBy( "path" , "path to the output file." ) .WithPositional(v => value = v) .DescribedBy( "value" , "some optional value." ) .BuildConfiguration(); // create a parser from the configuration and parse the command line arguments var parser = new CommandLineParser(configuration); var parseResult = parser.Parse(args); // print usage if parsing failed if (!parseResult.Succeeded) { Usage usage = new UsageComposer(configuration).Compose(); Console.WriteLine(parseResult.Message); Console.WriteLine( "usage:" + usage.Arguments); Console.WriteLine( "options" ); Console.WriteLine(usage.Options.IndentBy(4)); Console.WriteLine(); return ; } // run your program Console.WriteLine( "parsed successfully: path = " + path + ", value = " + value + "output = " + output + ", debug = " + debug + ", threshold = " + threshold); } } } |
Why yet another command line parser?
We needed a command line parser for Appccelerate.Version. We used the approach describe in this blog post by Daniel. And none of the existing .Net command line parser libraries met our requirements. Either the license was too restrictive, the code was of bad quality, the project was abandoned or we didn't like the API of the library. Therefore, Urs took the time to build our own little command line parser with focus on easy usage and high quality.
Conventions
The command line parser uses the following conventions:
-
is used for named and switch arguments--
is used for long alias
Argument Types
Appccelerate.CommandLineParser knows three types of arguments.
Named Arguments
A named argument is an argument identified by a name and having an associated value, like foo.exe -name value
.
A named argument is specified using WithNamed
:
1 2 3 4 5 6 7 | var configuration = CommandLineParserConfigurator .Create() .WithNamed( "o" , value => output = value) .HavingLongAlias( "output" ) .Required() .RestrictedTo(ShortOutput, LongOutput) .DescribedBy( "method" , "specifies the output method." ) |
You have to specify the name (without -
) and the callback that is called with the value when this argument is found.
Optionally, you can specify:
HavingLongAlias
specifies a long alias (e.g. --output).Required
specifies that the argument is required. If left out, the argument is optional.RestrictedTo
specifies allowed values - all other values result in a parsing error. If left out, all values are allowed.DescribedBy
specifies the help text by naming a place holder for the value and a description. If left out, "value" is used for the place holder and the description is empty.
Positional Arguments
A positional argument is an argument identified by its position (first positional, second positional, ...), like foo.exe first second third
.
A positional argument is specified using WithPositional
. The position is specified by the order of declaration:
1 2 3 4 5 | var configuration = CommandLineParserConfigurator .Create() .WithPositional(v => path = v) .Required() .DescribedBy( "path" , "path to the output file." ) |
You have to specify the callback that is called with the value when this argument is found.
Optionally, you can specify:
Required
specifies that the argument is required. If left out, the argument is optional.DescribedBy
specifies the help text by naming a place holder for the value and providing a description. If left out, "value" is used for the place holder and the description is empty.
Switch Arguments
A switch argument is an argument identified by its name, like foo.exe -first -second -third
.
A switch is always optional.
A switch argument is specified using WithSwitch
:
1 2 3 4 5 | var configuration = CommandLineParserConfigurator .Create() .WithSwitch( "d" , () => debug = true ) .HavingLongAlias( "debug" ) .DescribedBy( "enables debug mode" ) |
You have to specify the callback that is called when this argument is found.
Optionally, you can specify:
HavingLongAlias
specifies a long alias (e.g. --debug).DescribedBy
specifies the help text. If left out, the description is empty.
Setup Configuration
You can use the CommandLineParserConfigurator
to specify the known arguments:
1 2 3 4 5 6 7 | CommandLineConfiguration configuration = CommandLineParserConfigurator .Create() .WithNamed( "o" , v => output = v) . . . .BuildConfiguration(); |
The configuration can then be used to create a parser and to create the usage message.
Parse Command Line Arguments
You can create a command line parser using a command line configuration, and parse the command line arguments:
1 2 | var parser = new CommandLineParser(configuration); ParseResult parseResult = parser.Parse(args); |
The ParseResult
that is returned contains a flag whether parsing was successful (parseResult.Succeeded
)
and in the case of an error the error reason (parseResult.Message
).
Compose Usage
You can use the UsageComposer
to create the usage help message.
1 | Usage usage = new UsageComposer(configuration).Compose(); |
The returned Usage
contains the call syntax (usage.Arguments
) and the options (usage.Options
):
Arguments sample: -required <value> [-optional <value>] [-switch] <positional>
.
[ ]
denote optional arguments< >
denote placeholders
Options sample:
1 2 3 | -o (--output) <value = { first | second }> specifies the output method. -d use debug information <path> the target file path |
The options show the name (if any), the alias (if any), the value place holder (if any), the allowed values (if specified) and the description.
You can write this information to the console:
1 2 3 4 | Console.WriteLine(parseResult.Message); Console.WriteLine( "usage:" + usage.Arguments); Console.WriteLine( "options" ); Console.WriteLine(usage.Options.IndentBy(4)); |
The IndentBy
method can be used to indent lines of strings.
Type Conversion
If you have command line arguments that are basic types besides strings, you can use type conversion:
For named arguments: .WithNamed("t", (int v) => threshold = v)
.
For positional arguments: .WithPositional((bool b) => flag = b)
.
Only type conversions supported by Convert.ChangeType
are supported.
Current limitations
- only
-
/--
are supported to identify names/alias - no support for help argument, e.g.
-?
or-help
- no support for commands, e.g.
foo.exe do -name value
Road Map
- nicer usage output
- improved type conversion