Gem #139 : Master the Command Line - Part 2

High-level API

As mentioned in Part 1, GNAT.Command_Line also provides a higher-level API, where most of the handling is fully automatic. Here is an example of its use, similar to the example discussed in Part 1:

   with GNAT.Strings;  use GNAT.Strings;

   declare
      Config : Command_Line_Configuration;
      A_Enabled   : Boolean := False;
      B_Option    : String_Access;
      Long_Option : Integer := 0;

      procedure Callback (Switch, Param, Section : String) is
      begin
         if Switch = "-a" then
            A_Enabled := True;
         elsif Switch = "-b" then
            B_Option := new String'(Param);
         elsif Switch = "--long" then
            Long_Option := Integer'Value (Param);
         elsif Switch = "--help" then
            Display_Help (Config);    -- 1
         end if;
      end Callback;

   begin
      Define_Switch (Config, "-a", Help => "Enable option a");   -- 2
      Define_Switch (Config, "-b:", Help => "Enable option b");
      Define_Switch (Config, Long_Switch => "--long=",
                     Help => "Enable long option. Arg is an integer");
      Define_Switch (Config, Long_Switch => "--help",
                     Help => "Display help");

      Getopt (Config, Callback'Access);   -- 3
   end;

   Put_Line ("File argument was " & Get_Argument);  --  7

The code is not much shorter than the previous example, but in fact it is more realistic because it saves the parameter values of the various switches, since presumably these parameters are used to configure the application in some way.

We no longer have an explicit loop to get the switches one by one. Instead, the call to Getopt at step 3 will automatically call Callback for each switch it finds (as defined by the calls to Define_Switch in step 2).

In step 1, we see that the documentation for the list of valid switches can be automatically generated. This has the great benefit of ensuring consistency between what is documented and what is available. The documentation for each switch is also kept close to its definition.

Still, this API requires quite a lot of manual fiddling to save the parameters. It is also not type safe. For example, there is no indication that the parameter to --long is expected to be an integer, except in the help message.

So GNAT.Command_Line provides an even more convenient API, which can be combined with the above, as illustrated here:

   with GNAT.Strings;  use GNAT.Strings;

   declare
      Config : Command_Line_Configuration;
      A_Enabled   : aliased Boolean := False;
      B_Option    : aliased String_Access;
      Long_Option : aliased Integer := 0;

   begin
      Define_Switch (Config, A_Enabled'Access, "-a",
                     Help => "Enable option a");
      Define_Switch (Config, B_Option'Access, "-b:",
                     Help => "Enable option b");
      Define_Switch (Config, Long_Option'Access,
                     Long_Switch => "--long=",
                     Help => "Enable long option. Arg is an integer");

      Getopt (Config);
   end;

   Put_Line ("File argument was " & Get_Argument);

Now we are only declaring the switches and specifying where their parameters should be stored. We no longer need to explicitly mention "--help", which is automatically supported.

This high-level API also supports the use of sections and will automatically process the arguments in each section. It also supports reading the switches from a list of strings (via an Opt_Parser object, as before).

When you declare the switches, there are various subprograms provided to specify how they could be combined on the command line. For instance:

    Define_Prefix (Config, "-gnaty");
    Define_Switch (Config, "-gnatya");
    Define_Switch (Config, "-gnatyb");

would parse the command line "-gnatyab" as the combination of the two switches "-gnatya -gnatyb".

Furthermore, you can declare aliases. For instance, in addition to the above you could use:

   Define_Alias (Config, "-gnaty", "-gnatyab");

which would further allow users to use "-gnaty" as an equivalent to "-gnatya -gnatyb".

However, remember to keep your command-line options simple!

Creating command lines

Finally, GNAT.Command_Line allows you to create a command line. It is rarely needed, but this can benefit applications such as IDEs that allow you to edit the switches used when spawning applications.

With the above Config object, the application can now be written quite simply as follows:

   declare
      Cmd  : Command_Line;
      Args : Argument_List_Access;

   begin
      Set_Configuration (Cmd, Config);
      Add_Switch (Cmd, "-gnatya");
      Add_Switch (Cmd, "-a");
      Add_Switch (Cmd, "-gnatyb");

      Build (Cmd, Args);
      --  Args now contains ("-gnaty", "-a"), which is the shortest
      --  representation of the switches we specified.
   end;