CLAP: Command Line Argument Parser

This project is a simple implementation of an argument parser for command line applications.

It takes arguments in format:

[switches] [--] [paths]

Where:

  • switches are arguments starting with either - or --. When starting with -, every following character in that argument is assumed to represent one command switch (combining of one-char switches is allowed). When prefixed with -- the rest of the argument is assumed to be one switch.
  • -- by itself is an optional separator between switch arguments and paths.
  • paths are arguments which are not switches or separator. They can represent any value, not necessarily a path (although we refer to them as "paths").

Switches have the following data associated on creation:

  • Primary name: a string which identifies a switch, will be matched to supplied argument on command line.
  • Short name: when primary name is longer than 1 character, additional short name can be supplied on creation, of only 1 character, which is a synonym for the longer primary name. When primary name is already 1 character long this shorthand is not allowed.
  • Alternative names: additional names can be associated.
  • Help: help text that will be displayed when requested.
  • Argument: Switches can take arguments, of the following arities:
    • None: The default, no argument.
    • One: A single argument is mandatory. The argument is the next argument passed on command line, which must not be a switch.
    • NoneOrOne: argument is part of the switch text, denoted with =value, not separated by spaces.
    • Any: any number of arguments, which are separate arguments on command line, counted until the next switch or separator is found.
  • Importance flag: allows for displaying help items selectively.

Switches passed on command line can be repeated, with correct results in parsing with respect to arguments, long/short/alt names etc. This includes throwing exceptions in illegal situations.

After the first separator or path is encountered, all other arguments are presumed to be paths. This allows us to have arguments which are themselves starting with -, -- or -- itself.

Parsing returns a data structure containing:

  • A nullable list of paths.
  • A nullable list of switch data items, where each item consists of:
    • Data about the switch itself, set on creation.
    • A nullable list of arguments passed in.

The library also contains a text formatter class, which takes an input string and breaks it up so into lines of requested width, with option to set left margin and to justify.

Example:

var swA = new CommandSwitch(primaryName: "AAA", shortName: 'a');
swA.AddParameter(arity: Arity.One, name: "a-param");
swA.SetHelp("Help for A: a very long help line which will demonstrate our line breaking system.");

var swB = new CommandSwitch(primaryName: "be");
swB.AddParameter(arity: Arity.NoneOrOne, name: "b-param");
swB.SetHelp("Help for B: a very long help line which will demonstrate our line breaking system.");

var parser = new Parser();
parser.AddSwitch(swA);
parser.AddSwitch(swB);

var result = parser.Parse(new string[] {"-a", "111", "--be=222", "path1"});

Assert.AreEqual(1, result.pathsCount);
Assert.AreEqual("path1", result.paths[0]);
Assert.AreEqual(2, result.switchesCount);
Assert.AreEqual("AAA", result.switches[0].primaryName);
Assert.AreEqual("111", result.switches[0].args[0]);
Assert.AreEqual("be", result.switches[1].primaryName);
Assert.AreEqual("222", result.switches[1].args[0]);

To generate help, use:

StringBuilder sb = new StringBuilder();
parser.GetHelp(setup: new Setup(), builder: sb);
Console.WriteLine(sb.ToString());

which gives:

 -a --AAA <a-param>       Help for A: a very long help line which will
                          demonstrate our line breaking system.

    --be[=b-param]        Help for B: a very long help line which will
                          demonstrate our line breaking system.

Using the TextFormatter like this:

var text = "Lorem ipsum dolor sit amet, consecteturoriumuselratietoneritumusaden adipiscing elit. Aenean nec convallis magna, in pretium tellus.\n\nPellentesque mattis arcu sed neque pretium tincidunt. Nam magna neque, convallis quis dapibus sit amet, pulvinar sit amet arcu.";
var actual = TextFormatter.Format(input: text, lineWidth: 40,   
			 leftMargin: 2, firstLineLeftMargin: 4, justify: false);

Gives:

    Lorem ipsum dolor sit amet, 
  consecteturoriumuselratietoneritumusad
  en adipiscing elit. Aenean nec 
  convallis magna, in pretium tellus.
  
  Pellentesque mattis arcu sed neque 
  pretium tincidunt. Nam magna neque, 
  convallis quis dapibus sit amet, 
  pulvinar sit amet arcu.

and with justify:

var actual = TextFormatter.Format(input: text, lineWidth: 40,  
             leftMargin: 2, firstLineLeftMargin: 4, justify: true);

Gives:

    Lorem    ipsum    dolor   sit  amet,
  consecteturoriumuselratietoneritumusad
  en    adipiscing    elit.  Aenean  nec
  convallis magna, in pretium tellus.
  
  Pellentesque  mattis  arcu  sed  neque
  pretium  tincidunt.  Nam  magna neque,
  convallis    quis  dapibus  sit  amet,
  pulvinar sit amet arcu.

TextFormatter also has options for formatting headers:

var actual = TextFormatter.FormatHeader("HEADER", 40, TextFormatter.OutlineType.Line, leftMargin: 10);
          ---------------- HEADER ----------------
var actual = TextFormatter.FormatHeader("HEADER", 40, TextFormatter.OutlineType.Equals, leftMargin: 10);
          ================ HEADER ================
var actual = TextFormatter.FormatHeader("HEADER", 12, TextFormatter.OutlineType.Underline, leftMargin: 5);
        HEADER   
        ------