Clicky

Fortran Wiki
getopt_long_module

This is not well tested. I intended to finish it when allocatable-length strings became generally available.

This is based on Sun’s getopt_long, and include the Sun CLIP specification, which requires matching short and long versions of all options,s.

Precise getopt functionality is not really desirable. The biggest drawback of getopt is the use of globals. (It was designed a long time ago.) This interface uses OOP with a derived-type data object,

module getopt_long_module
public
integer, parameter :: max_option_len = 15

! return values 
character(len=1), parameter :: GETOPT_STATUS_BADCH = '?'
character(len=1), parameter :: GETOPT_STATUS_BADARG = ':'
character(len=1), parameter :: GETOPT_STATUS_INORDER = char(1)
character(len=1), parameter :: GETOPT_STATUS_END = char(255)
character(len=1), parameter :: GETOPT_STATUS_NIL = char(0)

! long options argument flag:
integer, parameter :: GETOPT_NO_ARG = 0
integer, parameter :: GETOPT_REQ_ARG = 1
integer, parameter :: GETOPT_OPT_ARG = 2

! Behavior flags:
integer, parameter :: GETOPT_FLAG_PERMUTE             = int(z'01') ! Permute non-optstring to the end of argv 
integer, parameter :: GETOPT_FLAG_ALLARGS             = int(z'02') ! Treat non-optstring as args to option INORDER
integer, parameter :: GETOPT_FLAG_LONGONLY            = int(z'04') ! Operate as getopt_long_only() 
integer, parameter :: GETOPT_FLAG_OPTIONAL_ARGS       = int(z'08') ! Support optional arguments in optstring 
integer, parameter :: GETOPT_FLAG_REQUIRE_EQUIVALENTS = int(z'10') ! Require short<->long equivalents 
integer, parameter :: GETOPT_FLAG_ABBREV              = int(z'20') ! Support long option abbreviations
integer, parameter :: GETOPT_FLAG_W_SEMICOLON         = int(z'40') ! Support W; in optstring 
integer, parameter :: GETOPT_FLAG_PLUS_DASH_START     = int(z'80') ! Support leading '+' or '-' in optstring 

integer, parameter :: GETOPT_FLAGS_GNU = &
         GETOPT_FLAG_PERMUTE + GETOPT_FLAG_OPTIONAL_ARGS + GETOPT_FLAG_ABBREV + &
         GETOPT_FLAG_W_SEMICOLON + GETOPT_FLAG_PLUS_DASH_START

integer, parameter :: GETOPT_FLAGS_POSIX = &
         GETOPT_FLAG_ABBREV  !?  + GETOPT_FLAG_PLUS_DASH_START

integer, parameter :: GETOPT_FLAGS_DEFAULT = &
         GETOPT_FLAG_PERMUTE + GETOPT_FLAG_ABBREV + GETOPT_FLAG_PLUS_DASH_START

integer, parameter :: GETOPT_FLAGS_LONG = &
         GETOPT_FLAG_PERMUTE + GETOPT_FLAG_OPTIONAL_ARGS + GETOPT_FLAG_ABBREV + &
         GETOPT_FLAG_W_SEMICOLON + GETOPT_FLAG_PLUS_DASH_START

integer, parameter :: GETOPT_FLAGS_LONG_ONLY = &
         GETOPT_FLAGS_LONG + GETOPT_FLAG_LONGONLY

integer, parameter :: GETOPT_FLAGS_SUN_CLIP = &
         GETOPT_FLAG_W_SEMICOLON + GETOPT_FLAG_REQUIRE_EQUIVALENTS
! GETOPT_FLAGS_SUN_CLIP behaves as Sun's getopt_clip() --
!  Parse argc/argv argument vector, requiring compliance with
!   Sun's CLIP specification (11/12/02)
! o Does not allow arguments to be optional (optional_argument is
!   treated as required_argument).
! o Does not allow long optstring to be abbreviated on the command line
! o Does not allow long argument to be preceded by a single dash
!   (Double-dash '--' is required)
! o Stops option processing at the first non-option
! o Requires that every long option have a short-option (single
!   character) equivalent and vice-versa. If a short option or
!   long option without an equivalent is found, an error message
!   is printed and -1 is returned on the first call, and errno
!   is set to EINVAL.
! o Leading + or - in optstring is ignored, and opstring is
!   treated as if it began after the + or - .
!
! It does support the special "W;" in optstring
!================================================================================
type getopt_option_type
  character(len=max_option_len) :: name
  integer :: has_arg = GETOPT_NO_ARG
  character(len=1), pointer :: flag => NULL() ! Reference to user's flag variable
  character(len=1) :: val = char(0)
