Easy argument parsing for .Net applications. Handles options (arguments with parameters) and flags (simple switches), with the facility to show automatically generated usage details and errors.
Compatible with NetStandard 2.0 for use in Core 3 or later (tested to Net 8). Available as a nuget package.
- What the User Sees
- Supported Features
- Example Usage
- Custom Validation for Options
- Showing Helpful Information to the User
- Getting the Provided Options and Flags
- Checking for Errors Manually
- Examples of Argument Errors
Here's a preview. Details are discussed further on. All this ouput is automatically generated on demand, with the headings being customisable.
User command:
MyApp -serve -from "15 APR 1980 GMT" -verbose 9999 -write "../output"
When called with the above (and using an example configuration detailed below) the default values for missing options will be included automatically and the user will see the following:
Usage:
-port integer Port to start the dev server on [1337]
-read text * Folder to read the site from ["site"]
-write text * CSV file to write the result to
-from datetime * Earliest date/time ["01 JAN 1980"]
-serve Start the site going in a dev server
-force Overwrite any destination content
(*) denotes a required option
[ ] is a default value
Provided:
-port 1337
-read "site"
-write "../output"
-from "15/04/1980 01:00:00"
-serve
Issues:
-write does not hold a CSV filename
-verbose is an unknown option
- Supports required named options
- Supports optional named options
- Supports defaults for option values
- Supports optional named flags
- Display automatically formatted help/usage text showing supported flags/options
- Also shows argument types, defaults, and an optional legend
- Display all errors (automatically formatted)
- Display all provided arguments (automatically formatted, including any defaults)
- Allows custom validators for options
- For example enforcing that a provided string is a CSV filename
- Option data types support any
IConvertable
, includingint
,bool
,DateTime
- Arguments can use either
-
or--
prefixes - Helpful errors for a variety of situations
- Missing required options
- Unknown options or flags
- Custom validator errors
- Option values of incorrect type
- Unexpected values (not following an option)
- Set up the options and flags
- Optionally show automatically-formatted instructions to your callers
- Get ArgsParser to deal with the provided arguments
- Show any errors (also automatically formatted)
- Use the options and flags in your own code
There's a tiny example console app, the contents of which are basically as follows:
using ArgsParser;
...
// Define the options and flags, including whether required and any default values.
var indent = 2;
var now = DateTime.Now.ToString("s");
var parser = new Parser(args)
.SupportsOption<int>("port", "Port to start the dev server on", 1337) // Optional, with default.
.RequiresOption<string>("read", "Folder to read the site from", "site") // Required, with default.
.RequiresOption<string>("write", "CSV file to write the result to") // Required, no default.
.RequiresOption<DateTime>("from", "Earliest date/time", "01 JAN 1980") // Required, with default.
.SupportsFlag("serve", "Start the site going in a dev server") // Optional flag.
.SupportsFlag("force", "Overwrite any destination content") // Optional flag.
.AddCustomValidator("write", IsCSV) // Automatic extra check.
.ShowHelpLegend(true) // Include explanatory notes in Help text?
.Help(indent, "Usage:") // Show instructions to the user.
.Parse() // Check the provided input arguments.
.ShowProvided(indent, "Provided:"); // Summarise the provided options and flags.
// Show any errors, and abort.
if (parser.HasErrors)
{
parser.ShowErrors(indent, "Issues:");
return;
}
// Examples of accessing the options/flags.
var shouldServe = parser.IsFlagProvided("serve");
var port = parser.GetOption<int>("port");
var readFromFolder = parser.GetOption<string>("read");
var writeToFolder = parser.GetOption<string>("write");
The methods used, including AddCustomValidator()
, are detailed further on.
Flags don't need custom validation; they are either provided or they're not. For options you can add custom validation to go beyond the built-in 'required' setting and the user-provided value's conversion to the expected data type.
- Standard validation is concerned with the presence/absence of arguments
- Custom option validators allow you to also check their contents
For example, here's a custom validator function that checks an option contains a CSV filename. This same function can be assigned to multiple options (eg both an input filename and an output filename). You can also use inline lambda but a full function is both clearer for explanatory purposes and also reusable.
/// <summary>Sample validator function which checks for a CSV filename.</summary>
/// <param name="key">Name of the argument.</param>
/// <param name="value">Content passed in.</param>
/// <returns>A list of any errors to be added to the parser's automatic ones.</returns>
private List<string> IsCSV(string key, object value)
{
// In reality we would also need null checks etc.
var errs = new List<string>();
var ext = Path.GetExtension($"{value}").ToLowerInvariant();
if (ext != ".csv") errs.Add($"{key} does not hold a CSV filename");
return errs;
}
A custom validator always receives an option name and any value provided. It should return a list of zero or more error messages which will be automatically included alongside the standard automatically generated ones.
The incoming value is an object
as the incoming data type may be one of a variety. The signature could be made generic, but validators are your code so you know what types your validator can expect and can safely cast as appropriate.
Once you have a validator you need to register it for any options requiring the check. You do this by calling AddCustomValidator
with the option name and the validation function.
var parser = new Parser(args)
.SupportsOption<string>("filename", "A CSV filename")
.AddCustomValidator("filename", IsCSV);
parser.Parse();
Most of the helpful text methods discussed below take two parameters:
int indent = 0
- allows the lines of text to be shifted to the right
string heading = ""
- any heading to show above the text (not indented)
In general an indent
of 0
is fine without headings, and 2
works well with headings.
This is the typical 'usage' information users might expect to see. Options and flags are displayed in the order they were added to the parser instance in your code, and whilst it's entirely up to you it's usually clearer if you therefore register all your options before adding any flags.
Here's an example help output showing a variety of options/flags:
-port integer Port to start the dev server on [1337]
-read text * Folder to read the site from [site]
-write text * Folder to write the result to
-apr number Annual interest [3.596]
-fee number Monthly charge [19.50]
-secure true/false Serve on HTTPS? [True]
-until datetime When to stop serving [22/08/2023 06:28:13]
-force Overwrite any destination content
-serve Start the site going in a dev server
* is required, values in square brackets are defaults
The last line is optional and can be deactivated by .ShowHelpLegend(false)
when configuring the Parser
instance (the default is true
).
All issues are displayed in a list. There are two distinct types of error and all errors of the first type will appear before any of the second type.
- The first type are errors with known options/flags and they will all show first in the order the options/flags were registered in the parser
- The second type are errors where the thing provided by the user isn't recognised (eg an unknown flag) and these appear in the order they were provided in the arguments
-write is required
-run is an unknown flag
This shows all the provided arguments in the order the options/flags were added to the parser instance. Any options which weren't provided by the user will also appear here if a default was applied.
-port 3000
-read in.txt
-force
-serve
These return true
if the option or flag was provided.
if (parser.IsFlagProvided("serve")) ...
Returns the generically typed value provided.
var port = parser.GetOption<int>("port");
- If the option provided was the wrong type an
InvalidCastException
is thrown - If the option was not provided but there is a default configured that is returned
- If the option was not provided and there is no default then the default value for the .Net type is returned (eg
0
for anInt32
orfalse
for abool
)
This is a helper method; checks for specific options and flags are simpler using the type-aware IsOptionProvided()
, IsFlagProvided()
, and GetOption()
as detailed above.
This method returns a dictionary of key/value pairs for the provided arguments in the order they were created on the parser instance in your code.
For each entry the key
is the name of the matching option or flag and the value
(returned as an object
) contains the type-converted value for an option or null
for a flag (as flags don't have values).
You can easily isolate options and flags using something like .Where(x => x.Value == null)
.
Example usage:
// Display arguments as either `-flag` or `-option value`.
Console.Write("MyApp");
foreach (var item in parser.GetProvided())
{
if (item.Value == null) Console.Write($" -{item.Key}");
else Console.Write($" -{item.Key} '{item.Value}'");
}
Assuming MyApp
was the name of your application, this would recreate the command used when it was called. For example:
MyApp -port "3000" -read "in.txt" -force -serve
That's a contrived example usage, though, as the next command does this for you anyway ...
This automatically wraps up the result of Parser.GetProvided()
(detailed above) as a space-delimited command argument string. In other words, it returns all the provided options/flags in the ideal format (minus the leading application name).
This can be used for example to include the command the user ran within the output their command generated.
Console.WriteLine("MyApp " + parser.GetProvidedAsCommandArgs());
This would output something like:
MyApp -port "3000" -read "in.txt" -force -serve
Usually all you need to do is check one flag and use the built-in helper to show the issues:
if (parser.HasErrors)
{
parser.ShowErrors(indent, "Issues:");
return;
}
If you want more granular access to the errors there are two collections:
Dictionary<string, string> ExpectationErrors
- Contains any errors where option/flag expectations are not met by the arguments
- Keyed by alphabetical option/flag name
- Example:
"write", "-write does not hold a CSV filename"
SortedList<int, string> parser.ArgumentErrors
- Contains errors relating to unknown arguments, indexed by order of discovery
- Keyed by order of discovery
- Example:
2, "-verbose is an unknown option"
These examples assume the Parser
was defined as shown in the Example usage section previously detailed.
Example user input:
MyApp -run data "Site Title" --serve -ignore -port 3000
There are a few things wrong with this input in relation to the setup of the options/flags in the example usage code:
- The
-write
option is required but not provided - The provided
-run
option is not known - The
"Site Title"
argument is orphan text with no option name preceeding it - The provided
-ignore
flag is not known
Here's what that looks like when run:
Issues:
-write is required
-run is an unknown option
Unexpected value: Site Title
-ignore is an unknown flag
Whilst the -read
option is missing there is no error logged as it was defined with a default value of site
and so the requirement is automatically met. The value data
is not treated as an error because even though -run
is an unknown option ArgsParser knows that data
would have belonged to -run
so it isn't another error.
Errors come in two collections (the property Parser.HasErrors
will be true
if either has entries). These correspond to the two blocks of error messages described in the ShowErrors()
method.
ExpectationErrors
are where specific expectations are not met (eg a missing required option), which means the relevant option/flag whose expectations are not being met is known- Custom option validator errors will also be in here as they apply to known options
ArgumentErrors
are where something was provided but the nature of the issue means we can't be sure which option/flag it relates to - for example a value was provided without an option name preceeding it
Based on the example above the errors (as key/value pairs) will be as follows:
ExpectationErrors
keyed by the name of the related option/flagwrite
=>-write is required
ArgumentErrors
keyed by the 0-based offset within the arguments provided0
=>-run is an unknown option
- This is assumed to be an option not a flag as it is followed by a value
2
=>Unexpected value: Site Title
- This is unexpected because
run
was not recognised as a valid option
- This is unexpected because
4
=>-ignore is an unknown flag
- This is assumed to be a flag because it is followed by
-port
rather than a value
- This is assumed to be a flag because it is followed by
Copyright K Cartlidge 2020-2024.
Licensed under GNU AGPLv3 (see here for more details). See the CHANGELOG for current status.