!/===========================================================================/
! CVS VERSION INFORMATION
! $Id$
! $Name$
! $Revision$
!/===========================================================================/
! Purpose: Library of Fortran string manipulation routines
!
! Copyright (C) 1997--2005 Charlie Zender
! License Summary: X11-style free, non-restrictive, non-copyleft. 
! Permission is hereby granted, free of charge, to any person obtaining a
! copy of this software and associated documentation files (the
! "Software"), to deal in the Software without restriction, including
! without limitation the rights to use, copy, modify, merge, publish,
! distribute, and/or sell copies of the Software, and to permit persons
! to whom the Software is furnished to do so, provided that the above
! copyright notice(s) and this permission notice appear in all copies of
! the Software and that both the above copyright notice(s) and this
! permission notice appear in supporting documentation.
! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
! OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
! MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
! OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
! HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
! INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
! FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
! NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
! WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
! Except as contained in this notice, the name of a copyright holder
! shall not be used in advertising or otherwise to promote the sale, use
! or other dealings in this Software without prior written authorization
! of the copyright holder.

! The original author of this software, Charlie Zender, wants to receive
! your suggestions, thanks, bug-reports, and patches to improve it.
! Charlie Zender <zender at uci dot edu>
! Department of Earth System Science
! University of California, Irvine
! Irvine, CA 92697-3100

! These routines contain the functionality required to parse command-line arguments
! http://www.winteracter.com/f2kcli/index.htm provides f2kcli module 
! required by some (non-UNIX) F9X compilers. F2K provides functions natively.
! A standalone test program, sng.F90, is used to demonstrate sng_mdl.F90

! Recursive I/O:
! Do not embed print statments in a function whose result is being printed

! NyL97 p. 318: POT, TIP, APT
! For formatted I/O, characters are truncated or blanks are added,
! depending on whether the field width is too small or too large.
! For input, truncation occurs on the left, and blank padding on the right;
! for output, truncation occurs on the right, and blak padding on the left

! Notes on PRC_DBL, auto-promotion, overloading:
! Assume PRC_DBL is defined when compiler is passed autopromotion flags ("-r8")
! This means all floats are promoted to double precision
! As a result, ftn_arg_get_flt() is identical to ftn_arg_get_dbl() in precision
! Thus compiler cannot disambiguate overloading these two functions
! Result is OK on all known compilers except Lahey lf95 which complains
! Solution is to define ftn_arg_get_flt() only when auto-promotion is not enabled
! Alternate solution is never use auto-promotion with lf95

! Usage:
!use sng_mdl ! [mdl] String manipulation

! Set F2K token on compilers known to support Fortran 2000/2003 intrinsics
! gfortran 4.1.2 does not recognize __GFORTRAN__ so use __GNUC__ instead
#if defined(__G95__) || defined(__GNUC__) || defined(F2KCLI)
# define F2K
#endif /* !g95 || gfortran */

module mod_sng ! [mdl] String manipulation
#ifdef F2KCLI
  ! If Fortran2000 compiler does not have command_argument_count() and 
  ! get_command_argument() intrinsics, then use Winteracter's f2kcli module
  use f2kcli ! [mdl] F2K Command-Line Interpreter (command_argument_count()...)
#endif /* !F2KCLI */
  implicit none
  public ! [stt] Symbols are public unless individually qualified as private
  private::ftn_arg_get_dbl,ftn_arg_get_int,ftn_arg_get_lgc,ftn_arg_get_sng
  private::ftn_arg_get_flt
  
  ! Overloaded command-line argument retrieval functions
  interface ftn_arg_get
     module procedure ftn_arg_get_dbl,ftn_arg_get_int,ftn_arg_get_lgc,ftn_arg_get_sng
     
     module procedure ftn_arg_get_flt
  end interface ! ftn_arg_get
  
contains
  
#ifndef F2K
  ! Declare forward-compatible F2K functions
  integer function command_argument_count() ! [fnc] Fortran intrinsic (F2K)
    ! Purpose: Return number of command-line arguments (not counting program name)
    ! Standardized F2K replacement for F77/F9X iargc()
    interface
       integer function iargc() ! [fnc] Fortran intrinsic
       end function iargc
    end interface
    command_argument_count=iargc()
    return
  end function command_argument_count
  
  subroutine get_command_argument(arg_idx,arg_val)
    ! Purpose: Return specified command-line argument
    ! Standardized F2K replacement for F77/F9X iargc()
    integer,intent(in)::arg_idx ! I [idx] Argument counter 
    character(len=*),intent(out)::arg_val ! I/O [sng] String to copy into opt_val
    interface
       subroutine getarg(idx,val) ! [fnc] Fortran intrinsic
         integer,intent(in)::idx
         character(len=*),intent(out)::val
       end subroutine getarg
    end interface
    call getarg(arg_idx,arg_val)
    return
  end subroutine get_command_argument
