How to enforce required command-line options with NDesk.Options?

BigJoe714 picture BigJoe714 · Aug 6, 2010 · Viewed 13.5k times · Source

I was just writing a console utility and decided to use NDesk.Options for command-line parsing. My question is, How do I enforce required command-line options?

I see in the docs that:

options with a required value (append '=' to the option name) or an optional value (append ':' to the option name).

However, when I put a = at the end of the option name there is no difference in behavior. Ideally the Parse method would throw an exception.

Is there something else I need to do?

Here is my test code:

class Program
{
    static void Main(string[] args)
    {
        bool show_help = false;
        string someoption = null;

        var p = new OptionSet() {
            { "someoption=", "Some String Option", v => someoption = v},
            { "h|help",  "show this message and exit", v => show_help = v != null }
        };

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

        if (show_help)
        {
            ShowHelp(p);
            return;
        }

        System.Console.WriteLine("==================");
        System.Console.WriteLine(someoption);
    }

    static void ShowHelp(OptionSet p)
    {
        System.Console.WriteLine("Usage: myconsole [OPTIONS]");
        System.Console.WriteLine();
        System.Console.WriteLine("Options:");
        p.WriteOptionDescriptions(System.Console.Out);
    }
}

Answer

jonp picture jonp · Sep 20, 2010

The problem is that documentation isn't as clear as it apparently needs to be. :-(

Specifically, as per:

http://www.ndesk.org/doc/ndesk-options/NDesk.Options/OptionValueType.html#F:NDesk.Options.OptionValueType.Required

The = within an option specification doesn't apply to the OptionSet as a whole, but just to the value for that specific option.

The importance of this is really only relevant in two scenarios, so first let's consider the OptionSet parser:

string a = null;
string b = null;
var options = new OptionSet {
    { "a=", v => a = v },
    { "b=", v => b = v },
};

Scenario 1 where it's important is that OptionSet.Parse() works in a single-pass, forward-only manner, and does not look at option values to determine if they "should be" values. Thus, consider:

options.Parse(new[]{"-a", "-b"});

The result of this will be that a has the value "-b", and b is null. Since the handler for -a requires a value, it always gets the following value (unless the value is "encoded" into the original option, e.g. -a=value).

The second place where this is important is when a value-requiring option is the last option, and there isn't a value present for it:

options.Parse(new[]{"-a"});

This will throw an OptionException, as the handler for -a requires a value, and no value is present.

Consequently, if you have an option that itself is required (as opposed to an option that requires a value), you need to manually check for this:

string dir = null;
new OptionSet {
    { "o=", v => dir = v },
}.Parse (args);

if (dir == null)
    throw new InvalidOperationException ("Missing required option -o=DIR");