Gem #139 : Master the Command Line - Part 2
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;