diff --git a/Makefile b/Makefile index 5ee6d20..d13e711 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,8 @@ BINDIR = build/bin LIBDIR = build/lib OBJDIR = build/obj +PREFIX ?= /usr/bin + # Directories that need to be created DIRS = $(BINDIR) $(OBJDIR) $(LIBDIR) $(SHAREDIR) @@ -62,8 +64,8 @@ GIT_DIRTY ?= $(shell test -n "$$(git status --porcelain)" && echo "-dirty") # Note: can't use ?= for FC because GNU Make defaults to f77 FC = gfortran LD = gfortran -FFLAGS += -O3 -J$(OBJDIR) -CPPFLAGS += -DREVISION=\"$(GIT_REV)$(GIT_DIRTY)\" +FFLAGS += -O3 -J$(OBJDIR) -ffree-line-length-none +CPPFLAGS += -DREVISION=\"$(GIT_REV)$(GIT_DIRTY)\" -DPREFIX=\"$(PREFIX)\" # Static build options ifdef STATIC diff --git a/src/gray_cli.f90 b/src/gray_cli.f90 new file mode 100644 index 0000000..93daa65 --- /dev/null +++ b/src/gray_cli.f90 @@ -0,0 +1,199 @@ +! This module implements the command-line interface (CLI) of GRAY +module gray_cli + + implicit none + + ! GRAY command line options + ! See print_help() for a description. + ! Note: if you change these, remember to update: + ! 1. the print_help() subroutine + ! 2. the print_cli_options() subroutine + type cli_options + ! Switches + logical :: verbose + logical :: quiet + ! Files + character(len=:), allocatable :: output_dir + character(len=:), allocatable :: params_file + character(len=:), allocatable :: sum_filelist + ! others + integer, allocatable :: units(:) + end type + + private + public :: cli_options, print_cli_options, parse_cli_options + +contains + + subroutine print_help() + ! Prints the usage screen + + print '(a)', 'Usage: gray [OPTIONS]' + print '(a)', '' + print '(a)', 'GRAY is a beam-tracing code for Electron Cyclotron (EC) waves in a tokamak.' + print '(a)', 'It is based on the complex eikonal theory (quasi-optics), which can accurately' + print '(a)', 'describe the propagation of a Gaussian beam, diffraction effects included.' + print '(a)', 'In addition, power absoption and current drive are computed using the fully' + print '(a)', 'relativistic dispersion relation and a neoclassical response function,' + print '(a)', 'respectively.' + print '(a)', '' + print '(a)', 'Options:' + print '(a)', ' -h, --help display this help and exit' + print '(a)', ' -V, --version display version information and exit' + print '(a)', ' -v, --verbose print additional information messages' + print '(a)', ' -q, --quiet suppress all messages on standard output' + print '(a)', ' -o, --output-dir DIR specify where to write the output files' + print '(a)', ' (default: current directory)' + print '(a)', ' -p, --params-file FILE set the parameters file' + print '(a)', ' (default: gray_params.data)' + print '(a)', ' -s, --sum FILE sum the output profiles from a list of files' + print '(a)', ' -u, --units ID[,ID...] select which units to output (default: 4, 7)' + print '(a)', '' + print '(a)', '*Exit status*' + print '(a)', ' 0 if OK,' + print '(a)', ' 1 if technical problems (e.g., invalid arguments, missing input files),' + print '(a)', ' 2 if serious trouble (e.g., computation failed).' + print '(a)', '' + print '(a)', 'Full documentation is available at' + print '(a)', '' + end subroutine + + + subroutine print_version() + ! Prints the version information + + print '(a)', 'gray revision ' // REVISION + print '(a)', '' + print '(a)', 'Written by D. Farina, L. Figini, A. Mariani and M. Guerini Rocco.' + end subroutine + + + subroutine print_cli_options(opts) + ! Prints the parsed CLI options (for debugging) + + implicit none + + ! subroutine arguments + type(cli_options), intent(in) :: opts + + print '(a)' , 'switches:' + print '(a,l)' , ' - verbose: ' , opts%verbose + print '(a,l)' , ' - quiet: ' , opts%quiet + print '(a)' , 'files:' + print '(a,a)' , ' output-dir: ' , opts%output_dir + print '(a,a)' , ' param-file: ' , opts%params_file + print '(a,a)' , ' sum: ' , opts%sum_filelist + print '(a)' , 'others:' + print '(a,20i3)' , ' - units: ' , opts%units + end subroutine + + + subroutine parse_cli_options(opts) + ! Parse the CLI arguments and initialise the options + + use units, only : ucenr, usumm + + implicit none + + ! subroutine arguments + type(cli_options), intent(out) :: opts + + ! local variables + character(len=:), allocatable :: argument, temp + logical :: skip_next = .false. + integer :: i, nargs + integer :: error, commas + + ! Default option values + opts%verbose = .false. + opts%quiet = .false. + opts%params_file = 'gray_params.data' + opts%units = [ucenr, usumm] + + nargs = command_argument_count() + do i = 1, nargs + call get_command_string(i, argument) + + ! skip one cycle if the last argument was a value + if (skip_next) then + skip_next = .false. + cycle + end if + + ! parse an argument (and possibly a value) + select case (argument) + case ('-h', '--help') + call print_help() + call exit(0) + + case ('-V', '--version') + call print_version() + call exit(0) + + case ('-v', '--verbose') + opts%verbose = .true. + + case ('-q', '--quiet') + opts%quiet = .true. + + case ('-o', '--output-dir') + call get_command_string(i + 1, opts%output_dir) + skip_next = .true. + + case ('-p', '--params-file') + call get_command_string(i + 1, opts%params_file) + skip_next = .true. + + case ('-s', '--sum') + call get_command_string(i + 1, opts%sum_filelist) + skip_next = .true. + + case ('-u', '--units') + call get_command_string(i + 1, temp) + + ! resize the array + commas = count([(temp(i:i) == ',', i = 1, len(temp))]) + deallocate(opts%units) + allocate(opts%units(commas + 1)) + + ! read the list of table IDs + read(temp, *, iostat=error) opts%units + if (error .gt. 0) then + print '(a,a)', 'invalid table IDs: ', temp + call exit(1) + end if + deallocate(temp) + skip_next = .true. + + case default + print '(a,a,/)', 'Unknown option: ', argument + call print_help() + call exit(1) + + end select + end do + + ! free temporary string + if (nargs .gt. 0) deallocate(argument) + end subroutine + + + subroutine get_command_string(i, arg) + ! Reads a CLI argument into a deferred-length string + + implicit none + + ! subroutine arguments + integer, intent(in) :: i + character(len=:), allocatable, intent(inout) :: arg + + ! local variables + integer :: len + + if (allocated(arg)) deallocate(arg) ! free memory (if needed) + call get_command_argument(i, length=len) ! get the arg length + allocate(character(len) :: arg) ! allocate memory + call get_command_argument(i, arg) ! copy + end subroutine + +end module gray_cli diff --git a/src/gray_params.f90 b/src/gray_params.f90 index 050b5c4..5af7fc8 100644 --- a/src/gray_params.f90 +++ b/src/gray_params.f90 @@ -240,8 +240,8 @@ contains open(u, file=filename, status='old', action='read', iostat=iostat) if (iostat > 0) then - print '(a)', 'gray_params file not found!' - call EXIT(1) + print '(3a)', 'gray_params file (', filename ,') not found!' + call exit(1) end if ! Raytracing @@ -335,6 +335,16 @@ contains read(u, *) params%output%istpr, params%output%istpl close(u) + + ! Convert all filenames to absolute paths + ! Note: `filenm` is assumed to be a path relative to the + ! gray_params.data file. + block + use utils, only : dirname + params%antenna%filenm = dirname(filename) // params%antenna%filenm + params%equilibrium%filenm = dirname(filename) // params%equilibrium%filenm + params%profiles%filenm = dirname(filename) // params%profiles%filenm + end block end subroutine read_parameters diff --git a/src/main.f90 b/src/main.f90 index 797a0c1..9995d3e 100644 --- a/src/main.f90 +++ b/src/main.f90 @@ -1,21 +1,26 @@ program main use const_and_precisions, only : wp_, one, zero + use gray_cli, only : cli_options, parse_cli_options use gray_core, only : gray_main use gray_params, only : gray_parameters, gray_data, gray_results, & read_parameters, params_set_globals => set_globals implicit none + ! CLI options + type(cli_options) :: opts + ! gray_main subroutine arguments type(gray_parameters) :: params ! Inputs type(gray_data) :: data ! type(gray_results) :: results ! Outputs integer :: error ! Exit code - logical :: sum_mode = .false. + ! Parse the command-line options + call parse_cli_options(opts) ! Load the parameters and also copy them into ! global variables exported by the gray_params - call read_parameters('gray_params.data', params) + call read_parameters(opts%params_file, params) call params_set_globals(params) ! Read the input data into set the global variables @@ -25,7 +30,15 @@ program main call init_antenna(params%antenna) call init_misc(params, data) - if (sum_mode) then + ! Change the current directory to output files here + if (allocated(opts%output_dir)) then + if (chdir(opts%output_dir) /= 0) then + print '(3a)', 'chdir to output_dir (', opts%output_dir, ') failed!' + call exit(1) + end if + end if + + if (allocated(opts%sum_filelist)) then sum: block real(wp_) :: pabs, icd, pec real(wp_), dimension(:), allocatable :: dpdv, jcd, jphi @@ -39,7 +52,7 @@ program main pins(params%output%nrho), rtin(params%output%nrho), & rpin(params%output%nrho)) - open(100, file='filelist.txt', action='read', status='old') + open(100, file=opts%sum_filelist, action='read', status='old') read(100, *) n, ngam do i=1,n read(100, *) filename diff --git a/src/utils.f90 b/src/utils.f90 index 04a3157..477b388 100644 --- a/src/utils.f90 +++ b/src/utils.f90 @@ -275,4 +275,26 @@ contains end do end function get_free_unit -end module utils \ No newline at end of file + + function dirname(filepath) result(directory) + ! Get the parent `directory` of `filepath` + + ! function arguments + character(*), intent(in) :: filepath + character(:), allocatable :: directory + + ! local variables + character(255) :: cwd + integer :: last_sep + + last_sep = scan(filepath, '/', back=.true.) + directory = filepath(1:last_sep) + + ! append the cwd to relative paths + if (filepath(1:1) /= '/') then + call getcwd(cwd) + directory = trim(cwd) // '/' // directory + end if + end function dirname + +end module utils