Mono.Options.OptionSet Class
A class to parse program options.

See Also: OptionSet Members

Syntax

public class OptionSet : System.Collections.ObjectModel.KeyedCollection<string, Option>

See Also

ResponseFileSource

Remarks

A common requirement for command-line programs is option parsing. The command line consists of a sequence of arguments. OptionSet.Parse(IEnumerable<string>) parses this argument sequence, invoking actions of registered Mono.Options.Options when a sequence of arguments matching the Mono.Options.Options requirements are encountered. Mono.Options.Option's are registered with OptionSet.Add(Option) and its overloads.

OptionSet.Parse(IEnumerable<string>), returns a List<string> of all arguments which were not matched by a registered Mono.Options.Option.

Response files, as used by mcs and other compiler tools, may be supported by using Mono.Options.ResponseFileSource.

"Headers" -- string constants which allow grouping of options in the output of OptionSet.WriteOptionDescriptions(System.IO.TextWriter) -- may be provided "inline" with the actual options. The greet example below shows such usage.

Three conditions must be met for a sequence of arguments to be considered as a match for an Mono.Options.Option:

There are three types of Mono.Options.Options that can be parsed:

  1. Boolean options, which correspond to a Option.OptionValueType value of OptionValueType.None. When a matching argument is encountered, their registered action is invoked immediately. The Action`1 value will be non-default(T) if the value is true, e.g. if -option or -option+ is specified, and the value will be default(T) if the value is false, e.g. if -option- is specified.
  2. Optional value options, which correspond to a Option.OptionValueType value of OptionValueType.Optional. Optional value options are not "greedy"; they will only get their value only from the current argument. If the value is not present, default(T) is provided to their corresponding Action`1.

    Thus, -opt:value would pass value to the action registered for opt, while -opt value would pass default(T) to the action registered for opt and value would be an unparsed argument.

  3. Required value options, which correspond to a Option.OptionValueType value of OptionValueType.Required. Required value options are "greedy" -- if the value is not found within the current argument, the following argument(s) will be used to supply the value(s). Once enough values have been parsed, their corresponding Action`1 is invoked.

    Thus, both -opt:value and -opt value would pass value to the action registered for opt.

    If no value can be found, an Mono.Options.OptionException is thrown from OptionSet.Parse(IEnumerable<string>).

Operation

Use of OptionSet is split up into two parts:

  1. Initialization.
  2. Parsing.

During the initialization phase, new Mono.Options.Option instances are created and associated with an action to perform when the Option requirements are met (e.g. when a required value has been encountered). This phase is not thread safe. All options added during this phase are considered to have been registered.

C# Example

OptionSet p = new OptionSet () {
  { "option-a", v => { /* action to perform */ } },
};

There are three ways to add Mono.Options.Options to the Mono.Options.OptionSet:

  1. With C# collection initializers, as used above.
  2. Explicitly by calling OptionSet.Add(string, Action<string>) and the other Add overloads.
  3. By creating a new subclass of Mono.Options.Option and adding it via OptionSet.Add(Option). This is not recommended, but is available if you require more direct option handling than the default Mono.Options.Option implementation provides.

During the parsing phase, an IEnumerable<string> is enumerated, looking for arguments which match a registered option, and invoking the corresponding action when an option and associated (optional) value is encountered. During this phase, the Mono.Options.OptionSet instance itself is thread safe, but full thread safety depends upon thread-safety of the registered actions. Any option-like strings for names that haven't been registered, e.g. --this-was-never-registered=false, and all arguments that are not used as option values are returned from OptionSet.Parse(IEnumerable<string>) or processed by the default handler <>, if registered.

C# Example

List<string> extra = p.Parse (new string[]{"-option-a"});
Note to Inheritors

Subclasses can override the following virtual methods to customize option parsing behavior:

Thread Safety

All public static members of this type are safe for multithreaded operations. The OptionSet.Parse(IEnumerable<string>) instance method is thread-safe if no concurrent modifying methods are invoked (OptionSet.Add(Option), System.Collections.ObjectModel.Collection<Option>.Remove(``0), etc.).

Example

The following greet example demonstrates some simple usage of Mono.Options.OptionSet.

C# Example

using System;
using System.Collections.Generic;
using Mono.Options;

class Test {
	static int verbosity;

	public static void Main (string[] args)
	{
		bool show_help = false;
		List<string> names = new List<string> ();
		int repeat = 1;

		var p = new OptionSet () {
			"Usage: greet [OPTIONS]+ message",
			"Greet a list of individuals with an optional message.",
			"If no message is specified, a generic greeting is used.",
			"",
			"Options:",
			{ "n|name=", "the {NAME} of someone to greet.",
			  v => names.Add (v) },
			{ "r|repeat=", 
				"the number of {TIMES} to repeat the greeting.\n" + 
					"this must be an integer.",
			  (int v) => repeat = v },
			{ "v", "increase debug message verbosity",
			  v => { if (v != null) ++verbosity; } },
			{ "h|help",  "show this message and exit", 
			  v => show_help = v != null },
		};

		List<string> extra;
		try {
			extra = p.Parse (args);
		}
		catch (OptionException e) {
			Console.Write ("greet: ");
			Console.WriteLine (e.Message);
			Console.WriteLine ("Try `greet --help' for more information.");
			return;
		}

		if (show_help) {
			p.WriteOptionDescriptions (Console.Out);
			return;
		}

		string message;
		if (extra.Count > 0) {
			message = string.Join (" ", extra.ToArray ());
			Debug ("Using new message: {0}", message);
		}
		else {
			message = "Hello {0}!";
			Debug ("Using default message: {0}", message);
		}

		foreach (string name in names) {
			for (int i = 0; i < repeat; ++i)
				Console.WriteLine (message, name);
		}
	}

	static void Debug (string format, params object[] args)
	{
		if (verbosity > 0) {
			Console.Write ("# ");
			Console.WriteLine (format, args);
		}
	}
}

