diff --git a/README.md b/README.md index a0af2a4..f7282eb 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,111 @@ -f90getopt -========= +# f90getopt [![Status](https://img.shields.io/badge/status-stable-brightgreen.svg)]() -getopt()- and getopt_long()-like functionality (similar to the C-functions) for Fortran 90. Based on sources from [Mark Gates](http://lagrange.mechse.illinois.edu/mwest/partmc/partmc-2.2.1/src/getopt.F90). +getopt()- and getopt_long()-like functionality (similar to the C-functions) for Fortran 90/2003 or higher. Based on sources from [Mark Gates](http://lagrange.mechse.illinois.edu/mwest/partmc/partmc-2.2.1/src/getopt.F90). -## Purpose: +*f90getopt* is developed as an easy to learn and compact library in one source file. The `f90getopt.F90` file can just be added to existing sources and deployed with them without producing dependencies. You can "learn" f90getopt in minutes and therefore it is even suitable for very small projects or "throw-away-code". + +* [Purpose](#Purpose) +* [Features](#Features) +* [Requirements](#Requirements) +* [Example](#Example) + * [Build the sample program](#Build-the-sample-program) + * [Run the sample program](#Run-the-sample-program) +* [Compile and link to static library](#Compile-and-link-to-static-library) +* [Userfunctions and Variables](#Userfunctions-and-Variables) +* [Differences](#Differences) + * [From C version](#From-C-version) + * [For long options](#For-long-options) +* [Changelog](#Changelog) +* [License](#License) + +## Purpose Parsing command-line options and arguments like:
myapp -xv --longarg --arg=5.0 -p 9
-## Features: +## Features * Short option without argument (e.g.: -x) * Short option with argument (e.g.: -p 9 or -p9) * Short options w/o arguments can be embraced (e.g.: -xv) * Long option w/o argument (e.g: --longarg) - * Long option w/ argument (e.g.: --arg 5.0) and ***NEW***: Equal sign (e.g.: --arg=5.0) + * Long option w/ argument (e.g.: --arg 5.0 or --arg=5.0) ## Requirements -Fortran 90 compiler which offers Fortran 2003 features *command_argument_count()* and *get_command_argument()*. E.g.: gfortran (GNU), g95 (g95.org), ifort (Intel), etc. + +Fortran 2003 or Fortran 90 compiler which offers Fortran 2003 features *command_argument_count()* and *get_command_argument()*. E.g.: gfortran (GNU), g95 (g95.org), ifort (Intel), etc. ## Example +This is a full working example and it make use of long and short options. It is well documented and should answer most questions. If you need further information, refer the [Wiki page](https://github.com/haniibrahim/f90getopt/wiki) + ``` program f90getopt_sample + ! Sample program for f90getopt function + use f90getopt implicit none - !character:: ch ! Unused: ch=getopt() - ! START For longopts only - type(option_s):: opts(3) - opts(1) = option_s( "alpha", .false., 'a' ) - opts(2) = option_s( "beta", .true., 'b' ) - opts(3) = option_s( "help", .false., 'h') + ! START for longopts only (optional) + ! ---------------------------------- + ! option_s derived type: + ! 1st value = long option name (character array, max. 80) + ! 2nd value = if option has value (boolean) + ! 3rd value = short option name (single character), same as in getopt() + ! option_s is not needed if you just use short options + type(option_s) :: opts(3) + opts(1) = option_s("alpha", .false., "a") + opts(2) = option_s("beta", .true., "b") + opts(3) = option_s("help", .false., "h") ! END longopts + ! If no options were committed - if (command_argument_count() .eq. 0 ) then - write (*,*) "Available Options: -a. --alpha -b X --beta=X --beta X" + ! ---------------------------- + if (command_argument_count() .eq. 0) then + print*, "ERROR: Program has options: -a. --alpha -b x --beta=x --beta x" end if - ! Process options one by one + + ! START Processing options + ! ------------------------ + ! Process short options one by one and long options if specified in option_s + ! + ! getopt(optstr, longopt): + ! - optstr = character of short option character without a space + ! ":" after a character says that this option requires a value + ! - opts = longopts, if specified in option_s (optional) do - select case( getopt( "ahb:", opts ) ) ! opts is optional (for longopts only) - case( char(0) ) + select case(getopt("ab:h", opts)) + case(char(0)) exit - case( 'a' ) - print *, 'option alpha/a' - case( 'b' ) - print *, 'option beta/b=', optarg - case( 'h' ) - print *, 'help-screen' + case("a") + print*, "option alpha/a" + case("b") + print*, "option beta/b=", trim(optarg) ! "trim" is quite useful to avoid trailing spaces + case("h") + print*, "help-screen" end select end do + ! END processing options + end program f90getopt_sample ``` -Build the sample program: +### Build the sample program -Put ```f90getopt.f90``` and your sample program from above (let's say ```f90getopt-sample.f90```) in the same directory and change to this directory with the ```cd```command in the terminal (or Windows command prompt). Then type: +Put `f90getopt.f90` and your sample program from above (let's say `f90getopt_sample.f90`) in the same directory and change to this directory with the `cd` command in the terminal (or Windows command prompt). Then type: ``` gfortran f90getopt.F90 f90getopt_sample.f90 -o f90getopt_sample ``` -(you can omit `.exe` in `-o f90fetopt_sample` on Windows) +(you can omit `.exe` in `-o f90getopt_sample` on Windows) + +to compile it. -to compile it and run it with, e.g.: +### Run the sample program On Unices: @@ -89,11 +128,11 @@ Output is: option beta/b=23.2 ``` -## Compile and link to (static) library +## Compile and link to static library To avoid copying the source file `f90getopt.F90` to your working directory everytime you want to use their features, it make sense to create a static library. After, you just need to link the library to your project. To do this follow the steps below: -Change to the directory where the ```f90getopt.F90``` is located and type: +Change to the directory where the `f90getopt.F90` is located and type: ``` gfortran -c f90getopt.F90 @@ -101,7 +140,7 @@ ar cr libf90getopt.a f90getopt.o ranlib libf90getopt.a ``` -You will get a static libray called ```libf90getopt.a``` and a module file ```f90getopt.mod```. Move the a-file on UNIX(-like) systems to ```/usr/local/lib``` and the mod-file to ```/usr/local/include```: +You will get a static libray called `libf90getopt.a` and a module file `f90getopt.mod`. Move the a-file on UNIX(-like) systems to `/usr/local/lib` and the mod-file to `/usr/local/include`: ``` sudo cp ./libf90getopt.a /usr/local/lib/ @@ -113,51 +152,56 @@ On Windows go to the appropriate directory of your compiler-system. MinGW and Cy To compile and link the sample program with the libray can be done on Unices with: ``` -gfortran -o f90getopt-sample f90getopt-sample.f90 -I/usr/local/include -lf90getopt +gfortran -o f90getopt_sample f90getopt_sample.f90 -I/usr/local/include -lf90getopt ``` Change paths to match the right ones on Windows. -## Userfunction +## Userfunctions and Variables + | Type | Name | Brief Description | | --------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | -| function | getopt(shortopts-string, [longopts]) | Returns short option character & value (if applicable) of all arguments one by one | -| global variable | optarg | value of the current option, e.g. "3" when long argument = --num=3. | -| global variable | optopt | Option (single) character e.g. "b" when option = -b | +| Function | getopt(shortopts-string, [longopts]) | Returns short option character & value (if applicable) of all arguments one by one | +| Derived Type | option_s(longopt, value, shortopt) | Contains the option's long name, if value is required and option's short (character) | + Refer example code above for details. -## Differences ... -### ... from C version +## Differences + +### From C version + - when options are finished, C version returns -1 instead of char(0), and thus stupidly requires an int instead of a char. - does not support optreset - does not support "--" as last argument - if no argument, optarg is blank, not NULL - argc and argv are implicit -### ... for long options +### For long options + - optional argument to getopt(), rather than separate function getopt_long() - has_arg is logical, and does not support optional_argument - does not support flag field (and thus always returns val) - does not support longindex - knows the length of longopts, so does not need an empty last record -## Changelog +## Changelog | Version | Description | | ------- | ---------------------------------------------------------------------------------------------------------------------- | | 0.9.0 | Almost finished. Equal sign recognition in longoption (logopt=arg) is missing for v1.0 | | 1.0.0 | Added support for equal sign (=) in long options, like --beta=2.0. Error messages are displayed on stderr (not stdout) | -| 1.0.1 | Longopt bug fixed and refactoring. | -| 1.0.2 | Bug in README.md fixed | -|1.0.3 | Bug in README.md (sample code section) fixed | +| 1.0.1 | Longopt bug fixed and refactoring. | +| 1.0.2 | Bug in README.md fixed | +| 1.0.3 | Bug in README.md (sample code section) fixed | +| 1.0.4 | Portable declaration of stdin/out/err fixed, minor refactoring and documentation, => GPL 3.0, Wiki page | ## License -**GNU Public License v. 2.0** +[![License](https://img.shields.io/badge/license-GNU%20GeneraL%20Public%20License%20v3%20,%20GPLv3-blue.svg)]() Copyright (C) 2014 Hani Andreas Ibrahim -This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. diff --git a/f90getopt.F90 b/f90getopt.F90 index bc53ebf..dc4e3d7 100644 --- a/f90getopt.F90 +++ b/f90getopt.F90 @@ -1,6 +1,54 @@ -module f90getopt +! This program is free software: you can redistribute it and/or modify +! it under the terms of the GNU General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! This program is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU General Public License for more details. +! +! You should have received a copy of the GNU General Public License +! along with this program. If not, see . - implicit none +module f90getopt +! +! ================== Prologue ===================================================================================================== +! +! Purpose: +! getopt()- and getopt_long()-like functionality (similar to the +! -functions) for Fortran 2003. +! +! +! History: +! Version Programmer Date Description +! ------- ---------- ---------- ----------- +! 0.8.0 Mark Gates 2014/04/27 Original code from Gates +! 0.9.0 Hani Ibrahim 2014/04/28 Removed non-standard CLI-functions and added standard F2K CLI-functions +! 1.0.0 Hani Ibrahim 2017/01/07 Parse "=" with long options, error messages to stderr not stdout +! 1.0.1 Hani Ibrahim 2017/09/10 longopt bug fixed +! 1.0.2 Hani Ibrahim 2017/09/29 Readme.md error fixed +! 1.0.3 Hani Ibrahim 2018/07/09 Several errors in Readme.md fixed +! 1.0.4 Hani Ibrahim 2022/03/31 Portable declaration of stdin/out/err fixed, refactoring, documentation +! +! +! User routines: +! getopt +! +! Global variables/types +! option_s +! +! Special requirements: +! Fortran 2003 compiler +! +! ------------------ Use Module / Include files ----------------------------------------------------------------------------------- +! +! ------------------ Implicit ----------------------------------------------------------------------------------------------------- + IMPLICIT NONE +! ------------------ Local declarations ------------------------------------------------------------------------------------------- + PUBLIC :: getopt, option_s, optarg + PRIVATE ! all other are private (hidden) +! ------------------ Constant declarations ---------------------------------------------------------------------------------------- ! Portable declaration of stderr, stdin, stdout #ifdef f2003 @@ -13,10 +61,10 @@ module f90getopt #define stderr 0 #endif - character(len=80):: optarg ! Option's value - character :: optopt ! Option's character - integer :: optind=1 ! Index of the next argument to process - logical :: opterr=.true. ! Errors are printed by default. Set opterr=.false. to suppress them + character(len=80) :: optarg ! Option's value + character :: optopt ! Option's character + integer :: optind=1 ! Index of the next argument to process + logical :: opterr=.true. ! Errors are printed by default. Set opterr=.false. to suppress them type option_s character(len=80) :: name ! Name of the option @@ -30,18 +78,20 @@ module f90getopt contains ! ---------------------------------------- + + character function substr( str, i, j ) ! Return str(i:j) if 1 <= i <= j <= len(str), ! else return empty string. + ! ! This is needed because Fortran standard allows but doesn't *require* short-circuited ! logical AND and OR operators. So this sometimes fails: ! if ( i < len(str) .and. str(i+1:i+1) == ':' ) then ! but this works: ! if ( substr(str, i+1, i+1) == ':' ) then - character function substr( str, i, j ) ! arguments - character(len=*), intent(in):: str - integer, intent(in):: i, j + character(len=*), intent(in) :: str + integer, intent(in) :: i, j if ( 1 <= i .and. i <= j .and. j <= len(str)) then substr = str(i:j) @@ -52,13 +102,16 @@ end function substr ! ---------------------------------------- + character function getopt( optstring, longopts ) + ! Returns short option character & value (if applicable) of all arguments one by one + ! arguments - character(len=*), intent(in):: optstring - type(option_s), intent(in), optional:: longopts(:) + character(len=*), intent(in) :: optstring + type(option_s), intent(in), optional :: longopts(:) ! local variables - character(len=80):: arg + character(len=80) :: arg optarg = '' if ( optind > command_argument_count()) then @@ -77,16 +130,19 @@ end function getopt ! ---------------------------------------- + character function process_long( longopts, arg ) + ! Process long options + ! arguments - type(option_s), intent(in):: longopts(:) - character(len=*), intent(in):: arg + type(option_s), intent(in) :: longopts(:) + character(len=*), intent(in) :: arg ! local variables - integer :: i = 0 - integer :: j = 0 - integer :: len_arg = 0 ! length of arg - logical :: has_equalsign = .false. ! arg contains equal sign? + integer :: i = 0 + integer :: j = 0 + integer :: len_arg = 0 ! length of arg + logical :: has_equalsign = .false. ! arg contains equal sign? len_arg = len_trim(arg) @@ -144,12 +200,15 @@ end function process_long ! ---------------------------------------- + character function process_short( optstring, arg ) + ! Process short options + ! arguments - character(len=*), intent(in):: optstring, arg + character(len=*), intent(in) :: optstring, arg ! local variables - integer:: i, arglen + integer :: i, arglen arglen = len( trim( arg )) optopt = arg(grpind:grpind) @@ -157,7 +216,7 @@ character function process_short( optstring, arg ) i = index( optstring, optopt ) if ( i == 0 ) then - ! unrecognized option + ! unrecognised option process_short = '?' if ( opterr ) then write(stderr, '(a,a,a)') "ERROR: Unrecognized option '-", optopt, "'" @@ -179,10 +238,10 @@ character function process_short( optstring, arg ) endif grpind = 2 elseif ( arglen > grpind ) then - ! no argument (or unrecognized), go to next option in argument (-xyz) + ! no argument (or unrecognised), go to next option in argument (-xyz) grpind = grpind + 1 else - ! no argument (or unrecognized), go to next argument + ! no argument (or unrecognised), go to next argument grpind = 2 optind = optind + 1 endif