end type getopt_option_type

type getopt_string_type
  character(len=1), allocatable :: string(:)
end type getopt_string_type

type getopt_type
! private:
  character(len=1), pointer :: place(:) ! => empty_string_array
  integer :: nonopt_start != -1; ! first non option argument (for permute) 
  integer :: nonopt_end != -1;   ! first option after non optstring (for permute) 
  logical :: posixly_correct ! = .false.
  integer :: flags
  character(len=512), pointer :: optstring ! should be (len=*)
  integer :: optstring_len
  type(getopt_option_type), pointer :: long_opts(:) ! reference to user's longopts array
! public:
  integer :: argc
  type(getopt_string_type), allocatable :: argv(:)
  integer :: index ! = 0
  logical :: error ! = .false.
  character(len=1), pointer :: optarg(:) ! => NULL()
  character(len=1) :: opt ! = char(0) == GETOPT_STATUS_NIL
end type getopt_type

character(len=1), target :: empty_string_array(0)
!================================================================================
interface warn
  module procedure warn_string, warn_char_array
end interface warn

private :: warn, warn_string, warn_char_array

private :: permute_args, parse_longopts

!================================================================================
contains

subroutine getopt_new(self, optstring, long_opts, flags, status)
  implicit none
  type(getopt_type), pointer :: self
  character(len=*), intent(in), target :: optstring
  type(getopt_option_type), intent(in), target, optional :: long_opts(:)
  integer, intent(in), optional :: flags
  integer, intent(out), optional :: status

  intrinsic :: command_argument_count, &
               get_command_argument, &
               get_environment_variable
  integer :: i, arglen

  allocate(self)
  self%optarg => NULL()
  self%place => empty_string_array
  self%nonopt_start = -1
  self%nonopt_end = -1
  self%index = 1
  self%error = .false.
  self%opt = GETOPT_STATUS_NIL

  if (present(flags)) then
    self%flags = flags
  else if (present(long_opts)) then
    self%flags = GETOPT_FLAGS_LONG
  else
    self%flags = GETOPT_FLAGS_DEFAULT
  end if

! Disable GNU extensions if ENV{POSIXLY_CORRECT} is set.
! If optstring begins with a '+' or '-', and the option flags
! enable this feature, the caller chooses POSIX correctness.
  self%posixly_correct = ( i <= 0 )
  self%optstring => optstring(1:len(self%optstring))
  self%optstring_len=len(optstring)
  if (iand(self%flags,GETOPT_FLAG_PLUS_DASH_START)/=0) then
    select case(optstring(1:1))
    case('+')
      self%posixly_correct = .true.
      self%optstring => optstring(2:)
      self%optstring_len=len(optstring)-1
    case('-')
      self%posixly_correct = .false.
      self%optstring => optstring(2:)
      self%optstring_len=len(optstring)-1
      self%flags = ior(self%flags,GETOPT_FLAG_ALLARGS)
    case default
      self%posixly_correct = ( i <= 0 )
    end select
  else
  end if

  if (self%posixly_correct) then
    self%flags = iand(self%flags,not( &
          GETOPT_FLAG_PERMUTE + GETOPT_FLAG_ALLARGS + GETOPT_FLAG_OPTIONAL_ARGS ))
  end if
  self%optarg => NULL()

!-------------------------------------------------------------------------
! EXTENSION: Sun's CLIP specification (11/12/02)
! This option requires a matching long and short version for every option.
  if ((self%index == 1) .and. iand(self%flags,GETOPT_FLAG_REQUIRE_EQUIVALENTS)/=0) then
    if (.not. verify_short_long_equivalents()) then
      if (.not. present(status)) stop 'Error in getopt_long() arguments'
      deallocate(self)
      status=.false.
      return
    end if
  end if
