338 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Fortran
		
	
	
	
	
	
		
		
			
		
	
	
			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
 |