CommandLine.Arguments - Simple Parsing of Command Line Arguments

6 minutes read

I needed a solution for parsing command line arguments and didn’t like the existing options, so I wrote my own. My primary goal was to create a lightweight, unobtrusive library that would cover all (or most) bases without requiring the creation of a dedicated class to encapsulate the arguments (however, you can if you like) and without requiring a bunch of lines of code to handle parsing in my startup code.

What I came up with clocks in at a little over 130 lines of code and fits in a single .cs file, perfect to include in your project as source or, if you prefer, as a NuGet package. It’s also got 100% code coverage and is verified to build on Windows and Mono.

The library can be found on GitHub here and the binaries can be downloaded from NuGet here.

Installation

Install from the NuGet gallery GUI or with the Package Manager Console using the following command:

Install-Package Utility.CommandLine.Arguments

The code is also designed to be incorporated into your project as a single source file (Arguments.cs).

Quick Start

Create private static properties in the class containing your Main() and mark them with the Argument attribute, assigning short and long names. Invoke the Arguments.Populate() method within Main(), then implement the rest of your logic.

The library will populate your properties with the values specified in the command line arguments.

internal class Program
{
    [Argument('b', "boolean")]
    private static bool Bool { get; set; }

    [Argument('f', "float")]
    private static double Double { get; set; }

    [Argument('i', "integer")]
    private static int Int { get; set; }

    [Argument('s', "string")]
    private static string[] String { get; set; }

    [Operands]
    private static string[] Operands { get; set; }

    private static void Main(string[] args)
    {
        Arguments.Populate();

        Console.WriteLine("Bool: " + Bool);
        Console.WriteLine("Int: " + Int);
        Console.WriteLine("Double: " + Double);

        foreach (string s in String)
        {
            Console.WriteLine("String: " + s);
        }

        foreach (string operand in Operands) 
        {
            Console.WriteLine("\r\n Operand:" + operand);
        }
    }
}

Grammar

The grammar supported by this library is designed to follow the guidelines set forth in the publication The Open Group Base Specifications Issue 7, specifically the content of Chapter 12, Utility Conventions, located here.

Each argument is treated as a key-value pair, regardless of whether a value is present. The general format is as follows:

<-|--|/>argument-name<=|:| >["|']value['|"] [--] [operand] ... [operand]

The key-value pair may begin with a single dash, a pair of dashes (double dash), or a forward slash. Single and double dashes indicate the use of short or long names, respectively, which are covered below. The forward slash may represent either, but does not allow for the grouping of parameterless arguments.

The argument name may be a single character when using short names, or any alphanumeric sequence not including spaces if using long names.

The value delimiter may be an equals sign, a colon, or a space.

Values may be any alphanumeric sequence, however if a value contains a space it must be enclosed in either single or double quotes.

Any word, or phrase enclosed in single or double quotes, will be parsed as an operand. The official specification requires operands to appear last, however this library will parse them in any position.

A double-hyphen --, not enclosed in single or double quotes, and appearing with whitespace on either side, designates the end of the argument list and beginning of the operand list. Anything appearing after this delimiter is treated as an operand, even if it begins with a hyphen, double-hyphen or forward slash.

Short Names

Short names consist of a single character, and arguments without parameters may be grouped. A grouping may be terminated with a single argument containing a parameter. Arguments using short names must be preceded by a single dash.

Examples

Single argument with a parameter: -a foo

KeyValue
afoo

Two parameterless arguments: -ab

KeyValue
a 
b 

Three arguments; two parameterless followed by a third argument with a parameter: -abc bar

KeyValue
a 
b 
cbar

Long Names

Long names can consist of any alphanumeric string not containing a space. Arguments using long names must be preceded by two dashes.

Examples

Single argument with a parameter: --foo bar

KeyValue
foobar

Two parameterless arguments: --foo --bar

KeyValue
foo 
bar 

Two arguments with parameters: --foo bar --hello world

KeyValue
foobar
helloworld

Mixed Naming

Any combination of short and long names are supported.

Example

-abc foo --hello world /new="slashes are ok too"

KeyValue
a 
b 
cfoo
helloworld
newslashes are ok too

Multiple Values

Arguments can accept multiple values, and when parsed a List<object> is returned if more than one value is specified. When using the Populate() method, the underlying property for an argument accepting multiple values must be an array or List, otherwise an InvalidCastException is thrown.

Example

--list 1 --list 2 --list 3

KeyValue
list1,2,3

Operands

Any text in the string that doesn’t match the argument-value format is considered an operand. Any text which appears after a double-hyphen --, not enclosed in single or double quotes, and with spaces on either side, is treated as an operand regardless of whether it matches the argument-value format.

Example

-a foo bar "hello world" -b -- -explicit operand

KeyValue
afoo
b 

Operands

  1. bar
  2. “hello world”
  3. -explicit
  4. operand

Parsing

Argument key-value pairs can be parsed from any string using the Parse(string) method. This method returns a Dictionary<string, object> containing all argument-value pairs.

If the string parameter is omitted, the value of Environment.CommandLine is used.

Note that passing the args variable, or the result of String.Join() on args, will prevent the library from property handling quoted strings. There are generally very few instance in which Environment.CommandLine should not be used.

Example

Dictionary<string, object> args = Arguments.Parse("-ab --foo bar");

The example above would result in a dictionary args containing:

KeyValue
a 
b 
foobar

Note that boolean values should be checked with Dictionary.ContainsKey("name"); the result will indicate whether the argument was encountered in the command line arguments. All other values are retrieved with Dictionary["key"].

Populating

The Populate() method uses reflection to populate private static properties in the target Type with the argument values matching properties marked with the Argument attribute.

The list of operands is placed into a single property marked with the Operands attribute. This property must be of type string[] or List<string>.

Creating Target Properties

Use the Argument attribute to designate properties to be populated. The constructor of Argument accepts a char and a string, representing the short and long names of the argument, respectively.

Note that the name of the property doesn’t matter; only the attribute values are used to match an argument key to a property.

The Type of the property does matter; the code attempts to convert argument values from string to the specified Type, and if the conversion fails an ArgumentException is thrown.

Properties can accept lists of parameters, as long as they are backed by an array or List<>. Specifying multiple parameters for an argument backed by an atomic Type (e.g. not an array or List) will result in an InvalidCastException.

The Operands property accepts no parameters. If the property type is not string[] or List<string>, an InvalidCastException will be thrown.

Examples

[Argument('f', "foo")]
private static string Foo { get; set; }

[Argument('n', "number")]
private static integer MyNumber { get; set; }

[Argument('b', "bool")]
private static bool TrueOrFalse { get; set; }

[Argument('l', "list")]
private static string[] List { get; set; }

[Operands]
private static List<string> Operands { get; set; }

Given the argument string -bf "bar" --number=5 --list 1 --list 2, the resulting property values would be as follows:

PropertyValue
Foobar
MyNumber5
TrueOrFalsetrue
List1,2

Updated:

Leave a Comment