!-------------------------------------------------------------------------

  self%argc=command_argument_count()
  allocate(self%argv(0:self%argc))
  do i=0,self%argc
    call get_command_argument(i,length=arglen)
    allocate(self%argv(i)%string(arglen))
    call get_command_argument(i,self%argv(i)%string(1)(1:arglen))
  end do
  if (present(status)) status=.true.
  return

contains

! Verify that each short option (character flag) has a long equivalent,
! and that each long option has a short option equivalent. Note that
! multiple long optstring can map to the same character.
! This behavior is defined by Sun's CLIP specification (11/12/02).
!
! If error output is enabled and an error is found, this function
! prints ONE error message (the first error found) and returns an
! error value.
! ASSUMES: optstring is present and long_opts is optional
! Returns .TRUE. on success
  logical &
  function verify_short_long_equivalents() result(ok)
    implicit none
    !accessed by host association:
    !type(getopt_type), pointer :: self
    !character(len=*), intent(in) :: optstring
    !type(getopt_option_type), intent(in), optional :: long_opts(:)
    !integer, intent(in) :: flags

    integer :: i
    character(len=1) :: ch
    ch = GETOPT_STATUS_NIL

  ! Find a long option for each short option
    i=1
    do while ( ok .and. (i<=len(optstring)))
      ch = optstring(i:i)
      if (ch == ':') then
        i=i+1
        cycle
      end if

  ! 'W;' is a special case, if GETOPT_FLAG_W_SEMICOLON is set:
      if (iand(flags,GETOPT_FLAG_W_SEMICOLON)/=0 .and. &
        (ch == 'W') .and. (optstring(i+1:i+1) == ';')) then
        i=i+2
        cycle
      end if

      if (associated(self%long_opts)) then
        ok=any(ch==self%long_opts(:)%val)
      else
        ok=.false.
      end if
      if (.not.ok) then
        call warn(self,"equivalent long option required",ch)
        return
      end if

      i=i+1
    end do

  ! Find a short option for each long option.
    if (associated(self%long_opts)) then
      do i=1,size(self%long_opts)
        ok = (self%long_opts(i)%val /= GETOPT_STATUS_NIL .and. &
            index(optstring, self%long_opts(i)%val) > 0)
        if (.not. ok) then
          call warn(self,"equivalent short option required",self%long_opts(i)%name)
          return
        end if
      end do
    end if
    ok=.true.
    return
  end function verify_short_long_equivalents
end subroutine getopt_new

!----------------------------------------------------------------------
! Public interface procedures:
character(len=getopt_argv_len(self,argn)) &
function getopt_argv(self,argn) result(argv)
  implicit none
  type(getopt_type), pointer :: self
  integer, intent(in) :: argn
  argv = self%argv(argn)%string(1)(1:len(argv))
end function getopt_argv

! getopt() --
!  Parse argc/argv argument vector.  Called by user level routines.
! This implements all of the getopt variants, depending on flag bits.
character(len=1) &
function getopt(self, longindex) result(retval)
  implicit none
  type(getopt_type), pointer :: self
  integer, intent(out), optional :: longindex

  integer :: option_letter_index
  character(len=1) :: optchar
  logical :: short_too

  if (.not. allocated(self%argv)) then
    retval = GETOPT_STATUS_END
    return
  end if

  self%optarg => NULL()
  self%opt = GETOPT_STATUS_NIL
write(*,*)'index=',self%index
NEXT_ARG: do
    if (self%index==0 .or. size(self%place)==0) then    ! update scanning pointer 
      if (self%index > ubound(self%argv,1)) then    ! end of argument vector 
        self%place => empty_string_array
        if (self%nonopt_end /= -1) then
! do permutation, if we have to 
          self%index = self%index - (self%nonopt_end - self%nonopt_start)

        else if (self%nonopt_start /= -1) then
