1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
|
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
|