TamaMcGlinn
TamaMcGlinn

Reputation: 3248

GNAT.Command_Line.Getopt - how to enforce all command line options were valid?

Following AdaCore Gem #138 : Master the Commandline I am trying to create a commandline program that only accepts two options.

I pass a string to getopt which says which options are valid:

Getopt ("-project= -help")

Which means '--project foo' can be given, and also '--help' without an argument. I want to be very strict - project requires a parameter, and help should not take an argument. However, it seems the above will happily accept (and ignore) futher parameters given on the commandline, as long as they don't begin with a minus symbol:

$ ./getopt_invalid_option.exe --project foo these options are all invalid
Project := foo

$ ./getopt_invalid_option.exe things
Project :=

$ ./getopt_invalid_option.exe --switch

raised GNAT.COMMAND_LINE.INVALID_SWITCH : Unrecognized option '--switch'

$ ./getopt_invalid_option.exe -sss

raised GNAT.COMMAND_LINE.INVALID_SWITCH : Unrecognized option '-s'

How do I disallow the first two examples above?

My situation is slightly complicated by the fact that I have a section, after which everything should be allowed. From reading the specification of GNAT.Command_Line I thought this would be achieved by calling Getopt("*") after going to the corresponding section, which works, but with and without a section, I am unable to catch invalid switches (unless they begin with '-').

In the code below, I've commented the parts pertaining to the section; but the point is that a complete solution would need to work with the section as well.

with Ada.Text_IO; use Ada.Text_IO;
with GNAT.Command_Line; use GNAT.Command_Line;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

procedure Main is
   Project : Unbounded_String;
begin
   -- Initialize_Option_Scan (Section_Delimiters => "anything");
   -- Goto_Section ("");
   loop
      case Getopt ("-project= -help") is
         when '-' =>
            if Full_Switch = "-project" then
               Project := To_Unbounded_String (Parameter);
            elsif Full_Switch = "-help" then
               Put_Line ("Usage: etc... ");
               return;
            end if;
         when ASCII.Nul =>
            exit;
         when others =>
            Put_Line ("Error: unrecognized switch " & Full_Switch);
            return;
      end case;
   end loop;
   -- Goto_Section ("anything");
   -- loop
   --    exit when Getopt ("*") = ASCII.Nul;
   --    Put_Line ("Accepted: " & Full_Switch);
   -- end loop;
   Put_Line ("Project := " & To_String (Project));
end Main;

I have tried changing the first Getopt to:

      case Getopt ("-project= -help *") is

Which does not change the behaviour at all! Also tried:

      case Getopt ("*") is

Which disallows everything, oddly enough.

Upvotes: 3

Views: 332

Answers (1)

DeeDee
DeeDee

Reputation: 5941

I altered the example program somewhat. The following seems to work:

main.adb

with Ada.Text_IO;           use Ada.Text_IO;
with Ada.Exceptions;        use Ada.Exceptions;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with GNAT.Command_Line;     use GNAT.Command_Line;

procedure Main is
   
   Usage_Error : exception;
   
   -----------------------
   -- Raise_Usage_Error --
   -----------------------
   
   procedure Raise_Usage_Error (Switch : String) is
   begin
      raise Usage_Error with "error: unrecognized switch " & Switch;
   end Raise_Usage_Error;
   
   --------------------
   --  Display_Usage --
   --------------------   
   
   procedure Display_Usage is
   begin
      Put_Line ("usage: etc... ");
   end Display_Usage;
   
      
   Project : Unbounded_String;
   
begin
   
   Initialize_Option_Scan (Section_Delimiters => "anything");   
   
   --  Scan the first section.

   loop
      case Getopt ("* project= help") is
         
         when ASCII.Nul =>
            exit;            
         
         when 'p' =>
            if Full_Switch = "project" then
               Project := To_Unbounded_String (Parameter);
            else
               Raise_Usage_Error (Full_Switch);
            end if;
            
         when 'h' =>
            if Full_Switch = "help" then
               Display_Usage;
               return;
            else
               Raise_Usage_Error (Full_Switch);
            end if;            
        
         when others =>
            Raise_Usage_Error (Full_Switch);
            
      end case;
   end loop;
      
   Put ("OK: project = " & To_String (Project));
      
   --  Scan the "anything" section.

   Goto_Section ("anything");
   while Getopt ("*") /= ASCII.Nul loop
      Put (", arg = " & Full_Switch);
   end loop;    
   
exception
   when UE : Usage_Error =>
      Put_Line (Exception_Message (UE));
      --  Optional: Display_Usage
   
end Main;

test.sh

#!/bin/bash

function run_test ()
{
    echo "------------------------------"
    echo "  input   : $1"
    echo "  output  : $($1)"
}

run_test "./obj/main -project=foo"
run_test "./obj/main -project foo"
run_test "./obj/main -help"
run_test "./obj/main -project=foo -anything ddd eee fff"
run_test "./obj/main -help -anything ddd eee fff"
run_test "./obj/main -project=foo aaa bbb ccc"
run_test "./obj/main -help aaa bbb ccc"
run_test "./obj/main -project=foo aaa bbb ccc -anything ddd eee fff"
run_test "./obj/main -help aaa bbb ccc -anything ddd eee fff"
run_test "./obj/main --sss"
run_test "./obj/main -sss"

output

$ ./test.sh 
------------------------------
  input     : ./obj/main -project=foo
  output    : OK: project = foo
------------------------------
  input     : ./obj/main -project foo
  output    : OK: project = foo
------------------------------
  input     : ./obj/main -help
  output    : usage: etc... 
------------------------------
  input     : ./obj/main -project=foo -anything ddd eee fff
  output    : OK: project = foo, arg = ddd, arg = eee, arg = fff
------------------------------
  input     : ./obj/main -help -anything ddd eee fff
  output    : usage: etc... 
------------------------------
  input     : ./obj/main -project=foo aaa bbb ccc
  output    : error: unrecognized switch aaa
------------------------------
  input     : ./obj/main -help aaa bbb ccc
  output    : usage: etc... 
------------------------------
  input     : ./obj/main -project=foo aaa bbb ccc -anything ddd eee fff
  output    : error: unrecognized switch aaa
------------------------------
  input     : ./obj/main -help aaa bbb ccc -anything ddd eee fff
  output    : usage: etc... 
------------------------------
  input     : ./obj/main --sss
  output    : error: unrecognized switch --sss
------------------------------
  input     : ./obj/main -sss
  output    : error: unrecognized switch -sss

Upvotes: 1

Related Questions