The output (under the influence of different command-line arguments) is:

sh Example

$ mono greet.exe --help
Usage: greet [OPTIONS]+ message
Greet a list of individuals with an optional message.
If no message is specified, a generic greeting is used.

Options:
  -n, --name=NAME            the NAME of someone to greet.
  -r, --repeat=TIMES         the number of TIMES to repeat the greeting.
                               this must be an integer.
  -v                         increase debug message verbosity
  -h, --help                 show this message and exit

$ mono greet.exe -v- -n A -name=B --name=C /name D -nE
Hello A!
Hello B!
Hello C!
Hello D!
Hello E!

$ mono greet.exe -v -n E custom greeting for: {0}
# Using new message: custom greeting for: {0}
custom greeting for: E

$ mono greet.exe -r 3 -n A
Hello A!
Hello A!
Hello A!

$ mono greet.exe -r not-an-int
greet: Could not convert string `not-an-int' to type Int32 for option `-r'.
Try `greet --help' for more information.

Notice how the output produced by --help uses the descriptions provided during OptionSet initialization. Notice that the Mono.Options.Option requiring a value (n|name=) can use multiple different forms of invocation, including: -n value, -n=value, -name value, -name=value, --name value, --name=value, /name value, and /name=value.

Notice also that the boolean v option can take three separate forms: -v and -v+, which both enable the option, and -v-, which disables the option. (The second greet invocation uses -v-, which is why no debug messages are shown.)

Finally, note that the action can specify a type to use. If no type is provided, the action parameter will be a string. If a type is provided, then System.ComponentModel.TypeConverter will be used to convert a string to the specified type.

Example

The following example shows how options and values can be bundled together.

C# Example

using System;
using System.Linq;
using System.Collections.Generic;
using Mono.Options;

class Test {
	public static void Main (string[] args)
	{
		var show_help = false;
		var macros = new Dictionary<string, string>();
		bool create = false, extract = false, list = false;
		string output = null, input = null;
		string color  = null;

		var p = new OptionSet () {
			"Usage: bundling [OPTIONS]+",
			"Demo program to show the effects of bundling options and their values",
			"",
			"gcc-like options:",
			{ "D:", "Predefine a macro with an (optional) value.",
				(m, v) => {
					if (m == null)
						throw new OptionException ("Missing macro name for option -D.", 
								"-D");
					macros.Add (m, v);
				} },
			{ "d={-->}{=>}", "Alternate macro syntax.", 
				(m, v) => macros.Add (m, v) },
			{ "o=", "Specify the output file", v => output = v },

			"",
			"tar-like options:",
			{ "f=", "The input file",   v => input = v },
			{ "x",  "Extract the file", v => extract = v != null },
			{ "c",  "Create the file",  v => create = v != null },
			{ "t",  "List the file",    v => list = v != null },

			"",
			"ls-like optional values:",
			{ "color:", "control whether and when color is used", 
				v => color = v },

			"",
			"other:",
			{ "h|help",  "show this message and exit", 
			  v => show_help = v != null },
			// default
			{ "<>",
				v => Console.WriteLine ("def handler: color={0}; arg={1}", color, v)},
		};

		try {
			p.Parse (args);
		}
		catch (OptionException e) {
			Console.Write ("bundling: ");
			Console.WriteLine (e.Message);
			Console.WriteLine ("Try `greet --help' for more information.");
			return;
		}

		if (show_help) {
			p.WriteOptionDescriptions (Console.Out);
			return;
		}

		Console.WriteLine ("Macros:");
		foreach (var m in (from k in macros.Keys orderby k select k)) {
			Console.WriteLine ("\t{0}={1}", m, macros [m] ?? "<null>");
		}
		Console.WriteLine ("Options:");
		Console.WriteLine ("\t Input File: {0}", input);
		Console.WriteLine ("\tOuptut File: {0}", output);
		Console.WriteLine ("\t     Create: {0}", create);
		Console.WriteLine ("\t    Extract: {0}", extract);
		Console.WriteLine ("\t       List: {0}", list);
		Console.WriteLine ("\t      Color: {0}", color ?? "<null>");
	}
}