#endif /* F2K */

  integer function ftn_strlen(sng) ! [nbr] Length of string
    ! Purpose: Return length of string preceding first null character
    ! Fortran intrinsic len(sng) is returned if string is not NUL-terminated
    ! Prototype:
    ! integer ftn_strlen ! [nbr] Length of string
    implicit none
    ! Input
    character(len=*),intent(in)::sng
    ! Local
    ! Main Code
    ! Normally, length of string is position of first insignificant character minus one
    ftn_strlen=ftn_strfic(sng)-1 ! [idx] First NUL or 8-bit character position
    ! String length may not exceed buffer size
    if (ftn_strlen > len(sng)) ftn_strlen=len(sng)
    ! String length may not be less than 0
    ! Strings initialized to '' should have length 0 not -1
    if (ftn_strlen < 0 .or. sng == '') ftn_strlen=0
    return
  end function ftn_strlen ! end ftn_strlen()
  
  integer function ftn_opt_lng_get(sng)
    ! Purpose: Return length of option string preceding space or equals character
    ! Any preceding dashes are NOT counted towards option length
    ! Fortran intrinsic len(sng) is returned if string is not NUL-terminated
    ! Prototype:
    ! integer ftn_opt_lng_get ! [nbr] Length of option
    ! Usage:
    ! Call ftn_opt_lng_get with the full option string, e.g.,
    ! ftn_opt_lng_get('--dbg_lvl=5') and ftn_opt_lng_get will return the
    ! length of the option string 
    ! opt_sng=arg_val(3:2+ftn_opt_lng_get(arg_val)) ! [sng] Option string
    ! if (opt_sng == 'dbg') then ...
    implicit none
    ! Input
    character(len=*),intent(in)::sng
    ! Local
    integer idx
    integer len_sng
    integer::dsh_nbr=0        ! [nbr] Number of dashes
    ! Main Code
    ! Length of option string is one less than position of first space or equal sign
    len_sng=len(sng)
    if (len_sng >= 1) then
       if (sng(1:1) == '-') dsh_nbr=1 ! [nbr] Number of dashes
    endif
    if (len_sng >= 2) then
       if (sng(1:2) == '--') dsh_nbr=2 ! [nbr] Number of dashes
    endif
    do idx=1,len_sng
       ! Look for first abnormal character
       if (sng(idx:idx) == ' ' .or. sng(idx:idx) == '=') exit      
    end do                    ! end loop over characters
    ftn_opt_lng_get=idx-1-dsh_nbr
    ! Strings initialized to '' should have length 0
    if (ftn_opt_lng_get < 0 .or. sng == '') ftn_opt_lng_get=0
    return
  end function ftn_opt_lng_get                       ! end ftn_opt_lng_get()
  
  subroutine ftn_getarg_wrp( & ! [sbr] Call getarg() and increment arg_idx
       arg_idx, & ! I/O [idx] Argument counter
       arg_val) ! I/O [sng] String to copy into opt_val
    ! Purpose: Wrapper for getarg() intrinsic that increments argument counter
    ! Argument index is incremented after getarg() is called
    ! Usage:
    ! call ftn_getarg_wrp(arg_idx,arg_val) ! [sbr] Call getarg() and increment arg_idx
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Input
    ! Input/Output
    integer,intent(inout)::arg_idx ! I/O [idx] Argument counter 
    character(len=*),intent(inout)::arg_val ! I/O [sng] String to copy into opt_val
    ! Output
    ! Local
    ! Main Code
    call get_command_argument(arg_idx,arg_val)
    if (dbg_lvl >= dbg_io) write (6,'(2a,i2,2a)') prg_nm(1:ftn_strlsc(prg_nm)),   &
         ': DEBUG ftn_getarg_wrp() reports arg_idx = ',arg_idx,', arg_val = ',arg_val(1:len_trim(arg_val))
    ! Increment counter for next read
    arg_idx=arg_idx+1
    return
  end subroutine ftn_getarg_wrp
  
  subroutine ftn_getarg_err( & ! [sbr] Error handler for getarg()
       arg_idx, & ! I/O [idx] Argument counter
       arg_val) ! I/O [sng] String to copy into opt_val
    ! Purpose: Handle option-not-found errors for getopt()
    ! Routine assumes arg_idx points to arg_val
    ! Typically arg_idx is advanced before option recognition is attempted,
    ! thus arg_idx may need to be decremented before ftn_getarg_err() is called
    ! Usage:
    ! call ftn_getarg_err(arg_idx,arg_val) ! [sbr] Error handler for getarg()
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Input
    ! Input/Output
    integer,intent(inout)::arg_idx ! I/O [idx] Argument counter 
    character(len=*),intent(inout)::arg_val ! I/O [sng] String to copy into opt_val
    ! Output
    ! Local
    integer arg_nbr           ! [nbr] Number of command-line arguments
    ! integer exit_status       ! [enm] Program exit status (non-standard Fortran)
    ! Main Code
    arg_nbr=command_argument_count() ! [nbr] Number of command-line arguments
    write (6,'(2a)') prg_nm(1:ftn_strlsc(prg_nm)),': ERROR Option not recognized'
    write (6,'(2a)') prg_nm(1:ftn_strlsc(prg_nm)),': HINT Option syntax is nearly POSIX/GNU compliant:'
    write (6,'(2a)') prg_nm(1:ftn_strlsc(prg_nm)), &
         ': Short option syntax is dash-key-space-value, e.g., -D 2'
    write (6,'(3a)') prg_nm(1:ftn_strlsc(prg_nm)), &
         ': Long option syntax is dash-dash-key-space-value, e.g., --dbg 2', &
         ', or dash-dash-key-equal-value, e.g., --dbg=2 (preferred)'
    ! Subtract 1 from arg_idx because rule is that arg_idx is incremented before option recognition is attempted
    write (6,'(2a,i2,2a)') prg_nm(1:ftn_strlsc(prg_nm)),   &
         ': DEBUG ftn_getarg_err() reports arg_idx = ',arg_idx,', arg_val = ',arg_val(1:len_trim(arg_val))
    if (arg_idx+1 <= arg_nbr) then
       arg_idx=arg_idx+1      ! [idx] Counting index
       call get_command_argument(arg_idx,arg_val)
       write (6,'(2a,i2,2a)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG Next argument value is arg_val(',arg_idx,') = ',arg_val
    else
       write (6,'(2a)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG No arguments follow this one'
    endif                     ! endif not last argument
    ! exit_status=-1            ! [enm] Program exit status (non-standard Fortran)
    ! call exit(exit_status)    ! [enm] Exit with current exit status (non-standard Fortran)
    stop 'Exit on error from ftn_getarg_err()'
    return
  end subroutine ftn_getarg_err
  
  ! Notes common to all arg_get() routines:
  ! arg_val is assumed to be present as a command-line line option 
  ! On input, first two characters of arg_val are assumed to be dashes,
  ! although this would not be too difficult to change.
  ! Routine accepts options specified in POSIX format: "--opt_sng val" or "--opt_sng=val" (preferred)
  ! Case 1: Option string and value are separated by space " " conjunction
  ! On input, arg_val is assumed to hold option string with dashes, e.g., "--opt_sng"
  ! On output, arg_val holds corresponding argument, e.g., "73"
  ! Routine uses getarg() to obtain arg_val, then increments arg_idx
  ! Case 2: Option string and value are separated by equals "=" conjunction
  ! On input and output, arg_val holds entire option and value, e.g., "--opt_sng=73"
  ! Routines do not call getarg() to change arg_val and do not increment arg_idx
  ! Routines set optional logical argument opt_flg to true if called
  
  subroutine ftn_arg_get_dbl( & ! [sbr] Process double-valued command-line argument
       arg_idx, & ! I/O [idx] Argument counter
       arg_val, & ! I/O [sng] Double to copy into opt_val
       opt_val, & ! O [frc] Variable to receive copy of arg_val
       opt_flg) ! O [frc] Variable set by command-line
    ! Purpose: Copy command-line double argument arg_val into variable opt_val
    ! Usage:
    ! call ftn_arg_get_dbl(arg_idx,arg_val,opt_val) ! [sbr] Process double-valued command-line argument
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Parameters
    character(len=*),parameter::sbr_nm='ftn_arg_get_dbl' ! [sng] Subroutine name
    ! Commons
    ! Input
    ! Input/Output
    integer,intent(inout)::arg_idx ! I/O [idx] Argument counter 
    character(len=*),intent(inout)::arg_val ! I/O [sng] Double to copy into opt_val
    ! Output
    real(selected_real_kind(p=12)),intent(out)::opt_val ! O [frc] Variable to receive copy of arg_val
    logical,optional,intent(out)::opt_flg ! O [flg] Variable set by command-line
    ! Local
    integer arg_lng           ! [nbr] Length of argument
    integer opt_lng           ! [nbr] Length of option
    integer arg_val_srt_idx   ! [idx] Starting position of argument value
    logical opt_cnj_is_spc ! [flg] Option conjunction is space character
    ! Main Code
    ! Print any diagnostics before current value of arg_val is overwritten
    arg_lng=len_trim(arg_val) ! [nbr] Length of argument
    opt_lng=ftn_opt_lng_get(arg_val) ! [nbr] Length of option
    if (dbg_lvl >= dbg_io) then
       write (6,'(2a,i2,3a,i2)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() reports arg_idx = ',arg_idx,  &
            ', Full option = ''',arg_val(1:arg_lng),''', Length = ',arg_lng
    endif                     ! endif dbg
    ! Determine whether format is --opt_sng=val or --opt_sng val
    ! Add two to cover preliminary dashes
    if (opt_lng+2 < arg_lng) then
       opt_cnj_is_spc=.false. ! [flg] Option conjunction is space character
       arg_val_srt_idx=3+opt_lng+1 ! [idx] Starting position of argument value
       if (dbg_lvl >= dbg_io) then
          write (6,'(8a)') prg_nm(1:ftn_strlsc(prg_nm)), &
               ': DEBUG '//sbr_nm//'() diassembles argument into Option = ''', &
               arg_val(3:2+opt_lng),''', conjunction is ''', &
               arg_val(3+opt_lng:3+opt_lng),''', argument = ''', &
               arg_val(arg_val_srt_idx:arg_lng),'''' ! Quote syntax is mystical
       endif                  ! endif dbg
    else
       opt_cnj_is_spc=.true.  ! [flg] Option conjunction is space character
       arg_val_srt_idx=1      ! [idx] Starting position of argument value
    endif                     ! endif
    if (opt_cnj_is_spc) then 
       ! Obtain value of option by getting next command-line argument
       ! New arguments will alter input values of arg_idx,arg_val
       call get_command_argument(arg_idx,arg_val)
       ! Increment counter for next read
       arg_idx=arg_idx+1
       ! Sanity checks
       arg_lng=len_trim(arg_val) ! [nbr] Length of argument
       if (arg_lng <= 0) stop 'ftn_???_arg_get() reports option lacks argument'
    endif                     ! endif opt_cnj_is_spc
    ! Read argument into double and return
    read (arg_val(arg_val_srt_idx:arg_lng),*) opt_val ! [frc] Variable to receive copy of arg_val
    if (dbg_lvl >= dbg_io) then
       write (6,'(2a,g13.6)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() assigned argument value = ',opt_val
    endif                     ! endif
    if(present(opt_flg)) opt_flg=.true. ! [flg] Variable set by command-line
    return
  end subroutine ftn_arg_get_dbl
  
  subroutine ftn_arg_get_flt( & ! [sbr] Process float-valued command-line argument
       arg_idx, & ! I/O [idx] Argument counter
       arg_val, & ! I/O [sng] Float to copy into opt_val
       opt_val, & ! O [frc] Variable to receive copy of arg_val
       opt_flg) ! O [frc] Variable set by command-line
    ! Purpose: Copy command-line float argument arg_val into variable opt_val
    ! Usage:
    ! call ftn_arg_get_flt(arg_idx,arg_val,opt_val) ! [sbr] Process float-valued command-line argument
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Parameters
    character(len=*),parameter::sbr_nm='ftn_arg_get_flt' ! [sng] Subroutine name
    ! Commons
    ! Input
    ! Input/Output
    integer,intent(inout)::arg_idx ! I/O [idx] Argument counter 
    character(len=*),intent(inout)::arg_val ! I/O [sng] Float to copy into opt_val
    ! Output
    real(selected_real_kind(p=6)),intent(out)::opt_val ! O [frc] Variable to receive copy of arg_val
    logical,optional,intent(out)::opt_flg ! O [flg] Variable set by command-line
    ! Local
    integer arg_lng           ! [nbr] Length of argument
    integer opt_lng           ! [nbr] Length of option
    integer arg_val_srt_idx   ! [idx] Starting position of argument value
    logical opt_cnj_is_spc ! [flg] Option conjunction is space character
    ! Main Code
    ! Print any diagnostics before current value of arg_val is overwritten
    arg_lng=len_trim(arg_val) ! [nbr] Length of argument
    opt_lng=ftn_opt_lng_get(arg_val) ! [nbr] Length of option
    if (dbg_lvl >= dbg_io) then
       write (6,'(2a,i2,3a,i2)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() reports arg_idx = ',arg_idx,  &
            ', Full option = ''',arg_val(1:arg_lng),''', Length = ',arg_lng
    endif                     ! endif dbg
    ! Determine whether format is --opt_sng=val or --opt_sng val
    ! Add two to cover preliminary dashes
    if (opt_lng+2 < arg_lng) then
       opt_cnj_is_spc=.false. ! [flg] Option conjunction is space character
       arg_val_srt_idx=3+opt_lng+1 ! [idx] Starting position of argument value
       if (dbg_lvl >= dbg_io) then
          write (6,'(8a)') prg_nm(1:ftn_strlsc(prg_nm)), &
               ': DEBUG '//sbr_nm//'() diassembles argument into Option = ''', &
               arg_val(3:2+opt_lng),''', conjunction is ''', &
               arg_val(3+opt_lng:3+opt_lng),''', argument = ''', &
               arg_val(arg_val_srt_idx:arg_lng),'''' ! Quote syntax is mystical
       endif                  ! endif dbg
    else
       opt_cnj_is_spc=.true.  ! [flg] Option conjunction is space character
       arg_val_srt_idx=1      ! [idx] Starting position of argument value
    endif                     ! endif
    if (opt_cnj_is_spc) then 
       ! Obtain value of option by getting next command-line argument
       ! New arguments will alter input values of arg_idx,arg_val
       call get_command_argument(arg_idx,arg_val)
       ! Increment counter for next read
       arg_idx=arg_idx+1
       ! Sanity checks
       arg_lng=len_trim(arg_val) ! [nbr] Length of argument
       if (arg_lng <= 0) stop 'ftn_???_arg_get() reports option lacks argument'
    endif                     ! endif opt_cnj_is_spc
    ! Read argument into float and return
    read (arg_val(arg_val_srt_idx:arg_lng),*) opt_val ! [frc] Variable to receive copy of arg_val
    if (dbg_lvl >= dbg_io) then
       write (6,'(2a,g13.6)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() assigned argument value = ',opt_val
    endif                     ! endif
    if(present(opt_flg)) opt_flg=.true. ! [flg] Variable set by command-line
    return
  end subroutine ftn_arg_get_flt
  
  subroutine ftn_arg_get_int( & ! [sbr] Process integer-valued command-line argument
       arg_idx, & ! I/O [idx] Argument counter
       arg_val, & ! I/O [sng] Integer to copy into opt_val
       opt_val, & ! O [nbr] Variable to receive copy of arg_val
       opt_flg) ! O [frc] Variable set by command-line
    ! Purpose: Copy command-line integer argument arg_val into variable opt_val
    ! Usage:
    ! call ftn_arg_get_int(arg_idx,arg_val,opt_val) ! [sbr] Process integer-valued command-line argument
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Parameters
    character(len=*),parameter::sbr_nm='ftn_arg_get_int' ! [sng] Subroutine name
    ! Commons
    ! Input
    ! Input/Output
    integer,intent(inout)::arg_idx ! I/O [idx] Argument counter 
    character(len=*),intent(inout)::arg_val ! I/O [sng] Integer to copy into opt_val
    ! 20030429: Change opt_val to inout since dbg_lvl itself may be an opt_val but is referenced on LHS
    integer,intent(inout)::opt_val ! O [nbr] Variable to receive copy of arg_val
    logical,optional,intent(out)::opt_flg ! O [flg] Variable set by command-line
    ! Output
    ! Local
    integer arg_lng           ! [nbr] Length of argument
    integer opt_lng           ! [nbr] Length of option
    integer arg_val_srt_idx   ! [idx] Starting position of argument value
    logical opt_cnj_is_spc ! [flg] Option conjunction is space character
    ! Main Code
    ! Print any diagnostics before current value of arg_val is overwritten
    arg_lng=len_trim(arg_val) ! [nbr] Length of argument
    opt_lng=ftn_opt_lng_get(arg_val) ! [nbr] Length of option
    if (dbg_lvl >= dbg_io) then
       write (6,'(2a,i2,3a,i2)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() reports arg_idx = ',arg_idx,  &
            ', Full option = ''',arg_val(1:arg_lng),''', Length = ',arg_lng
    endif                     ! endif dbg
    ! Determine whether format is --opt_sng=val or --opt_sng val
    ! Add two to cover preliminary dashes
    if (opt_lng+2 < arg_lng) then
       opt_cnj_is_spc=.false. ! [flg] Option conjunction is space character
       arg_val_srt_idx=3+opt_lng+1 ! [idx] Starting position of argument value
       if (dbg_lvl >= dbg_io) then
          write (6,'(8a)') prg_nm(1:ftn_strlsc(prg_nm)), &
               ': DEBUG '//sbr_nm//'() diassembles argument into Option = ''', &
               arg_val(3:2+opt_lng),''', conjunction is ''', &
               arg_val(3+opt_lng:3+opt_lng),''', argument = ''', &
               arg_val(arg_val_srt_idx:arg_lng),'''' ! Quote syntax is mystical
       endif                  ! endif dbg
    else
       opt_cnj_is_spc=.true.  ! [flg] Option conjunction is space character
       arg_val_srt_idx=1      ! [idx] Starting position of argument value
    endif                     ! endif
    if (opt_cnj_is_spc) then 
       ! Obtain value of option by getting next command-line argument
       ! New arguments will alter input values of arg_idx,arg_val
       call get_command_argument(arg_idx,arg_val)
       ! Increment counter for next read
       arg_idx=arg_idx+1
       ! Sanity checks
       arg_lng=len_trim(arg_val) ! [nbr] Length of argument
       if (arg_lng <= 0) stop 'ftn_arg_get_int() reports option lacks argument'
    endif                     ! endif opt_cnj_is_spc
    ! Read argument into integer and return
    read (arg_val(arg_val_srt_idx:arg_lng),*) opt_val ! [nbr] Variable to receive copy of arg_val
    if (dbg_lvl >= dbg_io) then
       write (6,'(2a,g13.6)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() assigned argument value = ',opt_val
    endif                     ! endif
    if(present(opt_flg)) opt_flg=.true. ! [flg] Variable set by command-line
    return
  end subroutine ftn_arg_get_int
  
  subroutine ftn_arg_get_lgc( & ! [sbr] Process logical-valued command-line argument
       arg_idx, & ! I/O [idx] Argument counter
       arg_val, & ! I/O [sng] Logical to copy into opt_val
       opt_val, & ! O [flg] Variable to receive copy of arg_val
       opt_flg) ! O [frc] Variable set by command-line
    ! Purpose: Copy command-line logical argument arg_val into variable opt_val
    ! Usage:
    ! call ftn_arg_get_lgc(arg_idx,arg_val,opt_val) ! [sbr] Process logical-valued command-line argument
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Parameters
    character(len=*),parameter::sbr_nm='ftn_arg_get_lgc' ! [sng] Subroutine name
    ! Commons
    ! Input
    ! Input/Output
    integer,intent(inout)::arg_idx ! I/O [idx] Argument counter 
    character(len=*),intent(inout)::arg_val ! I/O [sng] Logical to copy into opt_val
    ! Output
    logical,intent(out)::opt_val ! O [flg] Variable to receive copy of arg_val
    logical,optional,intent(out)::opt_flg ! O [flg] Variable set by command-line
    ! Local
    integer arg_lng           ! [nbr] Length of argument
    integer opt_lng           ! [nbr] Length of option
    integer arg_val_srt_idx   ! [idx] Starting position of argument value
    logical opt_cnj_is_spc ! [flg] Option conjunction is space character
    ! Main Code
    ! Print any diagnostics before current value of arg_val is overwritten
    arg_lng=len_trim(arg_val) ! [nbr] Length of argument
    opt_lng=ftn_opt_lng_get(arg_val) ! [nbr] Length of option
    if (dbg_lvl >= dbg_io) then
       write (6,'(2a,i2,3a,i2)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() reports arg_idx = ',arg_idx,  &
            ', Full option = ''',arg_val(1:arg_lng),''', Length = ',arg_lng
    endif                     ! endif dbg
    ! Determine whether format is --opt_sng=val or --opt_sng val
    ! Add two to cover preliminary dashes
    if (opt_lng+2 < arg_lng) then
       opt_cnj_is_spc=.false. ! [flg] Option conjunction is space character
       arg_val_srt_idx=3+opt_lng+1 ! [idx] Starting position of argument value
       if (dbg_lvl >= dbg_io) then
          write (6,'(8a)') prg_nm(1:ftn_strlsc(prg_nm)), &
               ': DEBUG '//sbr_nm//'() diassembles argument into Option = ''', &
               arg_val(3:2+opt_lng),''', conjunction is ''', &
               arg_val(3+opt_lng:3+opt_lng),''', argument = ''', &
               arg_val(arg_val_srt_idx:arg_lng),'''' ! Quote syntax is mystical
       endif                  ! endif dbg
    else
       opt_cnj_is_spc=.true.  ! [flg] Option conjunction is space character
       arg_val_srt_idx=1      ! [idx] Starting position of argument value
    endif                     ! endif
    if (opt_cnj_is_spc) then 
       ! Obtain value of option by getting next command-line argument
       ! New arguments will alter input values of arg_idx,arg_val
       call get_command_argument(arg_idx,arg_val)
       ! Increment counter for next read
       arg_idx=arg_idx+1
       ! Sanity checks
       arg_lng=len_trim(arg_val) ! [nbr] Length of argument
       if (arg_lng <= 0) stop 'ftn_arg_get_lgc() reports option lacks argument'
    endif                     ! endif opt_cnj_is_spc
    ! Read argument into integer and return
    read (arg_val(arg_val_srt_idx:arg_lng),*) opt_val ! [flg] Variable to receive copy of arg_val
    if (dbg_lvl >= dbg_io) then
       write (6,'(2a,g13.6)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() assigned argument value = ',opt_val
    endif                     ! endif
    if(present(opt_flg)) opt_flg=.true. ! [flg] Variable set by command-line
    return
  end subroutine ftn_arg_get_lgc
  
  subroutine ftn_arg_get_sng( & ! [sbr] Process string-valued command-line argument
       arg_idx, & ! I/O [idx] Argument counter
       arg_val, & ! I/O [sng] String to copy into opt_val
       opt_val, & ! O [sng] Variable to receive copy of arg_val
       opt_flg) ! O [frc] Variable set by command-line
    ! Purpose: Copy command-line string argument arg_val into variable opt_val
    ! opt_val is unchanged if arg_val cannot be copied
    ! Remainder of opt_val is NUL-initialized
    ! Usage:
    ! call ftn_arg_get_sng(arg_idx,arg_val,opt_val) ! [sbr] Process string-valued command-line argument
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Parameters
    character(len=*),parameter::sbr_nm='ftn_arg_get_sng' ! [sng] Subroutine name
    ! Commons
    ! Input
    ! Input/Output
    integer,intent(inout)::arg_idx ! I/O [idx] Argument counter 
    character(len=*),intent(inout)::arg_val ! I/O [sng] String to copy into opt_val
    ! Output
    character(len=*),intent(out)::opt_val ! O [sng] Variable to receive copy of arg_val
    logical,optional,intent(out)::opt_flg ! O [flg] Variable set by command-line
    ! Local
    integer idx
    integer len_var           ! [nbr] Length of string variable
    integer arg_lng           ! [nbr] Length of argument
    integer opt_lng           ! [nbr] Length of option
    integer arg_val_srt_idx   ! [idx] Starting position of argument value
    logical opt_cnj_is_spc ! [flg] Option conjunction is space character
    integer nll_srt_idx ! [idx] First NUL position in opt_val
    ! Main Code
    ! Print any diagnostics before current value of arg_val is overwritten
    len_var=len(opt_val)      ! [nbr] Length of string variable
    arg_lng=len_trim(arg_val) ! [nbr] Length of argument
    opt_lng=ftn_opt_lng_get(arg_val) ! [nbr] Length of option
    if (dbg_lvl >= dbg_io) then
       write (6,'(2a,i2,3a,i2)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() reports arg_idx = ',arg_idx,  &
            ', Option = ''',arg_val(1:arg_lng),''', Length = ',arg_lng
    endif                     ! endif
    ! Determine whether format is --opt_sng=val or --opt_sng val
    ! Add two to cover preliminary dashes
    if (opt_lng+2 < arg_lng) then
       opt_cnj_is_spc=.false. ! [flg] Option conjunction is space character
       arg_val_srt_idx=3+opt_lng+1 ! [idx] Starting position of argument value
       if (dbg_lvl >= dbg_io) then
          write (6,'(8a)') prg_nm(1:ftn_strlsc(prg_nm)), &
               ': DEBUG '//sbr_nm//'() diassembles argument into Option = ''', &
               arg_val(3:2+opt_lng),''', conjunction is ''', &
               arg_val(3+opt_lng:3+opt_lng),''', argument = ''', &
               arg_val(arg_val_srt_idx:arg_lng),'''' ! Quote syntax is mystical
       endif                  ! endif dbg
    else
       opt_cnj_is_spc=.true.  ! [flg] Option conjunction is space character
       arg_val_srt_idx=1      ! [idx] Starting position of argument value
    endif                     ! endif
    if (opt_cnj_is_spc) then 
       ! Obtain value of option by getting next command-line argument
       ! New arguments will alter input values of arg_idx,arg_val
       call get_command_argument(arg_idx,arg_val)
       ! Increment counter for next read
       arg_idx=arg_idx+1
       ! Sanity checks
       arg_lng=len_trim(arg_val) ! [nbr] Length of argument
       if (arg_lng <= 0) stop 'ftn_???_arg_get() reports option lacks argument'
       if (arg_lng > len_var) then
          write (6,'(2a,i5,a,i5)') prg_nm(1:ftn_strlsc(prg_nm)), &
               ': ERROR '//sbr_nm//'() reports argument length = ',arg_lng, &
               ' too long to fit into variable length ',len_var
          stop
       endif ! endif arg_lng > len_var
    endif                     ! endif opt_cnj_is_spc
    ! Read argument into string and return
    read (arg_val(arg_val_srt_idx:arg_lng),'(a)') opt_val ! [sng] Variable to receive copy of arg_val
    ! NUL-initialize remainder of opt_val
    nll_srt_idx=arg_lng-arg_val_srt_idx+2 ! [idx] First NUL position in opt_val
    do idx=nll_srt_idx,len_var
       opt_val(idx:idx)=char(0) ! [sng] Variable to receive copy of arg_val
    end do                    ! end loop over characters
    if (dbg_lvl >= dbg_io) then
       write (6,'(4a)') prg_nm(1:ftn_strlsc(prg_nm)), &
            ': DEBUG '//sbr_nm//'() assigned argument value = ',opt_val(1:len_trim(opt_val)),'<--End of opt_val string'
       call ftn_strprn(opt_val)   ! [fnc] Print character values of string
    endif ! endif
    if(present(opt_flg)) opt_flg=.true. ! [flg] Variable set by command-line
    return
  end subroutine ftn_arg_get_sng
  
  integer function ftn_strstr(sng1,sng2) ! [idx] Location of sng2 in sng1
    ! Purpose: Return the location of second string in the first
    ! Fortran intrinsic len(sng) is returned if string is not NUL-terminated
    ! Return -1 if sng1 does not contain sng1
    ! Prototype:
    ! integer ftn_strstr ! [idx] Location of sng1 in sng2
    implicit none
    ! Input
    character(len=*),intent(in)::sng1
    character(len=*),intent(in)::sng2
    ! Local
    integer lng1
    integer lng2
    integer idx1
    integer idx2
    ! Initialize
    idx2=0 ! CEWI for lf95
    ! Main Code
    lng1=ftn_strlen(sng1) ! [nbr] Length of string
    lng2=ftn_strlen(sng2) ! [nbr] Length of string
    outloop: do idx1=1,lng1    
       if (sng1(idx1:idx1) == sng2(1:1)) then
          do idx2=2,lng2
             if (sng1(idx1+idx2-1:idx1+idx2-1) /= sng2(idx2:idx2)) cycle outloop    
          end do              ! end loop over characters
          exit outloop    
       endif                  ! endif
    end do outloop       

    if (idx2 == lng2+1) then
       ftn_strstr=idx1
    else
       ftn_strstr=-1
    endif ! endif
    return
  end function ftn_strstr
  
  subroutine ftn_strini(sng) ! [sng] sng(1:len)=NUL
    ! Purpose: Initialize all elements of a character array to char(0)
    ! Usage:
    ! call ftn_strini(sng) ! [sng] sng(1:len)=NUL
    implicit none
    ! Input/Output
    character(len=*),intent(out)::sng ! I/O [sng] String to initialize
    ! Local
    integer idx
    integer lng
    ! Main Code
    lng=len(sng)
    do idx=1,lng
       sng(idx:idx)=char(0)
    end do                    ! end loop over characters
    ! write (6,*) 'ftn_strini(): Initialized string of length',lng
    return
  end subroutine ftn_strini
  
  subroutine ftn_strnul(sng) ! [sbr] NUL-initialize all characters after LSC
    ! Purpose: Change space characters to NUL characters in a string
    ! Spaces after last significant character (LSC) in string are changed to NUL
    ! Spaces before LSC are not affected
    ! Usage:
    ! call ftn_strnul(sng) ! [sbr] NUL-initialize all characters after LSC
    implicit none
    ! Input/Output
    character(len=*),intent(inout)::sng ! I/O [sng] String to NUL-initialize
    ! Local
    integer idx
    integer lng
    integer lst_sgn_chr_idx
    ! Main Code
    lng=ftn_strlen(sng) ! [nbr] Length of string
    lst_sgn_chr_idx=ftn_strlsc(sng)
    do idx=lst_sgn_chr_idx+1,lng
       if(sng(idx:idx) == ' ') sng(idx:idx)=char(0)
    end do                    ! end loop over characters
    return
  end subroutine ftn_strnul
  
  integer function ftn_strlsc(sng)
    ! Purpose: Return position of last significant character (LSC) in string
    ! LSC is last character that is not a space or a NUL character
    ! Routine is useful in determining effective lengths of strings which
    ! may have been padded by compiler or shell.
    ! Automatic padding is always done with spaces or NULs, so this routine
    ! tells us how long the non-padded portion of the string is.
    ! Routine will mis-handle user-defined strings that end in spaces
    ! LSC is always <= len(sng)
    ! Usage:
    ! psn=ftn_strlsc(sng) ! [idx] Last significant character position
    ! Prototype:
    ! integer ftn_strlsc ! [idx] Last significant character position
    implicit none
    ! Input
    character(len=*),intent(in)::sng
    ! Local
    integer idx
    integer lng
    ! Main Code
    lng=len(sng)
    do idx=lng,1,-1
       ! Look for last normal character
       if (sng(idx:idx) /= ' '.and.sng(idx:idx) /= char(0)) exit   
    end do                    ! end loop over characters
    ftn_strlsc=idx
    return
  end function ftn_strlsc
  
  integer function ftn_strfic(sng) ! [idx] First NUL or 8-bit character position
    ! Purpose: Return position of first insignificant character (FIC) in string
    ! FIC is first NUL character in string, or first 8-bit character, i.e., iachar > 127
    ! FIC is len(sng)+1 if string has no NUL character and no iachar > 127
    ! Usage:
    ! psn=ftn_strfic(sng) ! [idx] First NUL or 8-bit character position
    ! Prototype:
    ! integer ftn_strfic ! [idx] First NUL or 8-bit character position
    implicit none
    ! Input
    character(len=*),intent(in)::sng ! I [sng] String to examine
    ! Local
    integer idx
    integer lng
    ! Main Code
    lng=len(sng)
    do idx=1,lng
       ! Look for first abnormal character
       if (sng(idx:idx) == char(0) .or. iachar(sng(idx:idx)) > 127) exit    
    end do                    ! end loop over characters
    ftn_strfic=idx ! [idx] First NUL or 8-bit character position
    return
  end function ftn_strfic
  
  subroutine ftn_strprn(sng) ! [fnc] Print character values of string
    ! Purpose: Print character values of string
    ! Usage:
    ! call ftn_strprn(sng) ! [fnc] Print character values of string
    implicit none
    ! Input
    character(len=*),intent(in)::sng
    ! Local
    character(1) chr_crr ! [sng] Current character
    integer idx
    integer lng
    integer iachr_crr ! [enm] Integer representing current character
    ! Main Code
    lng=len(sng)
    do idx=1,lng
       ! Print each normal character
       chr_crr=sng(idx:idx) ! [sng] Current character
       iachr_crr=iachar(chr_crr) ! [enm] Integer representing current character
       if (idx < lng) then
          if (iachr_crr == 0) then
             write (6,'(a3,a3,i3,a2)',advance="no") 'NUL',' = ',iachr_crr,', '
          else if (iachr_crr == 32) then
             write (6,'(a5,a3,i3,a2)',advance="no") 'SPACE',' = ',iachr_crr,', '
          else
             write (6,'(a1,a3,i3,a2)',advance="no") chr_crr,' = ',iachr_crr,', '
          endif               ! endif
       else
          if (iachr_crr == 0) then
             write (6,'(a3,a3,i3)') 'NUL',' = ',iachr_crr,', '
          else if (iachr_crr == 32) then
             write (6,'(a5,a3,i3)') 'SPACE',' = ',iachr_crr,', '
          else
             write (6,'(a1,a3,i3)') chr_crr,' = ',iachr_crr,', '
          endif               ! endif
       endif                  ! endif
    end do                    ! end loop over characters
    return
  end subroutine ftn_strprn
  
  subroutine ftn_strcpy(sng1,sng2)
    ! Purpose: Copy sng2 into sng1
    ! Space remaining at end of sng1 is NUL-initialized
    ! This function works well at copying fixed strings into variables, e.g.,
    ! Usage:
    ! call ftn_strcpy(sng,'Hello World') ! [fnc] Copy sng2 into sng1, NUL-terminate unused space
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Input
    character(len=*),intent(in)::sng2
    ! Input/Output
    character(len=*),intent(inout)::sng1
    ! Local
    integer len1
    integer len2
    integer lng1
    integer lng2
    ! Main Code
    len1=len(sng1)
    len2=len(sng2)
    ! Initialize sng1 before diagnostic ftn_strlen(sng1) is evaluated
    ! This handles situation when sng1 is large enough, but not-initialized in calling routine
    ! Otherwise some compilers, e.g., lf95 in debug mode, may complain in ftn_strlen(sng1)->ftn_strfic()
    ! when they are unable to determine string length
    ! Initialize sng1 before copying sng2 into sng1 WLOG since ftn_strcpy() behavior
    ! is intended to mimic C library strcpy() which NUL-terminates the copy, and thus
    ! all material after the NUL may be assumed to be undefined, i.e., set to NUL.
    ! This is not equivalent to calling ftn_strnul(sng1) after the copy since when sng1 is undefined
    ! There could be corner cases where contents of undefined sng1 character immediately
    ! following copy of sng2 is not NUL or 8-bit. Results are unpredicatable in that case.
    ! Hence initialize sng1 to NUL before using ftn_strlen(sng1) and before copying sng2
    call ftn_strini(sng1) ! [sng] sng(1:len)=NUL
    lng1=ftn_strlen(sng1) ! [nbr] Length of string
    lng2=ftn_strlen(sng2) ! [nbr] Length of string
    if (len1 < lng2) then
       write (6,'(a,a)') prg_nm(1:ftn_strlen(prg_nm)),': ERROR len1 < lng2 in ftn_strcpy()'
       write (6,'(a,i3,a,i3,2a)') 'len1 = ',len1,', lng1 = ',lng1,', sng1 = ',sng1
       write (6,'(a,i3,a,i3,2a)') 'len2 = ',len2,', lng2 = ',lng2,', sng2 = ',sng2
       stop 'EXIT_FAILURE from ftn_strcpy()'
    endif                     ! endif
    sng1(1:lng2)=sng2(1:lng2)
    return
  end subroutine ftn_strcpy
  
  integer function ftn_strcmp(sng1,sng2) ! [fnc] Compare sng1 to sng2: -1,0,1 iff sng1 <,=,> sng2
    ! Purpose: Compare sng2 to sng1
    ! Returns an integer less than, equal to, or greater than
    ! zero  if  sng1  is  found, respectively, to be less than, to
    ! match, or be greater than sng2.
    ! Usage:
    ! if(ftn_strcmp(sng1,"Hello World")==0) then ! [fnc] Compare sng1 to sng2: -1,0,1 iff sng1 <,=,> sng2
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Input
    character(len=*),intent(in)::sng1
    character(len=*),intent(in)::sng2
    ! Input/Output
    ! Local
    integer idx               ! [idx] Counting idx
    integer len1
    integer len2
    integer lng1
    integer lng2
    integer lng_min
    ! Main Code
    len1=len(sng1)
    len2=len(sng2)
    lng1=ftn_strlen(sng1) ! [nbr] Length of string
    lng2=ftn_strlen(sng2) ! [nbr] Length of string
    if (.false.) then 
       ! If this debugging block is turned on then attempting to print,ftn_strcmp()
       ! will cause a recursive I/O error at runtime
       write (6,'(2a)') prg_nm(1:ftn_strlen(prg_nm)),': DEBUG Diagnostics from ftn_strcmp()'
       write (6,'(2(a,i3),2a)') 'len1 = ',len1,', lng1 = ',lng1,', sng1 = ',sng1
       write (6,'(2(a,i3),2a)') 'len2 = ',len2,', lng2 = ',lng2,', sng2 = ',sng2
       call ftn_strprn(sng1) ! [fnc] Print character values of string
       call ftn_strprn(sng2) ! [fnc] Print character values of string
    endif                     ! endif
    lng_min=min(lng1,lng2)
    do idx=1,lng_min
       ! Prevent memory overruns
       ! Characters are equal, proceed to next character
       if (iachar(sng1(idx:idx)) == iachar(sng2(idx:idx))) cycle ! Goto next iteration
       ! Characters are unequal, determine relationship
       if (iachar(sng1(idx:idx)) < iachar(sng2(idx:idx))) then
          ftn_strcmp=-1    ! [fnc] Compare sng1 to sng2: -1,0,1 iff sng1 <,=,> sng2
       else                ! (iachar(sng1(idx:idx)) > iachar(sng2(idx:idx)))
          ftn_strcmp=1     ! [fnc] Compare sng1 to sng2: -1,0,1 iff sng1 <,=,> sng2
       endif               ! endif
       
       ! Characters were unequal, exit with appropriate value
       return            ! Exit Branch     
    end do
    
    if (lng1 == lng2) then
       ! All significant values of both strings were equal in all positions
       ! Thus the strings are equal by our criterion
       ftn_strcmp=0           ! [fnc] Compare sng1 to sng2: -1,0,1 iff sng1 <,=,> sng2
    else
       ! All significant values of both strings were equal in all mutually legal positions
       ! However, one string has more significant characters than the other
       ! Thus return value depends on which string is longer
       if (lng1 > lng2) then
          ftn_strcmp=1        ! [fnc] Compare sng1 to sng2: -1,0,1 iff sng1 <,=,> sng2
       else 
          ftn_strcmp=-1       ! [fnc] Compare sng1 to sng2: -1,0,1 iff sng1 <,=,> sng2
       endif                  ! endif
    endif ! endif lng1 == lng2
    
    return
  end function ftn_strcmp
  
  subroutine ftn_strcpylsc(sng1,sng2)
    ! Purpose: Copy sng2 into sng1
    ! Space remaining at end of sng1 is NUL-initialized
    ! This function works well at copying fixed strings into variables, e.g.,
    ! Usage:
    ! call ftn_strcpylsc(sng,'Hello World') ! [fnc] Copy up to LSC of sng2 into sng1, NUL-initialize unused space
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Input
    character(len=*),intent(in)::sng2
    ! Input/Output
    character(len=*),intent(inout)::sng1
    ! Local
    integer idx               ! [idx] Counting idx
    integer len1
    integer len2
    integer lsc1
    integer lsc2
    ! Main Code
    len1=len(sng1)
    len2=len(sng2)
    lsc1=ftn_strlsc(sng1)
    lsc2=ftn_strlsc(sng2)
    if (len1 < lsc2) then
       write (6,'(a,a)') prg_nm(1:ftn_strlsc(prg_nm)),': ERROR len1 < lsc2 in ftn_strcpylsc()'
       write (6,'(a,i3,a,i3,2a)') 'len1 = ',len1,', lsc1 = ',lsc1,', sng1 = ',sng1
       write (6,'(a,i3,a,i3,2a)') 'len2 = ',len2,', lsc2 = ',lsc2,', sng2 = ',sng2
       stop 'EXIT_FAILURE from ftn_strcpylsc()'
    endif                     ! endif
    sng1(1:lsc2)=sng2(1:lsc2)
    ! NUL-initialize rest of string
    do idx=lsc2+1,len1
       sng1(idx:idx)=char(0)
    end do                    ! end loop over characters
    return
  end subroutine ftn_strcpylsc
  
  subroutine ftn_strcat( &   ! [fnc] sng1 := sng1 // sng2
       sng1, &                ! I/O [sng] String to affix second string to
       sng2)                ! I [sng] String to affix to first string
    ! Purpose: sng1 := sng1 // sng2
    ! The case where sng1=sng2 is handled correctly
    ! Usage:
    ! call ftn_strcat(sng1,sng2) ! [fnc] sng1 := sng1 // sng2
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Input
    character(len=*),intent(in)::sng2 ! I [sng] String to affix to first string
    ! Input/Output
    character(len=*),intent(inout)::sng1 ! I/O [sng] String to affix second string to
    ! Local
    integer len1
    integer len2
    integer lng1
    integer lng2
    ! Main Code
    len1=len(sng1)
    len2=len(sng2)
    lng1=ftn_strlen(sng1) ! [nbr] Length of string
    lng2=ftn_strlen(sng2) ! [nbr] Length of string
    if (lng1+lng2 >= len1) then
       write (6,'(a,a)') prg_nm(1:ftn_strlen(prg_nm)),': ERROR lng1+lng2 >= len1 in ftn_strcat()'
       write (6,'(a,i3,a,i3,2a)') 'len1 = ',len1,', lng1 = ',lng1,', sng1 = ',sng1
       write (6,'(a,i3,a,i3,2a)') 'len2 = ',len2,', lng2 = ',lng2,', sng2 = ',sng2
       stop 'EXIT_FAILURE from ftn_strcat()'
    endif                     ! endif
    sng1(lng1+1:lng1+lng2)=sng2(1:lng2) ! I/O [sng] String to affix second string to
    return
  end subroutine ftn_strcat
  
  subroutine ftn_strpfx(sng1,sng2) ! [sbr] sng2 := sng1 // sng2
    ! Purpose: sng2 := sng1 // sng2
    ! Differences with ftn_strcat(): 
    ! 1. Result is stored in second string not first string
    ! 2. Strings are trimmed before concatenation
    ! The case where sng1=sng2 is handled correctly
    ! Usage:
    ! call ftn_strpfx(sng1,sng2) ! [sbr] sng2 := sng1 // sng2
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Input
    character(len=*),intent(in)::sng1
    ! Output
    character(len=*),intent(inout)::sng2
    ! Local
    integer len1
    integer len2
    integer len_trim1
    integer len_trim2
    integer lng1
    integer lng2
    integer lsc1
    integer lsc2
    ! Main Code
    len1=len(sng1)
    len2=len(sng2)
    lng1=ftn_strlen(sng1) ! [nbr] Length of string
    lng2=ftn_strlen(sng2) ! [nbr] Length of string
    lsc1=ftn_strlsc(sng1)
    lsc2=ftn_strlsc(sng2)
    len_trim1=len_trim(sng1)
    len_trim2=len_trim(sng2)
    if (lsc1+lsc2 >= len2) then
       write (6,'(a,a)') prg_nm(1:ftn_strlen(prg_nm)),': ERROR lsc1+lsc2 >= len2 in ftn_strpfx()'
       write (6,'(4(a,i3),2a)') 'len1 = ',len1,', len_trim1 = ',len_trim1,', lng1 = ',lng1,', sng1 = ',sng1
       write (6,'(4(a,i3),2a)') 'len2 = ',len2,', len_trim2 = ',len_trim2,', lng2 = ',lng2,', sng2 = ',sng2
       call ftn_strprn(sng1)
       call ftn_strprn(sng2)
       stop 'EXIT_FAILURE from ftn_strpfx()'
    endif                     ! endif
    ! Order is important
    sng2(lsc1+1:lsc1+lsc2)=sng2(1:lsc2)
    sng2(1:lsc1)=sng1(1:lsc1)
    return
  end subroutine ftn_strpfx
  
  subroutine ftn_drcpfx( & ! [sbr] fl_nm := drc/fl_nm 
       drc, & ! I [sng] Directory to prepend
       fl_nm) ! I/O [sng] File name
    ! Purpose: fl_nm := drc/fl_nm, more or less
    ! Differences with ftn_strpfx(): 
    ! 1. Result is stored in second string not first string
    ! 2. Strings are trimmed before concatenation
    ! 3. Filenames containing slashes are unaltered
    ! Usage:
    ! call ftn_drcpfx(drc,fl_nm) ! [sbr] fl_nm := drc/fl_nm
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Input
    character(len=*),intent(in)::drc ! I [sng] Directory to prepend
    ! Input/Output
    character(len=*),intent(inout)::fl_nm ! I/O [sbr] File name
    ! Local
    integer len_drc
    integer len_fl
    integer lng_drc
    integer lng_fl
    ! Main Code
    len_drc=len(drc)
    len_fl=len(fl_nm)
    lng_drc=ftn_strlen(drc) ! [nbr] Length of string
    lng_fl=ftn_strlen(fl_nm) ! [nbr] Length of string
    if (lng_drc+lng_fl >= len_fl) then
       write (6,'(a,a)') prg_nm(1:ftn_strlen(prg_nm)),': ERROR lng_drc+lng_fl >= len_fl in ftn_drcpfx()'
       write (6,'(2(a,i3),2a)') 'len_drc = ',len_drc,', lng_drc = ',lng_drc,', drc = ',drc
       write (6,'(2(a,i3),2a)') 'len_fl = ',len_fl,', lng_fl = ',lng_fl,', fl_nm = ',fl_nm
       call ftn_strprn(drc)
       call ftn_strprn(fl_nm)
       stop 'EXIT_FAILURE from ftn_drcpfx()'
    endif                     ! endif
    
    if ( &                     ! Conditions for prepending fl_nm with drc:
         lng_drc > 0 .and. &   ! Directory must be non-NUL 
         ftn_strstr(fl_nm,'/') == -1 & ! fl_nm does not look like directory itself
         ) then
       ! If directory does not have trailing slash...
       if (drc(lng_drc:lng_drc) /= '/') then ! Directory does not have trailing slash
          ! ...and if room exists for one...
          if (lng_drc+lng_fl+1 < len_fl) then
             ! ...then add one
             ! Order is important
             fl_nm(lng_drc+2:lng_drc+2+lng_fl)=fl_nm(1:lng_fl)
             fl_nm(1:lng_drc)=drc(1:lng_drc)
             fl_nm(lng_drc+1:lng_drc+1)='/'
          endif               ! endif adding trailing slash
       else                   ! Directory does have trailing slash
          fl_nm(lng_drc+1:lng_drc+1+lng_fl)=fl_nm(1:lng_fl)
          fl_nm(1:lng_drc)=drc(1:lng_drc)
       endif                  ! Directory does have trailing slash
    endif                     ! endif prepending drc
    call ftn_strnul(fl_nm) ! [sbr] NUL-initialize all characters after LSC
    
    return
  end subroutine ftn_drcpfx
  
  subroutine ftn_prg_ID_mk(CVS_Id,CVS_Revision,CVS_Date,prg_ID)
    ! Purpose: Create identity string from CVS input
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Parameter
    integer,parameter::CVS_kk=0
    integer,parameter::CVS_kv=1
    integer,parameter::CVS_kkv=2
    ! Input
    character(len=*),intent(in)::CVS_Id
    character(len=*),intent(in)::CVS_Revision
    character(len=*),intent(in)::CVS_Date
    ! Output
    character(len=*),intent(out)::prg_ID
    ! Local
    integer CVS_typ
    integer Date_ptr
    integer slash_ptr
    integer prg_ptr
    integer vrs_ptr
    ! Main Code
    ! Decide how CVS keywords were expanded
    Date_ptr=ftn_strstr(CVS_Date,'Date')
    if (Date_ptr < 0) then
       ! String 'Date' was not found---CVS expansion must be -kv
       CVS_typ=CVS_kv
    else
       ! String 'Date' was found---CVS expansion is -kk or -kkv
       slash_ptr=ftn_strstr(CVS_Date,'/')
       if (slash_ptr > 0) then 
          CVS_typ=CVS_kkv 
       else 
          CVS_typ=CVS_kk
       endif                  ! endif
    endif                     ! endif
    
    if (.false.) then
       write (6,'(a,a)') prg_nm(1:ftn_strlen(prg_nm)),': DEBUG ftn_prg_ID_mk() reports CVS strings:'
       write (6,'(a,a)') 'CVS_Id: ',CVS_Id
       write (6,'(a,a)') 'CVS_Revision: ',CVS_Revision
       write (6,'(a,a)') 'CVS_Date: ',CVS_Date
       write (6,'(a,i1)') 'CVS_typ: ',CVS_typ
       write (6,'(a,i2)') 'Date_ptr: ',Date_ptr
       write (6,'(a,i2)') 'slash_ptr: ',slash_ptr
    endif
    
    ! CVS_Revision and CVS_Date are parameters---do not attempt to write them
    !  call ftn_strnul(CVS_Revision) ! [sbr] NUL-initialize all characters after LSC
    !  call ftn_strnul(CVS_Date) ! [sbr] NUL-initialize all characters after LSC
    call ftn_strini(prg_ID) ! [sng] sng(1:len)=NUL
    if (CVS_typ == CVS_kk) then
       prg_ID='Source file unknown Version unknown Date unknown' // char(0)
    else 
       prg_ptr=ftn_strstr(CVS_Id,',v') ! ',v' is right after program name
       if (prg_ptr < 0) stop 'ERROR: ftn_prg_ID_mk() unable to find source code name'
       if (CVS_typ == CVS_kv) then
          prg_ID=CVS_Id(1:prg_ptr-1) //  &
               ' version ' // CVS_Revision(1:ftn_strlen(CVS_Revision)) //  &
               ' dated ' // CVS_Date(1:ftn_strlen(CVS_Date)) // ' GMT' // char(0)
       else if (CVS_typ == CVS_kkv) then
          vrs_ptr=ftn_strstr(CVS_Revision,'ion:') ! 'ion:' should appear two characters before version
          if (vrs_ptr < 0) stop 'ERROR: ftn_prg_ID_mk() unable to find revision number'
          prg_ID=CVS_Id(6:prg_ptr-1) //  &
               ' version ' // CVS_Revision(vrs_ptr+5:ftn_strlen(CVS_Revision)-2) //  &
               ' dated ' // CVS_Date(8:26) // ' GMT' // char(0)
       else 
          write (6,'(a,a)') prg_nm(1:ftn_strlen(prg_nm)),': ERROR ftn_prg_ID_mk() reports unknown CVS_typ'
       endif                  ! endif
    endif                     ! endif
    return
  end subroutine ftn_prg_ID_mk
  
  subroutine ftn_cmd_ln_sng(cmd_ln) ! [sng] Re-construct command-line into single string
    ! Purpose: Return a copy of command-line and initialize program name
    ! See http://www.winteracter.com/f2kcli/index.htm for alternatives
    ! Usage:
    ! call ftn_cmd_ln_sng(cmd_ln) ! [sng] command-line
    use mod_utils ! [mdl] Debugging constants, prg_nm, dbg_lvl
    implicit none
    ! Commons
    ! Output
    character(len=*),intent(out)::cmd_ln ! O [sng] command-line
    ! Local
    character(len=80) arg_val         ! [sng] command-line argument value
    integer arg_idx           ! [idx] Counting index
    integer arg_nbr           ! [nbr] Number of command-line arguments
    integer cmd_ln_len        ! [nbr] Length of command-line
    integer cmd_ln_lng        ! [nbr] Length of command-line
    ! Main Code
    ! Initialize defaults
    call ftn_strini(cmd_ln) ! [sng] sng(1:len)=NUL
    call ftn_strini(prg_nm) ! [sng] sng(1:len)=NUL
    cmd_ln_len=len(cmd_ln)  ! [nbr] Length of command-line
    cmd_ln_lng=0            ! [nbr] Length of command-line CEWI
    
    arg_nbr=command_argument_count() ! [nbr] Number of command-line arguments
    if (dbg_lvl >= dbg_vec) then
       write (6,'(a,i2)') 'ftn_cmd_ln_sng(): arg_nbr = ',arg_nbr
    endif                     ! endif dbg
    
    ! Loop over arguments
    do arg_idx=0,arg_nbr      ! NB: Loop starts with 0
       ! Argument 0 is program name
       ! if (arg_idx > 0) call ftn_strcat(cmd_ln,' ')
       ! Insert space between arguments
       if (arg_idx > 0) cmd_ln(cmd_ln_lng+1:cmd_ln_lng+1)=' '
       call ftn_strini(arg_val) ! [sng] sng(1:len)=NUL
       call get_command_argument(arg_idx,arg_val)
       call ftn_strnul(arg_val) ! [sbr] NUL-initialize all characters after LSC
       call ftn_strcat(cmd_ln,arg_val)
       if (arg_idx == 0) call ftn_strcpy(prg_nm,arg_val) ! [sng] Program name
       cmd_ln_lng=ftn_strlen(cmd_ln) ! [nbr] Length of string
       if (cmd_ln_lng > cmd_ln_len) stop 'cmd_ln_lng > cmd_ln_len in ftn_cmd_ln_sng()'
       if (dbg_lvl >= dbg_vrb) then
          write (6,'(a,i3)') 'arg_idx = ',arg_idx
          write (6,'(a,a)') 'arg_val = ',arg_val
          write (6,'(a,i3)') 'len(arg_val) = ',len(arg_val)
          write (6,'(a,i3)') 'ftn_strlen(arg_val) = ',ftn_strlen(arg_val)
          write (6,'(a,i3)') 'ftn_strlen(cmd_ln) = ',ftn_strlen(cmd_ln)
          write (6,'(a,a)') 'cmd_ln = ',cmd_ln
       endif                  ! endif dbg
    end do                     ! end loop over arg
    
    if (dbg_lvl >= dbg_io) write (6,'(a,a)') 'cmd_ln = ',cmd_ln(1:ftn_strlen(cmd_ln))
    return
  end subroutine ftn_cmd_ln_sng
  
  character(len=10) function ftn_date2sng(idate)
    ! Purpose: Convert integer date in YYYYMMDD to a character array in the form
    ! 'YYYY-MM-DD'
    ! Author: Brian Eaton charutl.F:idate2char()
    ! Recoded: 19990901 Charlie Zender
    ! Usage:
    ! character(len=10) ftn_date2sng   ! [sng] Convert YYYYMMDD integer to YYYY-MM-DD string
    implicit none
    ! Input
    integer,intent(in)::idate
    ! Local
    integer yy 
    integer mm
    integer dd
    character dash
    ! Main code
    dash='-'
    ! Extract year, month, and day from date
    yy=idate/10000
    mm=mod(abs(idate),10000)/100
    dd=mod(abs(idate),100)
    write(ftn_date2sng,'(i4.4,a1,i2.2,a1,i2.2)') yy,dash,mm,dash,dd
    return
  end function ftn_date2sng
  
  character(len=8) function ftn_sec2sng(isec)
    ! Purpose: Convert integer seconds to a character array in the form 'HH:MM:SS'
    ! Author: Brian Eaton charutl.F:isec2char()
    ! Recoded: 19990901 Charlie Zender
    ! character(len=8) ftn_sec2sng   ! [sng] Convert integer to 'HH:MM:SS' string
    implicit none
    ! Input
    integer,intent(in)::isec
    ! Local
    integer hh
    integer mm
    integer ss
    character colon
    ! Main Code
    colon=':'
    ! Extract hour, minutes, seconds
    hh=isec/3600
    mm=mod(isec,3600)/60
    ss=mod(isec,60)
    write(ftn_sec2sng,'(i2.2,a1,i2.2,a1,i2.2)') hh,colon,mm,colon,ss
    return
  end function ftn_sec2sng
  
end module mod_sng ! [mdl] String manipulation