The CuiMallet is a .NET library for making Command Line Interface. It depends on .NET Standard 2.0.
CuiMallet.CSharp is available as the NuGet package.
CuiMallet makes it easy to implement the conventions which POSIX recommends for command-line options [1] and to which GNU adds long options [2].
First, create an OptionSchema
object as follows:
var schema = Options.NewSchema();
And then, add the definition of the command-line options to the
OptionSchema
object.
For example, to add the --help
option, which has the shortened form
-h
, no argument, and the description "Show help message"
for its help
message, use the Add(string, char?, string)
method against the
OptionSchema
object as follows:
schema = schema.Add("help", 'h', "Show help message");
Note that the OptionSchema
object is immutable, so the Add
method returns
a new OptionSchema
object.
After adding all the options, use the Parse(string[])
method against the
schema
, with the specified string
array representing the command-line
arguments, which a static method Main(string[])
typically provides at the
entry point of a program, as follows:
public static void Main(string[] args)
{
var schema = Options.NewSchema()
// Adds options to schema.
⋮
.Add("help", 'h', "Show help message");
try
{
var setting = schema.Parse(args);
var options = setting.Options;
var arguments = setting.Arguments;
// Customizes the program behavior according to the options and
// arguments.
⋮
}
catch (OptionParsingException e)
{
// Handles the exception that the Parse method may throw.
⋮
}
}
The Parse(string[])
method returns the Setting
object,
which contains the options and the arguments.
The options
represent the parsed options in order of appearance.
Each option is given as an Option
object,
and the type of the options
is IEnumerable<Option>
.
Meanwhile, the arguments
represent the remaining non-option
arguments. The type of the arguments
is IEnumerable<string>
.
In addition, the Parse(string[])
method must be in the try
block followed by the catch
clause
handling an OptionParsingException
because it may throw the exception when the specified option is
not valid for the schema.
Here, there is a relationship between the options and the non-option arguments illustrated as follows:
command [Options...] [
--
] [Arguments...]
-
A component of Options... is a list separated with whitespace characters, which contains a string starting with a hyphen (
-
) character and consisting of two or more characters (e.g.,--help
,-h
). The string starting with a double hyphen (--
) is called an option, and the string after the double hyphen is called the option name. The option name consists of alphanumeric characters and hyphens. Meanwhile, the string consisting of a single character preceded with a single hyphen (-
) is called a shortened-form option, and the character after the hyphen is called a short name of the option. The short names are single alphanumeric characters. -
A component of Arguments... is a list of strings separated with whitespace characters.
Each component enclosed in square brackets can be omitted.
However, a double hyphen (--
) must be placed between Options... and
Arguments... only if the first element of Arguments...
begins with a hyphen character and is not a hyphen exactly.
Note that the option name in CuiMallet corresponds to the long option name
in GNU getopt_long
[3], and the short name corresponds to
the option name in POSIX getopt
[4].
There are the two types of options:
the options that can't have an option argument
(e.g., --help
as described above)
and that must have a single option argument.
For example, suppose the --file
option,
which has the shortened form -f
,
requires an option argument.
Then its argument must be supplied as any one of the following forms:
--file ARGUMENT
--file=ARGUMENT
-f ARGUMENT
-fARGUMENT
where you must replace the ARGUMENT
with the actual option argument.
Thus, --file index.html
, --file=infex.html
, -f index.html
,
and -findex.html
are equivalent.
However, if ARGUMENT
must be a zero-length string (that is, an empty string),
you cannot use the -fARGUMENT
form.
Note that, in general, there are two types in the options that have an option argument: the required argument option and the optional argument option. The former cannot omit an option argument, but the latter can omit it. However, CuiMallet has not yet implemented the latter.
Specifying -a -b -c
and -abc
are equivalent in meaning.
The concatenated options, except the last one, can't have an option argument.
Only the last of them can have an option argument as follows:
-abcf ARGUMENT
(or-abcfARGUMENT
)
where the ARGUMENT
is of the -f
option, and -a
, -b
, and -c
options cannot have an option argument.
You can abbreviate the option names as long as the abbreviations are unique.
For example, suppose there are only three options in the schema: --help
,
--verbose
, and --version
. Specifying --h
, --he
, --hel
, and --help
are equivalent. Likewise, --verb --vers
is equivalent to
--verbose --version
. However, specifying --ver
cause an error
because there are two options starting with it.
To add the --file
option (as described above) to the schema,
use the
Add(string, char?, string, string)
method against the schema object as follows:
schema = schema.Add("file", 'f', "FILE", "Specify an input file");
To get the value of the actual option argument,
use a RequiredArgumentOption
object as follows:
var options = setting.Options;
foreach (o in options)
{
if (o is RequiredArgumentOption a)
{
var arg = a.ArgumentValue;
⋮
}
⋮
}
Thus, the ArgumentValue
property of a RequiredArgumentOption
object
provides the value of the option argument.
You can specify the same option two or more times.
For example, suppose the option -f
, as noted above,
which takes an argument, is specified on the command line as follows:
-f foo -f bar -f baz
In such cases, the ArgumentValues
property of a RequiredArgumentOption
object is useful. The following code shows how to use the property.
var options = setting.Options;
foreach (o in options)
{
if (o is RequiredArgumentOption a)
{
var arg = a.ArgumentValue;
var all = string.Join(",", a.ArgumentValues);
Console.WriteLine($"{arg} {all}");
}
}
The ArgumentValues
property returns
the values of all the option arguments corresponding to the same option
in occurrence order.
Note that they do not contain the option arguments of the options
specified after it.
Thus, the output to the console is as follows:
foo foo
bar foo,bar
baz foo,bar,baz
You can also specify a callback function that takes the
Option
or RequiredArgumentOption
object as the argument when adding
the definition of the command-line options to the OptionSchema
object. From invoking the Parse(string[])
method of the
OptionSchema
object until returning, the function is called to provide
the Option
or RequiredArgumentOption
object
each time the object is created.
To add the option, which has no option argument, with a callback function
to the OptionSchema
object, use the
Add(string, char?, string, Action<Option>)
method as follows:
// Adds the definition of an Option with the callback function.
schema = schema.Add(
"help",
'h',
"Show help message",
o =>
{
// typeof(o) is Option.
⋮
});
In the same way,
to add the option, which has an option argument, with a callback function
to the OptionSchema
object, use the
Add(string, char?, string, string, Action<RequiredArgumentOption>)
method as follows:
// Adds the definition of a RequiredArgumentOption with the callback
// function.
schema = schema.Add(
"file",
'f',
"FILE",
"Specify an input file",
o =>
{
// typeof(o) is RequiredArgumentOption.
⋮
});
You can generate the help message of command-line options with
OptionSchema
object. To get the help message,
use the GetHelpMessage()
method against the schema
as follows:
public static void Main(string[] args)
{
var schema = Options.NewSchema()
.Add("file", 'f', "FILE", "Specify an input file");
.Add("help", 'h', "Show help message");
PrintUsage(schema, Console.Out);
}
private static void PrintUsage(OptionSchema schema, TextWriter output)
{
var usage = new[]
{
"usage: command [Options...] [--] Arguments...",
"",
"Options are:",
};
var messages = usage.Concat(schema.GetHelpMessage());
foreach (var m in messages)
{
output.WriteLine(m);
}
}
The type of the value the method returns is IEnumerable<string>
.
The output to the console is as follows:
usage: command [Options...] [--] Arguments...
Options are:
-f, --file FILE Specify an input file
-h, --help Show help message
If the description has to be composed of two or more lines,
you can split it into them by inserting a line feed character ('\n'
)
as a line separator. See the following example:
var schema = Options.NewSchema()
.Add(
"file",
'f',
"FILE",
"Specify an input file\n"
+ "The FILE can be - for standard input")
.Add("help", 'h', "Show help message");
⋮
As in the previous example, the output to the console is as follows:
⋮
-f, --file FILE Specify an input file
The FILE can be - for standard input
-h, --help Show help message
Note that the GetHelpMessage()
method sorts options by name.
The Parse(string[])
method, as mentioned earlier,
throws an OptionParsingException
when the specified argument to the schema is invalid.
Specific cases are as follows:
Unknown option
The specified option was not found in the schema.
Missing an argument
The specified option requires an argument, but no argument was given.
Unable to get an argument
The specified option takes no argument, but the argument was given.
Ambiguous option
The name of the specified option is abbreviated, but the abbreviations were not unique.
In most cases, all you need in the catch clause is to write the message of the exception to the standard error output, print the usage, and then exit with a non-zero status code. The following code shows a typical example:
try
{
var setting = schema.Parse(args);
⋮
}
catch (OptionParsingException e)
{
var output = Console.Error;
output.WriteLine(e.Message);
PrintUsage(schema, output);
Environment.Exit(1);
}
When specifying a callback function for a required argument option,
the value of the option argument often has to be validated in the function.
In that case, you can throw an OptionParsingException
when the validation fails so that the catch clause noted above handles
the exception as well as the other OptionParsingException
s.
For example, suppose the --count
option,
which requires the option argument representing a positive integer.
You can parse the command-line options to get the value of the
option argument, as follows:
private static int Count { get; set; } = DefaultCount;
private static void ParseCount(RequiredArgumentOption o)
{
var v = o.ArgumentValue;
if (!int.TryParse(v, out var num) || num < 0)
{
var n = o.ArgumentName;
throw new OptionParsingException(
o, $"option '{o}': the value '{v}' is invalid for {n}");
}
Count = num;
}
public static void Main(string[] args)
{
var schema = Options.NewSchema()
.Add(
"count",
'c',
"NUM",
"Specifies the count",
ParseCount)
⋮
try
{
var setting = schema.Parse(args);
⋮
}
catch (OptionParsingException e)
{
var output = Console.Error;
output.WriteLine(e.Message);
PrintUsage(schema, output);
Environment.Exit(1);
}
The Count
property returns 10
after parsing args
containing --count=10
.
But if replacing it with --count=abc
,
you have the output to the console as follows:
option '--count=abc': the value 'abc' is invalid for NUM
- Maroontress.Cui namespace
- Visual Studio 2019 Version 16.5 or .NET Core 3.1 SDK (SDK 3.1.300)
git clone URL
cd Cui
dotnet restore
dotnet build
dotnet test -p:CollectCoverage=true -p:CoverletOutputFormat=opencover \
--no-build CuiMallet.Test
dotnet ANYWHERE/reportgenerator.dll \
--reports:CuiMallet.Test/coverage.opencover.xml \
--targetdir:Coverlet-html
[1] POSIX, Utility Conventions
[2] The GNU C Library, Program Argument Syntax Conventions
[3] The GNU C Library, Parsing Long Options with getopt_long
[4] POSIX, getopt
, optarg
, opterr
, optind
, optopt
— command option parsing