The output (under the influence of different command-line arguments) is:

sh Example

$ mono bundling.exe --help
Usage: bundling [OPTIONS]+
Demo program to show the effects of bundling options and their values

gcc-like options:
  -D[=VALUE1:VALUE2]         Predefine a macro with an (optional) value.
  -d=VALUE1-->VALUE2         Alternate macro syntax.
  -o=VALUE                   Specify the output file

tar-like options:
  -f=VALUE                   The input file
  -x                         Extract the file
  -c                         Create the file
  -t                         List the file

ls-like optional values:
      --color[=VALUE]        control whether and when color is used

other:
  -h, --help                 show this message and exit

$ mono bundling.exe -D
bundling: Missing macro name for option -D.
Try `greet --help' for more information.

$ mono bundling.exe -DA -DB=C "-dD-->E" "-dF=>G" -d "H=>I" -cf input --color -ooutput
Macros:
	A=<null>
	B=C
	D=E
	F=G
	H=I
Options:
	 Input File: input
	Ouptut File: output
	     Create: True
	    Extract: False
	       List: False
	      Color: <null>

$ mono bundling.exe -cfv input
def handler: color=; arg=input
Macros:
Options:
	 Input File: v
	Ouptut File: 
	     Create: True
	    Extract: False
	       List: False
	      Color: <null>

$ mono bundling.exe -xctf input
Macros:
Options:
	 Input File: input
	Ouptut File: 
	     Create: True
	    Extract: True
	       List: True
	      Color: <null>

$ mono bundling.exe --color=auto -o output -finput
Macros:
Options:
	 Input File: input
	Ouptut File: output
	     Create: False
	    Extract: False
	       List: False
	      Color: auto

$ mono bundling.exe --color=on A B --color=off C D
def handler: color=on; arg=A
def handler: color=on; arg=B
def handler: color=off; arg=C
def handler: color=off; arg=D
Macros:
Options:
	 Input File: 
	Ouptut File: 
	     Create: False
	    Extract: False
	       List: False
	      Color: off

Example

The following example shows a custom OptionSet subclass with the following additional functionality:

  1. Option name lookup is done in a case insensitive fashion, so --name and --NAME are identical.
  2. Option-like strings cannot be used as values to options, so --name --repeat is an error.
  3. A key/value parser is provided, to show the use of custom Mono.Options.OptionContext and Mono.Options.Option subclasses.

C# Example

// Case-Insensitive and Concatenating OptionSet
using System;
using System.Collections.Generic;
using Mono.Options;

class DemoOptionSet : OptionSet {
	protected override void InsertItem (int index, Option item)
	{
		if (item.Prototype.ToLower () != item.Prototype)
			throw new ArgumentException ("prototypes must be lower-case!");
		base.InsertItem (index, item);
	}

	protected override OptionContext CreateOptionContext ()
	{
		return new OptionContext (this);
	}

	protected override bool Parse (string option, OptionContext c)
	{
		string f, n, s, v;
		bool haveParts = GetOptionParts (option, out f, out n, out s, out v);
		Option nextOption = null;
		string newOption  = option;

		if (haveParts) {
			string nl = n.ToLower ();
			nextOption = Contains (nl) ? this [nl] : null;
			newOption = f + n.ToLower () + (v != null ? s + v : "");
		}

		if (c.Option != null) {
			// Prevent --a --b
			if (c.Option != null && haveParts) {
				if (nextOption == null) {
					// ignore
				}
				else 
					throw new OptionException (
						string.Format ("Found option `{0}' as value for option `{1}'.",
							option, c.OptionName), c.OptionName);
			}

			// have a option w/ required value; try to concat values.
			if (AppendValue (option, c)) {
				if (!option.EndsWith ("\\") && 
						c.Option.MaxValueCount == c.OptionValues.Count) {
					c.Option.Invoke (c);
				}
				return true;
			}
			else
				base.Parse (newOption, c);
		}

		if (!haveParts || v == null) {
			// Not an option; let base handle as a non-option argument.
			return base.Parse (newOption, c);
		}

		if (nextOption.OptionValueType != OptionValueType.None && 
				v.EndsWith ("\\")) {
			c.Option = nextOption;
			c.OptionValues.Add (v);
			c.OptionName = f + n;
			return true;
		}

		return base.Parse (newOption, c);
	}

	private bool AppendValue (string value, OptionContext c)
	{
		bool added = false;
		string[] seps = c.Option.GetValueSeparators ();
		foreach (var o in seps.Length != 0
				? value.Split (seps, StringSplitOptions.None)
				: new string[]{value}) {
			int idx = c.OptionValues.Count-1;
			if (idx == -1 || !c.OptionValues [idx].EndsWith ("\\")) {
				c.OptionValues.Add (o);
				added = true;
			}
			else {
				c.OptionValues [idx] += value;
				added = true;
			}
		}
		return added;
	}
}