! If we skipped non-optstring, set self%index
! to the first of them.
          self%index = self%nonopt_start
        end if
        self%nonopt_start = -1
        self%nonopt_end = -1
        retval = GETOPT_STATUS_END
        return
      end if
      self%place => self%argv(self%index)%string
      if (self%place(1) /= '-' .or. size(self%place,1)<2 ) then
        self%place => empty_string_array  ! found non-option
        write(*,*) 'found non-option at ',self%index
        if (iand(self%flags,GETOPT_FLAG_ALLARGS)/=0) then
! GNU extension:
! return non-option as argument to option char(1)
          self%index=self%index+1
          self%optarg => self%argv(self%index)%string
          retval = GETOPT_STATUS_INORDER
          return
        end if
        if (iand(self%flags,GETOPT_FLAG_PERMUTE)==0) then
! If no permutation wanted, stop parsing
! at first non-option.
          retval = GETOPT_STATUS_END
          return
        end if
! do permutation 
        if (self%nonopt_start == -1) then
          self%nonopt_start = self%index
        else if (self%nonopt_end /= -1) then
          self%nonopt_start = self%index - (self%nonopt_end - self%nonopt_start)
          self%nonopt_end = -1
        end if
        self%index = self%index + 1
! process next argument 
        cycle NEXT_ARG
      end if
      if (self%nonopt_start /= -1 .and. self%nonopt_end == -1) &
        self%nonopt_end = self%index

! Check for "--" or "--foo" with no long optstring
! but if self%place is simply "-" leave it unmolested.
      if (size(self%place)>1) then
        self%place=>self%place(2:)
        if (self%place(1) == '-' .and. &
           (size(self%place)==1 .or. .not. associated(self%long_opts))) then
          self%index = self%index + 1
          self%place => empty_string_array
! We found an option (--), so if we skipped
! non-optstring, we have to permute.
          if (self%nonopt_end /= -1) then
            self%index = self%index - (self%nonopt_end - self%nonopt_start)
          end if
          self%nonopt_start = -1
          self%nonopt_end = -1
          retval = GETOPT_STATUS_END
          return
        end if
      end if
    end if
    exit NEXT_ARG
  end do NEXT_ARG

! Check long optstring if:
!  1) we were passed some
!  2) the arg is not just "-"
!  3) either the arg starts with -- or we are getopt_long_only()
!if (self%long_opts /= NULL .and. self%place /= self%argv[self%index] && (*self%place == '-' .or. (GETOPT_FLAG_IS_SET(GETOPT_FLAG_LONGONLY)))) then

  if (associated(self%long_opts) .and. &
      (.not. associated(self%place,self%argv(self%index)%string)) .and. &
      (self%place(1) == '-' .or. iand(self%flags,GETOPT_FLAG_LONGONLY)/=0)) then
    short_too = .false.
    if (self%place(1) == '-') then
      self%place => self%place(2:)  ! --foo long option 
    else if (self%place(1) /= ':' .and. index(self%optstring, self%place(1)) > 0) then
      short_too = .true.    ! could be short option too 
    end if

    optchar = parse_longopts(self, longindex, short_too)
    if (optchar /= GETOPT_STATUS_END) then
      self%place => empty_string_array
      retval = optchar
      return
    end if
  end if

  optchar = self%place(1)
  self%place => self%place(2:)
  option_letter_index = index(self%optstring(1:self%optstring_len), optchar)
  if (optchar == ':' .or. option_letter_index==0) then
! If the user didn't specify '-' as an option,
! assume it means GETOPT_STATUS_END (-1) as POSIX specifies.
    if (optchar == '-') then
      retval = GETOPT_STATUS_END
      return
    end if
! option letter unknown or ':' 
    if (size(self%place)==0) self%index=self%index+1
    if ((self%error) .and. self%optstring(1:1) /= ':') then
    end if
    self%opt = optchar
    retval = GETOPT_STATUS_BADCH
    return
  end if

! -W long-option 
  if (iand(self%flags,GETOPT_FLAG_W_SEMICOLON)/=0 &
      .and. associated(self%long_opts) &
      .and. optchar == 'W' &
      .and. self%optstring(option_letter_index:option_letter_index) == ';') then
    if (size(self%place)>0) then
      self%index=self%index+1
      if (self%index > ubound(self%argv,1)) then  ! no long-option after -W 
        self%place => empty_string_array
        if ((self%error) .and. self%optstring(1:1) /= ':') then
        end if
        self%opt = optchar
        retval = merge(GETOPT_STATUS_BADARG,GETOPT_STATUS_BADCH,self%optstring(1:1)==':')
        return
      else      ! white space 
        self%place => self%argv(self%index)%string
      end if
    end if
    optchar = parse_longopts(self, longindex, .false.)

