Amazon.com Widgets Another Gem from the VSoft Guys: TCommandLineParser

Another Gem from the VSoft Guys: TCommandLineParser

By Nick at November 16, 2014 02:02
Filed Under: Delphi, Software Development, Tech Stuff

You know, the gang at VSoft, led by Vince Parrett, have really contributed a lot to the Delphi and the Developer community.  First, they built FinalBuilder – probably one of the best known apps built with Delphi -- and now ContinuaCI for all developers.  And being Delphi developers, they have contributed the DelphiMocks and DUnitX frameworks to the Delphi community.  Recently, they added another little gem to that list – TCommandLineParser.

It’s seems less common than it used to be with “regular” applications, but command line switches certain remain popular and common with console applications.  They are used to pass filenames, boolean values, and other information on the command line. Handling those switches can be a bother, and it’s seems silly to repeat similar code for each application.  This is where TCommandLineParser comes in. 

TCommandLineParser is a library that makes it really easy to add and read command line parameters to your applications. Sure, the RTL provide the ParamStr/ParamCount functionality, but they don’t provide some of the features that TCommandLineParser does.  For instance, with TCommandLineParser you can:

  • Set switches to contain specific value types
  • Easily retrieve those switch values in your application in a simple class
  • Pass strings, boolean values, and numbers and other types

To get started using it, you can pull the code using Git from the following endpoint (You do have Git installed on your machine, right?) :

https://github.com/VSoftTechnologies/VSoft.CommandLineParser.git

The project comes with a simple demo application, but let’s build one ourselves so that we can step through the process and really see how it works.  So, let’s take the following steps:

 

  1. 1.  Create a new Console application by selecting File|New|Other|Delphi Applications|Console Application.
  2. 2.  Make sure that the directory where you put TCommandLineParser is on your path, either for the application you just created or as part of your library path for Delphi.
  3. 3.  In the uses clause of the project file, add the following two units: VSoft.CommandLine.Parser and VSoft.CommandLIne.Options.

4.  Now, add a new, blank unit to the file and call it CLPOptions.pas. This is the file that will hold the class that will end up with all of the command line parameter values.  In that file we’ll have an interesting class – one with nothing but class variables.  Put the following code into the unit.



type
  TSampleOptions = class
  public
    class var
      FileToProcess: string;
      OutputInUpperCase: Boolean; 
      NumberOfIterations: integer;
  end;

This class contains a string, a Boolean and an integer.  Other types that can be passed on the command line include enumerated types, sets, and floating point numbers.  Again, this class will end up holding the values passed on the command line, and since the variables are all class variables, they can be accessed directly without having to instantiate the class. 

5.  Next, we’ll register these items with the TOptionsRegistry class.  This class is the one that translates the command line parameters into options usable by your application.  You can define options as having a long name, a short name, and whether the parameter is optional or required.  The TOptionsRegistry.Parse method will then parse out the command line and fill in the values of the class we defined above.  In order to do that, create a new, empty unit, add it to the project, and call it CLPConfig.pas.  Then, add VSoft.CommandLine.Options and CLPOptions to the uses clause of your new unit.  Then, add the following code to the implementation part of the unit (you can leave the interface section empty):

 
procedure ConfigureOptions;
var
  Option : IOptionDefintion;
begin
  Option := TOptionsRegistry.RegisterUnNamedOption<string>('The file to be processed',
    procedure(value : string)
    begin
        TSampleOptions.FileToProcess:= value;
    end);
  Option.Required := true;

  Option := TOptionsRegistry.RegisterOption<Boolean>('OutputInUpperCase','o', 'The output should be in upper case',
    procedure(value : Boolean)
    begin
        TSampleOptions.OutputInUpperCase:= value;
    end);
  Option.Required := true;
  Option.HasValue := False;

  Option := TOptionsRegistry.RegisterOption<integer>('NumberOfIterations','n','The number of times the file should be processed',
    procedure(value : integer)
    begin
        TSampleOptions.NumberofIterations := value;
    end);
  Option.Required := False;   
  Option.HasValue := True;
  
end;

