VSoft Technologies Blogs

rss

VSoft Technologies Blogs - posts about our products and software development.

Command line parsing

Pretty much every delphi console application I have ever written or worked on had command line options, and every one of the projects tried different ways for defining and parsing the supplied options. Whilst working on DUnitX recently, I needed to add some command line options, and wanted to find a nice way to add them and make it easy to add more in the future. The result is VSoft.CommandLineParser (copies of which are included with the latest DUnitX).

Defining Options

One of the things I really wanted, was to have the parsing totally decoupled from definition and the storage of the options values. Options are defined by registering them with the TOptionsRegistry, via TOptionsRegistry.RegisterOption<T> - whilst it makes use of generics, only certain types can be used, the types are checked at runtime, as generic constraints are not flexible enough to specify which types we allow at compile time. Valid types are string, integer, boolean, enums & sets and floating point numbers.

Calling RegisterOption will return a definition object which implements IOptionDefinition. This definition object allows you to set various settings (such as Required). When registering the option, you specify the long option name, the short option name, help text (will be used when showing the usage) and a TProc<T> anonymous method that will take the parsed value as a parameter.

procedure ConfigureOptions;
var
  option : IOptionDefintion;
begin
  option := TOptionsRegistry.RegisterOption<string>('inputfile','i','The file to be processed',
    procedure(value : string)
    begin
        TSampleOptions.InputFile := value;
    end);
  option.Required := true;

  option := TOptionsRegistry.RegisterOption<string>('outputfile','o','The processed output file',
    procedure(value : string)
    begin
        TSampleOptions.OutputFile := value;
    end);
  option.Required := true;

  option := TOptionsRegistry.RegisterOption<boolean>('mangle','m','Mangle the file!',
    procedure(value : boolean)
    begin
        TSampleOptions.MangleFile := value;
    end);
  option.HasValue := False;

  option := TOptionsRegistry.RegisterOption<boolean>('options','','Options file',nil);
  option.IsOptionFile := true;
end;

For options that are boolean in nature, ie they have do not value part, the value passed to the anonymous method will be true if the option was specified, otherwise the anonymous method will not be called. The 'mangle' option in the above example shows this scenario.

You can also specify that an option is a File, by setting the IsOptionFile property on the option definition. This tells the parser the value will be a file, which contains other options to be parsed (in the same format as the command line). This is useful for working around windows command line length limitations.

Currently the parser will accept
-option:value
--option:value
/option:value

Note the : delimiter between the option and the value.

Unnamed parameters are registered via the TOptionsRegistry.RegisterUnNamedOption<T> method. Unlike named options, unnamed options are positional, but only when more than one is registered, as they will be passed to the anonymous methods in the order they are registered.

Parsing the options.

Parsing the options is as simple as calling TOptionsRegistry.Parse, which returns a ICommandLineParseResult object. Check the HasErrors property to see if the options were valid, the ErrorText property has the parser error messages.

Printing Usage

If the parser reports errors, then typically you would show the user what the valid options are and exit the application, e.g:

    parseresult := TOptionsRegistry.Parse;
    if parseresult.HasErrors then
    begin
      Writeln(parseresult.ErrorText);
      Writeln('Usage :');
      TOptionsRegistry.PrintUsage(
        procedure(value : string)
        begin
          Writeln(value);
        end);
    end
    else
        ..start normal execution here

The TOptionsRegistry.PrintUsage makes it easy to print the usage to the command line.

When I started working on this library, I found some really complex libraries (mostly .net) out there with a lot of options, but I decided to keep mine as simple as possible and only cover off the scenarios I need right now. So it's entirely possible this doesn't do everything people might need, but it's pretty easy to extend. The VSoft.CommandLineParser library (just three units) is open source and available on Github, with a sample application and unit tests (DUnitX) included.

Showing 5 Comments

Avatar
Jon Robertson 5 years ago

I just wanted to say thanks Vincent. I have my own set of command line functions that I've used for years. They're a little more useful than Delphi's FindCmdLineSwitch. But I really like your parser. Today I used it in two new utilities I just threw together. With your parser, it is quick and easy to add/remove/change the command line options.


Avatar
Vincent Parrett 5 years ago

Svip - not cluttering is not supported. Why, because I didn't need them. I'm sure it could be added (using regex) without too much effort.

As for : vs = - personal preference. Again, would be easy to add an option to specify the separator. Happy to receive pull reqests.


Avatar
Svip 5 years ago

Does it support cluttering of single letter boolean options?

For instance, if you have two booleans options called (say) 'm' and 'f', can I then write '-mf' in the command line?

Also why ":", why not "=" for "option=value"? That seems like an odd choice. I hope "-" and "--" are restricted to single letter and multi-letter options, respectively.


Avatar
Vincent Parrett 5 years ago

@Joseph - I did look at docopt, but it seemed more complex than it needed to be. I just looked at the amount of code involved in the parsing.. what I have is much simpler.


Avatar
Joseph 5 years ago

>Pretty much every delphi console application I have ever written or worked on had command line options, and every one of the projects tried different ways for
>defining and parsing the supplied options.

Delphi is in sore, sore need of a powerful argument parser bundled in its standard library.

>When I started working on this library, I found some really complex libraries (mostly .net) out there with a lot of options

There are actually some extremely simple libraries with a lot of options. IMHO the ultimate is Docopt, originally for Python but now ported to several other languages. All one needs to do is write the help text and docopt deduces all of the argument rules from that!

http://docopt.org/

For instance, one would need only write this:

Naval Fate.

Usage:
naval_fate ship new <name>...
naval_fate ship <name> move <x> <y> <--speed=<kn>>
naval_fate ship shoot <x> <y>
naval_fate mine (set|remove) <x> <y> <--moored|--drifting>
naval_fate -h | --help
naval_fate --version

Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots .
--moored Moored (anchored) mine.
--drifting Drifting mine.

and then call the parser and get back a dictionary containing the appropriate options!
https://github.com/docopt/docopt

Really amazing stuff.



Comments are closed.