/cfgio

Fortran config file parser

Primary LanguageFortranMIT LicenseMIT

CFGIO

Fortran config file parser

Install

make
make install

Compile

your_fortran_compiler -o main.e main.f90 -I/path/to/include -L/path/to/lib -lcfgio

Config (ini) file

A configuration file contains sections, keywords, and values as follows.

[Section title]
keyword = value

Lines starting with # or ; are comments.

Here is an example configuration file.

[DEFAULTS]
path = ../include
use_abs = True

[Section 1]
nmax = 30
# comment 1
vmin = 1.0
freqs = 5.0, 10.0, 30.0, 50.0
amps = 0.0, 1.0, 1.0, 0.0
path = ../text

[Section 2]
use_abs = no
; comment 2
my file = ${Section 1:path}/file.txt

Let's parse it using Fortran.

cfgio_mod module

cfg_t type

A derived data type cfg_t is required to parse a config file.

use cfgio_mod, only: cfg_t, parse_cfg
type(cfg_t):: cfg

Input

We can use cfg = parse_cfg(config_file_name) to parse a config file. The resultant cfg_t type varable cfg contains all sections, keys and values of the config file. Use call cfg%get(section_title,keyword,value) to get a value from the derived data type. value can be one of following types.

  • integer
  • logical
  • character(len=*)
  • real(kind=4)
  • real(kind=8)
  • complex(kind=4)
  • complex(kind=8)
  • allocatable arrays of above types for array input/output

For example, we can parse the example config file using the following code.

use cfgio_mod
type(cfg_t):: cfg
integer:: nmax, npar
real:: vmin
real(kind=8),allocatable:: freqs(:),amps(:)
logical:: flag1,flag2
character(len=256):: filename

cfg=parse_cfg("test.cfg")
call cfg%get("Section 1","nmax",nmax)    !! 30
call cfg%get("Section 1","vmin",vmin)    !! 1.0

call cfg%get("Section 1","freqs",freqs)  !! [5.0, 10.0, 30.0, 50.0]
call cfg%get("Section 1","amps",amps,npar)  !! [0.0, 1.0, 1.0, 0.0], npar=4
call cfg%get("Section 1","use_abs",flag1) !! .true. (from DEFAULTS section)

call cfg%get("Section 2","use_abs",flag2) !! .false.
call cfg%get("Section 2","my file",filename) !! ../text/file.txt (interpolated)

The code may require additional explanations regarding "DEFAULTS" section, array input, string interpolation, and boolean values.

DEFAULTS section

If a keyword is missing in the designated section, cfgio tries to search the keyword from the 'DEFAULTS' section (Idea from Python configparser). For example, flag1 value from the above code is from the 'DEFAULTS' section because "Section 1" does not contain "use_abs" keyword.

Array input

We can use allocatable arrays to obtain a list of values. cfg.get subroutine allocates the memory. Optionally, the subroutine returns number of parameters in a list as shown in the example using npar.

Interpolation

${key1} in a value is replaced with the value of key1 in the current section. We can specify the section name containing the keyword as ${Section 1:key1} as shown in the example (filename). "DEFAULTS" section for a missing keyword is also available in the interpolation process.

Boolean

Following strings are rendered as .true.. Otherwize, .false.

  • TRUE, True, true, T, t
  • YES, Yes, yes, Y, y
  • ON, On, on
  • .TRUE., .true.

Output

We can assign a key=value pair to a cfg_t type variable using call cfg%set(section_title,keyword,value) and write a config file using call cfg.write(output_file_name). We can use a logical unit number instead of the output_file_name. If the output_file_name argument is missing, the subroutine writes output to the standard output.

use cfgio_mod
type(cfg_t):: cfg
integer:: value=1
real:: values(3) = [1.0,2.0,3.0]

call cfg%set("Section 1","value",value)
call cfg%set("Section 2","values",values)
call cfg%write("output.cfg")

API reference

type(cfg_t):: cfg
integer:: value
! Supported types of input/output values:
!   integer, logical, character(len=*),
!   real(kind=4), real(kind=8),
!   complex(kind=4), complex(kind=8),
!   allocatable arrays of above types

!!! INPUT
!! parse config file
cfg=parse_cfg("config_file_name")

!! getter subroutines
! The program stops if it cannot find the "key" in both "section title" and "DEFAULTS" sections.
call cfg%get("section title","key",value)

! (optional integer output) npar: number of parameters (array size)
call cfg%get("section title","key",value,npar)

!! getter functions
val = cfg%gets("section title","key") ! character
val = cfg%getb("section title","key") ! logical
val = cfg%geti("section title","key") ! integer
val = cfg%getf("section title","key") ! real(kind=4)
val = cfg%getd("section title","key") ! real(kind=8)
val = cfg%getc("section title","key") ! complex(kind=4)
val = cfg%getz("section title","key") ! complex(kind=8)

!! Does the file have designated section/keyword?
if( .not. cfg%has_section("section title") ) stop

if( .not. cfg%has_key("section title","key") ) stop

! search_defaults=.false. : Do not try to find the key in the DEFAULTS section
if( .not. cfg%has_key("section title","key",search_defaults=.false.) ) stop


!!! OUTPUT
call cfg%set("section title","key",value)

!! generate a config file (we can use 'print' instead of 'write')
call cfg%write("output_file_name.cfg")

! to standard output
call cfg%write()

open(output_unit_number,file="output_file_name.cfg")
call cfg%write(output_unit_number)
close(output_unit_number)