class Demo {
	public static void Main (string[] args)
	{
		List<string> names = new List<string> ();
		Dictionary<string,string> map = new Dictionary<string,string> ();
		int repeat = 1;

		OptionSet p = new DemoOptionSet () {
			{ "n|name=",    v => names.Add (v) },
			{ "r|repeat:",  (int v) => repeat = v },
			{ "m|map=",     (k,v) => map.Add (k, v) },
		};

		List<string> extra;
		try {
			extra = p.Parse (args);
		}
		catch (OptionException e) {
			Console.Write ("subclass: ");
			Console.WriteLine (e.Message);
			return;
		}

		string message;
		if (extra.Count > 0) {
			message = string.Join (" ", extra.ToArray ());
		}
		else {
			message = "Hello {0}!";
		}

		foreach (string name in names) {
			for (int i = 0; i < repeat; ++i)
				Console.WriteLine (message, name);
		}
		List<string> keys = new List<string>(map.Keys);
		keys.Sort ();
		foreach (string key in keys) {
			Console.WriteLine ("Key: {0}={1}", key, map [key]);
		}
	}
}

The output (under the influence of different command-line arguments) is:

sh Example

$ mono subclass.exe -n A -Name=B --NAME=C /nAMe D
Hello A!
Hello B!
Hello C!
Hello D!

$ mono subclass.exe --Repeat -name A

$ mono subclass.exe -Name --Repeat 3
subclass: Found option `--Repeat' as value for option `-name'.

$ mono subclass.exe --Map a b -mAp c=d /maP=e=f
Key: a=b
Key: c=d
Key: e=f

$ mono subclass.exe --map 'a\' 'b\' c 'd\' 'e\' f
Key: a\b\c=d\e\f

Notice:

Requirements

Namespace: Mono.Options
Assembly: Mono.Options (in Mono.Options.dll)
Assembly Versions: 0.2.0.0, 0.2.1.0, 0.2.2.0, 0.2.3.0