Here’s what is important to note about the following code:

  • The first thing to note is that each of the calls that registers a parameter takes a parameterized type of the type that should be received as part of the command line parameter. 
  • The first Option registered is an “unnamed” option, meaning that there is no switch or name associated with it.  Normally, a parameter will have a switch (for example “/d”)associated with it.  This option, however, will just be the filename.  The first parameter of RegisterUnNamedOption is a description of the option that will be shown to the user when “help” is displayed.  (We’ll discuss that in a bit).  The second parameter is an anonymous method that lets you do what you want with the value passed in the parameter.  It’s a TFunc<string> and in this case, we assign the string to the TSampleOptions.FileToProcess class variable.  Note that this option is also set to Required, meaning that unless the parameter is present, the application run and the “help” will be shown.
  • The second Option registered is a “regular” parameter, in that it is a switch.  That switch can two values – “/OutputInUpperCase” or “/o”.  These are defined by the first two parameters of the call to RegisterOption.  The third parameter is the help string, and the fourth is an anonymous method taking a single Boolean value that defines the presence or absence of the switch.  Because the Option.HasValue property is set to False, the switch will stand alone, and its presence means the value is True, and its absence means it is False.  In other words, the parameter has no value passed along with it. 
  • The third Option is yet another variation. This one has a switch, but it takes a value, because the Option.HasValue property is set to True.  This means that the parameter will appear as “/o:42”, using a colon to separate the switch from the passed value.  Note that this parameter is set to be optional, because the Required property is set to False.

Of course this procedure needs to be called very early in the executable timeline, so you should add an initialization section to the unit that looks like this:


initialization
  ConfigureOptions;

 

Switches are key to passing command line parameters, and TCommandLineParser will accept the following to define a switch:

  • A forward slash: /d:aValue
  • A single dash: -d:aValue
  • Double dashes: --d:aValue

Note that for options that have HasValue set to False, there is no need to pass a value. 

Of course, once you have set all these options, you need to write some code to retrieve them, and handle the situation when things aren’t right – required parameters are missing, or switches requiring values don’t have them. 

Go to the DPR file and add this code:


var
  ParseResult :  ICommandLineParseResult;
begin
  try
    //parse the command line options
    ParseResult := TOptionsRegistry.Parse;
    if ParseResult.HasErrors then
    begin
      Writeln(ParseResult.ErrorText);
      Writeln('Usage :');
      TOptionsRegistry.PrintUsage(
        procedure(value : string)
        begin
          Writeln(value);
        end);
    end else
    begin
      Writeln('FileToProcess : ', TSampleOptions.FileToProcess);
      Writeln('OutputInUpperCase : ', TSampleOptions.OutputInUpperCase);
      Writeln('Iterations : ', TSampleOptions.NumberOfIterations);
    end;
    ReadLn;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Note the following about the above code:

  • It declares ParseResult with is of type ICommandLineParseResult.  It will contain the results of parsing the command line when TOptionsRegistry.Parse is called.  The names make that pretty obvious, eh? 
  • If the result of the parsing HasErrors, then the “Usage” of the application is output.  It can be output anyway you like, as you pass the PrintUsage method an anonymous method that does the output in any way you want.  In this case, it simply prints out the value passed in to it.  This is where the “help” parameters above come in.  The PrintUsage method parses out all the registered parameters, grabs all the switch and parameter values, as well as the help string you passed in, and creates a string that shows the proper usage of the parameters.  Before printing that out, the code prints out the actual problem with the usage.
  • If all is well, and all the required parameters are present and properly formed, then the code will simply print out the values passed on the command line.

That’s it for code.  Now for using the application.  First, let’s get a look at what happens when you pass no parameters at all.  Press F9 and you should see something like this:

image

 

Note that you receive error messages, and a description of how the command line parameters work.

Then, go to Run|Parameters and in the Parameters box enter:

somefile.dat -o -n:42

Running the app should result in the following:

image

Which shows that the parameters were correctly passed. 

And that is about it.  It takes a bit of work to get going, but not much, and you get a lot of functionality for the little input that you have to make.

Like I said in the title – another gem from the folks at VSoft Technologies.

blog comments powered by Disqus

My Book

A Pithy Quote for You

"We make men without chests and expect of them virtue and enterprise. We laugh at honor and are shocked to find traitors in our midst. We castrate and then bid the geldings to be fruitful."    –  C. S. Lewis

Amazon Gift Cards

General Disclaimer

The views I express here are entirely my own and not necessarily those of any other rational person or organization.  However, I strongly recommend that you agree with pretty much everything I say because, well, I'm right.  Most of the time. Except when I'm not, in which case, you shouldn't agree with me.