Skip to content
Giacomo Stelluti Scala edited this page Jul 3, 2015 · 86 revisions

As you can see Version 2.0 is publicly available as alpha. This is not only a matter of stability but it means that API could be subject to change until it quits pre-release status.

From a git repository perspective 2.0 alpha lives in master branch (opposites to latest stable that is stored in stable-1.9.71.2 branch; others previous versions are tagged).

Depending on development state, public branches may exist, but are normally of author and main contributors interest only.

As first I suggest you to refer to unit tests of Parser class.

Get it

I suggest you to clone the repository, since I'll not push the alpha on NuGet so often. Anyway each time it happens, I'll warn Twitter followers.

Install-Package CommandLineParser -Pre

If want to try immediately after my Twitter post and you use Visual Studio, maybe that the last package is still not listed. So take note of the version and install it via the Package Manager Console. For example for 2.0.9-alpha:

PM> Install-Package CommandLineParser -Version 2.0.4-alpha

Overview

The library doesn't depend on any other assembly. The reference to FSharp.Core.dll is needed only at compile time and required runtime only from F# projects (that contain such dependency by default).

Parser is activated from Parser class defined in CommandLine namespace. I suggest you to use the pre-configured default singleton and build the instance on your own only when really required.

using CommandLine;

// (1) default singleton
var result = Parser.Default.ParseArguments<Options>(args);

// (2) build and configure instance
var parser = new Parser(with => with.EnableDashDash = true);
var result = parser.ParseArguments<Options(args)

In the latter case the Parser(Action<ParserSettings>) constructor was called to configure the parser via the ParserSettings instance.

The CommandLine.Text namespace contains everything you need to create an user friendly help screen. This is done in a completely automatic manner, if ParserSettings.HelpWriter is set.

For example, the default instance Parser.Default initializes with ParserSettings.HelpWriter to Console.Error.

Anyway you're not forced to use these features and you can take advantage and customize these behaviours to the degree you like.

Options

Version 2.0 uses only two attributes to describe option syntax, Option and Value·

Option works much like in previous version but it can be applied to scalar or sequence value (IEnumerable<T>).

When applied to sequences you can define also Min and Max properties to define a range of values.

Value resembles previous ValueOption and ValueList. Like new Option attribute can be applied to sequences and now supports Required property too.

[Value] attribute

Values are partitioned using index. Let's see an example:

class Options {
  [Value(0)]
  public int IntValue { get; set; }

  [Value(1, Min=1, Max=3)]
  public IEnumerable<string> StringSeq { get; set; }

  [Value(2)]
  public DoubleValue { get; set; }
}

As long as you supply values they will be set to corresponding properties:

$ app 10 str1 str2 str3 1.1

If you omit Min and Max constraint, all available values will be captured by the sequence. So there's no sense to define Value attribute with higher index after a sequence value that lack a range constraint:

class Options {
  [Value(0)]
  public int IntValue { get; set; }

  [Value(1)]
  public IEnumerable<string> StringSeq { get; set; }

  // all values captured by previous specifications,
  // this property will never receive a value
  [Value(2)]
  public DoubleValue { get; set; }
}

[Option] attribute

Omitting option name is perfectly legal. In this case the long name will be inferred from identifier name.

class Options {
  [Option]
  public bool Version { get; set; }
}

This allows:

$ app --version

Option attribute supports also a Separator property to mimic previous OptionList behavior when applied to sequences.

class Options {
  [Option('t', Separator=':')]
  public IEnumerable<string> Types { get; set; }
}

This allows:

$ app -t int:long:string

Capturing sequences

As said you can apply both attributes to IEnumerable<T> (where T is a CLR built-in datatype, like string, int, etc).

You can also specify also a Min constraint or a Max constraint alone: this means you only want check for minimum or maximum number of elements. Breaking a constraint will cause parsing to fail.

Also if you can overlay Min=1 with Required=true, there's no sense in doing so, since the parser will check Required before the Min constraint. In this case you should favor Min=1. Also Min=1, Max=1 should be avoided (even if perfectly legal) in favor of Required=true.

Parsing

Parsing is easy as writing one line of code:

var result = Parser.Default.ParseArguments<Options>(args);

This is a basic scenario (without verbs); here we're using the default pre-built instance to parse input arguments using rules engraved in Options type (as described above).

The result variable (here defined used implicit typing) is of type ParserResult<T> or better ParserResult<Options> in this specific case.

ParserResult<T> is a type that exposes an instance of T through its Value property. When its Errors sequence (IEnumerable<Error>) is empty, than Value is properly loaded with parsed values.

Even if it's possible examine the errors sequence, it's recommended quit with appropriate error code. The library help subsystem will print out usage and error descriptions automatically. The parser default instance is configured to output this text to Console.Error.

Verbs

To take advantage of verb commands simply an option class for each verb decorated with [Verb] attribute:

[Verb("add", HelpText = "Add file contents to the index.")]
class AddOptions { //normal options here
}
[Verb("commit", HelpText = "Record changes to the repository.")]
class CommitOptions { //normal options here
}
[Verb("clone", HelpText = "Clone a repository into a new directory.")]
class CloneOptions { //normal options here
}

A this point you have to use a proper ParserArguments<T1, T2...> overload that accepts more than one type (without using the overload with variadic arguments, the library define versions with up to 16 type parameters):

var result = Parser.Default.ParseArguments<AddOptions, CommitOptions, CloneOptions>(args);

In this case the T Value property of ParserResult<T> will be object but will contain the proper instance if parsing succeeds or NullInstance if fails.

The only change with normal parsing is the requirement to query the ParserResult<object>.Value property and invoke the application logic written to handle a specific verb.

In this scenario the parser supplies you an additional help verb that allows:

$ app help clone

In this way application users can display specific verb command help screen.

$ app help

Or will display an help screen for available verbs.

Immutable Target

If you develop according to functional programming, you probably won't define a mutable type like the ones presented in samples.

You're free to define an immutable type:

class Options {
  private readonly IEnumerable<string> Files;
  private readonly bool Verbose;
  private readonly long Offset;

  public Options(IEnumerable<string> files, bool verbose, long offset) {
    this.Files = files;
    this.Verbose = verbose;
    this.Offset = offset;
  }

  [Option]
  public IEnumerable<string> Files { get { return files; } }

  [Option]
  public bool Verbose { get { return verbose; } }

  [Option]
  public long Offset { get { return offset; } ]
}

The parser will rank this class as immutable by the absence of public property setters and fields.

This is the same feature that allow you to parse against an F# record:

type options = {
  [<Option>] files : seq<string>;
  [<Option>] verbose : bool;
  [<Option>] offset : option int64;
}

As you can see the options.offset record member was defined as option<int64> since the library has full support for option<'a> (full CLR name type name Microsoft.FSharp.Core.FSharpOption<T>).

Need help?

This section is the a reduced version of full wiki, still dedicated to latest stable. Sometimes old informations can be adapted to 2.0.x, but if not feel free to ask opening an issue.

Clone this wiki locally