! PSARC 2003/645 - Match GNU behavior, set self%optarg to
! the long-option.
    if (.not. associated(self%optarg)) then
      self%optarg => self%argv(self%index-1)%string
    end if
    self%place => empty_string_array
    retval = optchar
    return
  end if

  option_letter_index=option_letter_index+1
  if (self%optstring(option_letter_index:option_letter_index) /= ':') then ! no ':' suffix: doesn't take argument 
    if (size(self%place)==0) self%index=self%index+1
  else        ! ':' or '::' suffix: takes (optional) argument 
    self%optarg => NULL()
    if (size(self%place)>0) then  ! arg value joined (no white space)
      self%optarg => self%place
! XXX: disable test for :: if PC? (GNU doesn't) 
    else if (iand(self%flags,GETOPT_FLAG_OPTIONAL_ARGS)==0 .and. &
          self%optstring(option_letter_index+1:option_letter_index+1) == ':') then
! arg is required (not optional) 
      self%index=self%index+1
      if (self%index > ubound(self%argv,1)) then  ! no arg 
        self%place => empty_string_array
        if ((self%error) .and. self%optstring(1:1) /= ':') then
        end if
        self%opt = optchar
        retval = merge(GETOPT_STATUS_BADARG,GETOPT_STATUS_BADCH,self%optstring(1:1)==':')
        return
      else
        self%optarg => self%argv(self%index)%string
      end if
    end if
    self%place => empty_string_array
    self%index=self%index+1
  end if
! return valid option letter 
  self%opt = optchar  ! preserve getopt() behavior 
  retval = optchar
  return
end function getopt

!----------------------------------------------------------------------
pure integer &
function getopt_argv_len(self,argn) result(len)
  implicit none
  type(getopt_type), pointer :: self
  integer, intent(in) :: argn
  if (.not. allocated(self%argv)) then
    len = 0
  else if ( (argn < 0) .or. (argn > ubound(self%argv,1)) ) then
    len = 0
  else
    len = size(self%argv(argn)%string)
  end if
end function getopt_argv_len

!----------------------------------------------------------------------
! Private procedures:
subroutine warn_string(self, msg, arg)
  implicit none
  type(getopt_type), pointer :: self
  character(len=*), intent(in) :: msg, arg
  if (self%error .and. self%optstring(1:1) /= ':') then 
    write(0,"(A,': ',A,' -- ',A)") &
        self%argv(0)%string(1)(1:ubound(self%argv(0)%string,1)), &
        msg, trim(arg)
  end if
end subroutine warn_string

subroutine warn_char_array(self, msg, arg)
  implicit none
  type(getopt_type), pointer :: self
  character(len=*), intent(in) :: msg
  character(len=1), intent(in) :: arg(:)
  if (self%error .and. self%optstring(1:1) /= ':') then 
    write(0,"(A,': ',A,' -- ',100A1)") &
        self%argv(0)%string(1)(1:ubound(self%argv(0)%string,1)), &
        msg, arg
  end if
end subroutine warn_char_array

subroutine permute_args(self)
  implicit none
  type(getopt_type), pointer :: self

  integer :: cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos
  character(len=1), allocatable :: swap(:)

  if (.not. allocated(self%argv)) return

! compute lengths of blocks and number and size of cycles
  nnonopts = self%nonopt_end - self%nonopt_start
  nopts = self%index - self%nonopt_end
  ncycle = gcd(nnonopts, nopts)
  cyclelen = (self%index - self%nonopt_start) / ncycle

  do i=1,ncycle
    cstart = self%nonopt_end+i
    pos = cstart
    do j=0,cyclelen-1
      if (pos >= self%nonopt_end) then
        pos = pos - nnonopts
      else
        pos = pos + nopts
      end if

      allocate(swap(size(self%argv(pos)%string)))

      swap = self%argv(pos)%string
      deallocate(self%argv(pos)%string)
      allocate(self%argv(pos)%string(size(self%argv(cstart)%string)))
      self%argv(pos)%string = self%argv(cstart)%string

      deallocate(self%argv(cstart)%string)
      allocate(self%argv(cstart)%string(size(swap)))
      self%argv(cstart)%string = swap

      deallocate(swap)
    end do
  end do
contains
! Compute the greatest common divisor of a and b.
  integer function gcd(aval, bval)
    integer, intent(in) :: aval, bval
    integer :: a,b,c
    a = aval
    b = bval
    c = modulo(a,b)
    do while (c /= 0)
      a = b
      b = c
      c = modulo(a,b)
    end do
    gcd = b
  end function gcd
end subroutine permute_args

! parse_longopts --
!  Parse long optstring in argc/argv argument vector.
! Returns -1 if short_too is set and the option does not match self%long_opts.
character(len=1) &
function parse_longopts(self, longindex, short_too) result(retval)
  implicit none
  type(getopt_type), pointer :: self
  integer, intent(inout), optional :: longindex
  logical, intent(in) :: short_too

  character(len=1), pointer :: current_argv(:)
  character(len=1), pointer :: argv_equal_ptr(:)
  integer :: current_argv_len
  integer :: long_option_len
  integer :: i
  integer :: match
  logical :: longopt_requires_arg
  integer :: argv_equal_index

  current_argv => NULL()
  current_argv_len = 0
  long_option_len = 0
  i = 0
  match = 0
  current_argv => self%place
  match = -1

  self%index=self%index+1

  argv_equal_index = index(current_argv(1)(1:size(current_argv)), '=')
  if (argv_equal_index>0) then
! argument found (--option=arg) 
    current_argv_len = argv_equal_index - 1
    argv_equal_ptr => current_argv(argv_equal_index+1:)
  else
    current_argv_len = size(current_argv)
    argv_equal_ptr => NULL()
  end if

! find matching long option 
  do i=1,size(self%long_opts)
    long_option_len = len_trim(self%long_opts(i)%name)
    if (long_option_len < current_argv_len) cycle
    if (current_argv(1)(1:current_argv_len) /= self%long_opts(i)%name(1:current_argv_len)) cycle

    if (iand(self%flags,GETOPT_FLAG_ABBREV)==0 .and. &
        long_option_len > current_argv_len) then
      cycle  ! Abbreviations are disabled 
    end if

    if (long_option_len == current_argv_len) then
! exact match 
      match = i
      exit
    end if

! If this is a known short option, don't allow
! a partial match of a single character.
    if (short_too .and. current_argv_len == 1) cycle

    if (match == -1) then ! first partial match 
      match = i
    else ! ambiguous abbreviation 
      if (self%error .and. self%optstring(1:1) /= ':') then
      end if
      self%opt = GETOPT_STATUS_NIL
      retval = GETOPT_STATUS_BADCH
      return
    end if
  end do

  if (match /= -1) then    ! option found 
    if (self%long_opts(match)%has_arg == GETOPT_NO_ARG .and. &
        associated(argv_equal_ptr)) then
      if (self%error .and. self%optstring(1:1) /= ':') then
      end if
! XXX: GNU sets self%opt to val regardless of flag
      if (.not. associated(self%long_opts(match)%flag)) then
        self%opt = self%long_opts(match)%val
      else
        self%opt = GETOPT_STATUS_NIL
      end if
      retval = merge(GETOPT_STATUS_BADARG,GETOPT_STATUS_BADCH,self%optstring(1:1)==':')
      return
    end if

    longopt_requires_arg = &
        ( self%long_opts(match)%has_arg == GETOPT_OPT_ARG &
        .and.  iand(self%flags,GETOPT_FLAG_OPTIONAL_ARGS) == 0 ) &
        .or. self%long_opts(match)%has_arg == GETOPT_REQ_ARG

    if (self%long_opts(match)%has_arg == GETOPT_REQ_ARG .or. &
        self%long_opts(match)%has_arg == GETOPT_OPT_ARG) then
      if (associated(argv_equal_ptr)) then
        self%optarg => argv_equal_ptr
      else if (longopt_requires_arg) then
! The next argv must be the option argument 
        if (self%index <= ubound(self%argv,1)) then
          self%optarg => self%argv(self%index)%string
        end if
        self%index=self%index+1 ! code below depends on this 
      end if
    end if

    if (longopt_requires_arg .and. .not. associated(self%optarg)) then
! Missing argument; leading ':' indicates no error
! should be generated.
      if ((self%error) .and. (self%optstring(1:1) /= ':')) then
      end if
! XXX: GNU sets self%opt to val regardless of flag
      if (.not. associated(self%long_opts(match)%flag)) then
        self%opt = self%long_opts(match)%val
      else
        self%opt = GETOPT_STATUS_NIL
      end if
      self%index=self%index-1
      retval = merge(GETOPT_STATUS_BADARG,GETOPT_STATUS_BADCH,self%optstring(1:1)==':')
      return
    end if

  else     ! match==-1; unknown option 
    if (short_too) then
      self%index=self%index-1
      retval = GETOPT_STATUS_END
      return
    end if
    if ((self%error) .and. (self%optstring(1:1) /= ':')) then
    end if
    self%opt = GETOPT_STATUS_NIL
    retval = GETOPT_STATUS_BADCH
    return
  end if

  if (present(longindex)) longindex = match
  if (associated(self%long_opts(match)%flag)) then
    self%long_opts(match)%flag = self%long_opts(match)%val
    retval = GETOPT_STATUS_NIL
    return
  else
    self%opt = self%long_opts(match)%val
    retval = self%opt
  end if
end function parse_longopts

end module getopt_long_module
!
! C getopt_long globals:
! int   optind => integer::self%index
! int   opterr => integer::self%error
! int   optopt => integer::self%opt
! char *optarg => character(len=1)::self%optarg(:)
!EXAMPLE
#ifdef SELFTEST
program selftest_program
  use getopt_long_module
  implicit none
  character(len=1) :: c
  integer :: i
  integer :: digit_optind = 0
  type(getopt_type), pointer :: opts

  integer :: this_option_optind
  integer :: option_index
  character(len=1), parameter :: NIL = char(0)

  type(getopt_option_type) :: long_options(6) = (/ &
      getopt_option_type("add",     1, NULL(), NIL), &
      getopt_option_type("append",  0, NULL(), NIL), &
      getopt_option_type("delete",  1, NULL(), NIL), &
      getopt_option_type("verbose", 0, NULL(), NIL), &
      getopt_option_type("create",  1, NULL(), 'c'), &
      getopt_option_type("file",    1, NULL(), NIL) /)
  character(len=*), parameter :: optstring = "abc:d:012"

  call getopt_new(opts,optstring,long_options)

  do
    this_option_optind = merge(opts%index,1,opts%index>0)
    option_index = 0
    c = getopt(opts,option_index)
    write(*,*)'retval=',c
    select case(c)
    case (GETOPT_STATUS_END)
      exit
    case (GETOPT_STATUS_NIL)
      write(*,'(2A)',advance='no') "option ", trim(long_options(option_index)%name)
      if (associated(opts%optarg)) &
        write(*,'(2A)',advance='no') "with arg ", opts%optarg
      write(*,*) !newline

    case ('0','1','2')
      if (digit_optind /= 0 .and. digit_optind /= this_option_optind) &
        write(*,*) "digits occur in two different argv-elements."
      digit_optind = this_option_optind
      write(*,*)"option ",c

    case ('a','b')
      write(*,*)"option ",c

    case ('c','d')
      write(*,*)"option ",c," with value '",opts%optarg,'"'

    case default
      write(*,*) "?? getopt returned character code ",ichar(c)," ??"

    end select

  end do

  if (opts%index <= opts%argc) then
    write(*,'(A)',advance='no') "non-option ARGV-elements: "
    do i=opts%index,opts%argc
      write(*,'(A,1X)',advance='no') getopt_argv(opts,i)
    end do
    write(*,*) ! newline
  end if

  stop
end program selftest_program
#endif /* SELFTEST */