WSJT-X/lib/options.f90

338 lines
10 KiB
Fortran

module options
!
! Source code copied from:
! http://fortranwiki.org/fortran/show/Command-line+arguments
!
implicit none
type option
!> Long name.
character(len=100) :: name
!> Does the option require an argument?
logical :: has_arg
!> Corresponding short name.
character :: chr
!> Description.
character(len=500) :: descr
!> Argument name, if required.
character(len=20) :: argname
contains
procedure :: print => print_opt
end type option
contains
!> Parse command line options. Options and their arguments must come before
!> all non-option arguments. Short options have the form "-X", long options
!> have the form "--XXXX..." where "X" is any character. Parsing can be
!> stopped with the option '--'.
!> The following code snippet illustrates the intended use:
!> \code
!> do
!> call getopt (..., optchar=c, ...)
!> if (stat /= 0) then
!> ! optional error handling
!> exit
!> end if
!> select case (c)
!> ! process options
!> end select
!> end do
!> \endcode
subroutine getopt (options, longopts, optchar, optarg, arglen, stat, &
offset, remain, err)
use iso_fortran_env, only: error_unit
!> String containing the characters that are valid short options. If
!> present, command line arguments are scanned for those options.
!> If a character is followed by a colon (:) its corresponding option
!> requires an argument. E.g. "vn:" defines two options -v and -n with -n
!> requiring an argument.
character(len=*), intent(in), optional :: options
!> Array of long options. If present, options of the form '--XXXX...' are
!> recognised. Each option has an associated option character. This can be
!> any character of default kind, it is just an identifier. It can, but
!> doesn't have to, match any character in the options argument. In fact it
!> is possible to only pass long options and no short options at all.
!> Only name, has_arg and chr need to be set.
type(option), intent(in), optional :: longopts(:)
!> If stat is not 1, optchar contains the option character that was parsed.
!> Otherwise its value is undefined.
character, intent(out), optional :: optchar
!> If stat is 0 and the parsed option requires an argument, optarg contains
!> the first len(optarg) (but at most 500) characters of that argument.
!> Otherwise its value is undefined. If the arguments length exceeds 500
!> characters and err is .true., a warning is issued.
character(len=*), intent(out), optional :: optarg
!> If stat is 0 and the parsed option requires an argument, arglen contains
!> the actual length of that argument. Otherwise its value is undefined.
!> This can be used to make sure the argument was not truncated by the
!> limited length of optarg.
integer, intent(out), optional :: arglen
!> Status indicator. Can have the following values:
!> - 0: An option was successfully parsed.
!> - 1: Parsing stopped successfully because a non-option or '--' was
!> encountered.
!> - -1: An unrecognised option was encountered.
!> - -2: A required argument was missing.
!> .
!> Its value is never undefined.
integer, intent(out), optional :: stat
!> If stat is 1, offset contains the number of the argument before the
!> first non-option argument, i.e. offset+n is the nth non-option argument.
!> If stat is not 1, offset contains the number of the argument that would
!> be parsed in the next call to getopt. This number can be greater than
!> the actual number of arguments.
integer, intent(out), optional :: offset
!> If stat is 1, remain contains the number of remaining non-option
!> arguments, i.e. the non-option arguments are in the range
!> (offset+1:offset+remain). If stat is not 1, remain is undefined.
integer, intent(out), optional :: remain
!> If err is present and .true., getopt prints messages to the standard
!> error unit if an error is encountered (i.e. whenever stat would be set
!> to a negative value).
logical, intent(in), optional :: err
integer, save :: pos = 1, cnt = 0
character(len=500), save :: arg
integer :: chrpos, length, st, id = 0
character :: chr
logical :: long
if (cnt == 0) cnt = command_argument_count()
long = .false.
! no more arguments left
if (pos > cnt) then
pos = pos - 1
st = 1
goto 10
end if
call get_command_argument (pos, arg, length)
! is argument an option?
if (arg(1:1) == '-') then
chr = arg(2:2)
! too long ('-xxxx...') for one dash?
if (chr /= '-' .and. len_trim(arg) > 2) then
st = -1
goto 10
end if
! forced stop ('--')
if (chr == '-' .and. arg(3:3) == ' ') then
st = 1
goto 10
end if
! long option ('--xxx...')
if (chr == '-') then
long = .true.
! check if valid
id = lookup(arg(3:))
! option is invalid, stop
if (id == 0) then
st = -1
goto 10
end if
chr = longopts(id)%chr
! check if option requires an argument
if (.not. longopts(id)%has_arg) then
st = 0
goto 10
end if
! check if there are still arguments left
if (pos == cnt) then
st = -2
goto 10
end if
! go to next position
pos = pos + 1
! get argument
call get_command_argument (pos, arg, length)
! make sure it is not an option
if (arg(1:1) == '-') then
st = -2
pos = pos - 1
goto 10
end if
end if
! short option
! check if valid
if (present(options)) then
chrpos = scan(options, chr)
else
chrpos = 0
end if
! option is invalid, stop
if (chrpos == 0) then
st = -1
goto 10
end if
! look for argument requirement
if (chrpos < len_trim(options)) then
if (options(chrpos+1:chrpos+1) == ':') then
! check if there are still arguments left
if (pos == cnt) then
st = -2
goto 10
end if
! go to next position
pos = pos + 1
! get argument
call get_command_argument (pos, arg, length)
! make sure it is not an option
if (arg(1:1) == '-') then
st = -2
pos = pos - 1
goto 10
end if
end if
end if
! if we get to this point, no error happened
! return option and the argument (if there is one)
st = 0
goto 10
end if
! not an option, parsing stops
st = 1
! we are already at the first non-option argument
! go one step back to the last option or option argument
pos = pos - 1
! error handling and setting of return values
10 continue
if (present(err)) then
if (err) then
select case (st)
case (-1)
write (error_unit, *) "error: unrecognised option: " // trim(arg)
case (-2)
if (.not. long) then
write (error_unit, *) "error: option -" // chr &
// " requires an argument"
else
write (error_unit, *) "error: option --" &
// trim(longopts(id)%name) // " requires an argument"
end if
end select
end if
end if
if (present(optchar)) optchar = chr
if (present(optarg)) optarg = arg
if (present(arglen)) arglen = length
if (present(stat)) stat = st
if (present(offset)) offset = pos
if (present(remain)) remain = cnt-pos
! setup pos for next call to getopt
pos = pos + 1
contains
integer function lookup (name)
character(len=*), intent(in) :: name
integer :: i
! if there are no long options, skip the loop
if (.not. present(longopts)) goto 10
do i = 1, size(longopts)
if (name == longopts(i)%name) then
lookup = i
return
end if
end do
! if we get to this point, the option was not found
10 lookup = 0
end function lookup
end subroutine getopt
!============================================================================
!> Print an option in the style of a man page. I.e.
!> \code
!> -o arg
!> --option arg
!> description.................................................................
!> ............................................................................
!> \endcode
subroutine print_opt (opt, unit)
!> the option
class(option), intent(in) :: opt
!> logical unit number
integer, intent(in) :: unit
integer :: l, c1, c2
if (opt%has_arg) then
write (unit, '(1x,"-",a,1x,a)') opt%chr, trim(opt%argname)
write (unit, '(1x,"--",a,1x,a)') trim(opt%name), trim(opt%argname)
else
write (unit, '(1x,"-",a)') opt%chr
write (unit, '(1x,"--",a)') trim(opt%name)
end if
l = len_trim(opt%descr)
! c1 is the first character of the line
! c2 is one past the last character of the line
c1 = 1
do
if (c1 > l) exit
! print at maximum 4+76 = 80 characters
c2 = min(c1 + 76, 500)
! if not at the end of the whole string
if (c2 /= 500) then
! find the end of a word
do
if (opt%descr(c2:c2) == ' ') exit
c2 = c2-1
end do
end if
write (unit, '(4x,a)') opt%descr(c1:c2-1)
c1 = c2+1
end do
end subroutine print_opt
end module options