Skip to content

Usage ​

All examples use the modern API: use finer and type(file_ini).

INI Parsing ​

Load from a file ​

Pass filename= to read an INI file from disk. The file is opened, parsed, and closed automatically. The option separator is = unless overridden with separator=.

fortran
use finer
use penf, only: I4P
type(file_ini) :: fini
integer(I4P)   :: error
integer(I4P)   :: port

call fini%load(filename='config.ini', error=error)
if (error /= 0) stop 'failed to load config.ini'

call fini%get(section_name='database', option_name='port', val=port, error=error)
print *, 'port =', port

Load from a string ​

Pass source= to parse INI data held in a character variable — no file on disk required. Useful for embedding default configuration in source code or for testing.

fortran
use finer
use penf, only: I4P
type(file_ini)                :: fini
character(len=:), allocatable :: source
integer(I4P)                  :: error
character(64)                 :: host

source = '[database]'//new_line('A')// &
         'host = localhost'//new_line('A')// &
         'port = 5432'//new_line('A')// &
         '[logging]'//new_line('A')// &
         'level = info'

call fini%load(source=source, error=error)
call fini%get(section_name='database', option_name='host', val=host, error=error)
print *, trim(host)   ! localhost

INFO

If both filename= and source= are supplied, filename takes priority.

Automatic section and option detection ​

FiNeR scans the input and discovers all sections and options without any schema or pre-declaration. The section count is returned by fini%sections_number(); use it to iterate programmatically.

fortran
use finer
type(file_ini)                :: fini
character(len=:), allocatable :: sec_name
integer                       :: s

call fini%load(filename='config.ini')
print *, fini%sections_number(), 'section(s) found'

do s = 1, fini%sections_number()
  sec_name = fini%section(s)
  print *, '  [', sec_name, ']'
end do

TIP

sections_number() returns the number of sections, the global one included. The count is managed internally by load, add and del.

Options before the first section ​

Options defined before the first section header belong to the global section, which has an empty name. Address it with section_name='' in any method.

ini
title = my analysis
steps = 10

[mesh]
cells = 100
fortran
use finer
type(file_ini)                :: fini
character(len=:), allocatable :: title
integer                       :: steps

call fini%load(filename='config.ini')
call fini%get_string(section_name='', option_name='title', val=title)
call fini%get(section_name='', option_name='steps', val=steps)
call fini%add(section_name='', option_name='author', val='me')   ! add a global option

The global section is always the first one. save and print write its options first, without a header, so a file with global options is saved as it was loaded. A file with no section headers at all is loaded entirely into the global section. A section with an explicit empty header, [], is the global section too: its options are merged with the ones defined before the first section.

Subsections ​

FiNeR has no nested sections, but a section name can contain any character, so the common [parent.child] convention works as a flat name: pass the full dotted name as section_name.

ini
[server]
name = main

[server.http]
host = localhost

[server.ssh]
port = 22
fortran
use finer
type(file_ini)                :: fini
character(len=:), allocatable :: list(:), host
integer                       :: s

call fini%load(filename='config.ini')
call fini%get_string(section_name='server.http', option_name='host', val=host)

! list the subsections of [server]
call fini%get_sections_list(list)
do s=1, size(list)
  if (index(list(s), 'server.') == 1) print '(A)', trim(list(s))   ! server.http, server.ssh
end do

Multi-line option values ​

A line that contains no = (and is not a section header or comment) is treated as a continuation of the previous option. FiNeR concatenates it — separated by a space — to the preceding value.

ini
[section-1]
option-2 = 2.
           3.
           4.
fortran
use finer
use penf, only: R4P, I4P
type(file_ini)         :: fini
real(R4P), allocatable :: values(:)
integer(I4P)           :: Nv, error

call fini%load(filename='config.ini')

Nv = fini%count_values(section_name='section-1', option_name='option-2')
allocate(values(1:Nv))
call fini%get(section_name='section-1', option_name='option-2', val=values, error=error)
print *, values   ! 2.0  3.0  4.0

Inline comments ​

Any text following ; on an option line is treated as an inline comment and stripped before the value is stored.

ini
[database]
port = 5432 ; default PostgreSQL port
host = localhost ; change for remote server
fortran
use finer
use penf, only: I4P
type(file_ini) :: fini
integer(I4P)   :: port

call fini%load(filename='config.ini')
call fini%get(section_name='database', option_name='port', val=port)
print *, port   ! 5432  (comment stripped)

TIP

Full-line comments use !, ;, or # as the first non-blank character. Inline comments use ; after the value.

Configurable option separator ​

The default separator between option name and value is =. Use separator= to override it for files that use : or another character.

ini
[server]
host: 192.168.1.1
port: 8080
fortran
use finer
type(file_ini) :: fini
character(64)  :: host

call fini%load(filename='config.ini', separator=':')
call fini%get(section_name='server', option_name='host', val=host)
print *, trim(host)   ! 192.168.1.1

The separator is kept by the object for the following load calls, until free or initialize resets it to =.


INI Generation ​

Add sections and options programmatically ​

Use add to build an INI structure in memory. Adding a section that already exists is a no-op. Adding an option to a non-existent section creates the section automatically.

fortran
use finer
type(file_ini) :: fini

call fini%add(section_name='network')
call fini%add(section_name='network', option_name='host', val='localhost')
call fini%add(section_name='network', option_name='port', val=8080)
call fini%add(section_name='logging')   ! section only, no options yet

Set option values of any intrinsic type ​

val= is unlimited polymorphic. Pass integer, real, logical, or character scalars of any kind, including all PENF kind parameters.

fortran
use finer
use penf, only: I4P, I8P, R4P, R8P
type(file_ini) :: fini

call fini%add(section_name='types')
call fini%add(section_name='types', option_name='int32',  val=42_I4P)
call fini%add(section_name='types', option_name='int64',  val=123456789_I8P)
call fini%add(section_name='types', option_name='real32', val=3.14_R4P)
call fini%add(section_name='types', option_name='real64', val=2.71828_R8P)
call fini%add(section_name='types', option_name='flag',   val=.true.)
call fini%add(section_name='types', option_name='label',  val='production')

Array-valued options ​

Pass a 1-D array as val=. Values are written space-separated. Use the optional delimiter= argument to choose a different separator.

fortran
use finer
use penf, only: R8P
type(file_ini) :: fini

call fini%add(section_name='sec-foo')
call fini%add(section_name='sec-foo', option_name='array',  val=[1, 2, 3, 4])
call fini%add(section_name='sec-foo', option_name='floats', val=[1.0_R8P, 2.5_R8P, 3.0_R8P])
call fini%add(section_name='sec-foo', option_name='bools',  val=[.true., .false., .true.])

The resulting INI content:

ini
[sec-foo]
array = 1 2 3 4
floats = 1.0 2.5 3.0
bools = T F T

Real values are written with the shortest text that reads back to exactly the same number: 0.1 is saved as 0.1, and a value such as 1/3 keeps all the digits it needs (0.3333333333333333). Very large or very small values use the scientific notation, e.g. 1.0E+20.

TIP

count_values counts space-separated tokens, so it works correctly with array-valued options written by add.

Remove sections and options ​

del removes an option from a section, or an entire section together with all its options.

fortran
use finer
type(file_ini) :: fini

call fini%load(filename='config.ini')

call fini%del(section_name='sec-foo', option_name='bar')   ! remove one option
call fini%del(section_name='sec-bar')                      ! remove whole section

WARNING

Deleting a section removes all of its options and decreases fini%sections_number().

Save to a named file ​

save writes the in-memory structure to a file. If filename= is omitted, the value of fini%filename is used (set automatically by a previous load call, or assignable directly).

fortran
use finer
use penf, only: R8P
type(file_ini) :: fini
integer        :: iostat

call fini%add(section_name='sec-foo')
call fini%add(section_name='sec-foo', option_name='bar', val=-32.1_R8P)
call fini%add(section_name='sec-foo', option_name='baz', val=' hello FiNeR! ')
call fini%add(section_name='sec-bar')
call fini%add(section_name='sec-bar', option_name='bools', val=[.true., .false., .false.])

call fini%save(filename='foo.ini', iostat=iostat)
if (iostat /= 0) stop 'save failed'

The generated foo.ini:

ini
[sec-foo]
bar = -32.1
baz =  hello FiNeR!
[sec-bar]
bools = T F F

Introspection ​

Check whether a section exists ​

has_section scans the section list and returns .true. on the first match.

fortran
use finer
type(file_ini) :: fini

call fini%load(filename='config.ini')

if (fini%has_section(section_name='database')) then
  print *, 'database section found'
end if

Check whether an option exists ​

has_option searches all sections (or a specific one) for the named option. The optional section_name= argument restricts the search; without it, all sections are scanned.

fortran
use finer
type(file_ini) :: fini
character(64)  :: container
logical        :: found

call fini%load(filename='config.ini')

! Search everywhere
found = fini%has_option(option_name='host')

! Search in a specific section
found = fini%has_option(option_name='host', section_name='database')

! Find which section contains the first occurrence
found = fini%has_option(option_name='host', section_name=container)
if (found) print *, 'host found in: ', trim(container)

INFO

When section_name is used as an output buffer the returned value is truncated to the declared length if the actual section name is longer.

Get a section name by index ​

section(i) returns the name of the i-th section. Combine with fini%sections_number() to iterate over all sections without hard-coding their names.

fortran
use finer
type(file_ini)                :: fini
character(len=:), allocatable :: sec_name
integer                       :: s

call fini%load(filename='config.ini')

do s = 1, fini%sections_number()
  sec_name = fini%section(s)
  print *, 'section ', s, ': ', sec_name
end do

Get the index of a section or option ​

index is the inverse of section: given a name, it returns the position in the list, or 0 if not found. It can also locate an option within a section.

fortran
use finer
type(file_ini) :: fini
integer        :: s, o

call fini%load(filename='config.ini')

s = fini%index(section_name='database')
if (s > 0) print *, 'database is section #', s

o = fini%index(section_name='database', option_name='host')
if (o > 0) print *, 'host is option #', o, ' in database'

TIP

index can be used as an integer-valued alternative to has_section/has_option when you need the position and not just the presence.

Count values in an option ​

count_values returns the number of space-separated tokens in an option's value. Use it to allocate a receiving array before calling get.

fortran
use finer
use penf, only: I4P
type(file_ini)            :: fini
integer(I4P), allocatable :: arr(:)
integer                   :: Nv

call fini%load(source='[mesh]'//new_line('A')//'dims = 100 200 50')

Nv = fini%count_values(section_name='mesh', option_name='dims')
allocate(arr(1:Nv))
call fini%get(section_name='mesh', option_name='dims', val=arr)
print *, arr   ! 100 200 50

Retrieve all option pairs as an array ​

items returns a (N, 2) allocatable character array. Each row contains [option_name, option_value] as strings. Useful for generic processing or serialisation.

fortran
use finer
type(file_ini)                :: fini
character(len=:), allocatable :: it(:,:)
integer                       :: i

call fini%load(filename='config.ini')

it = fini%items(section_name='database')
do i = 1, size(it, dim=1)
  print *, trim(it(i,1)), ' = ', trim(it(i,2))
end do

Iterate over options with a loop ​

loop provides a stateful do while iterator. It returns .true. and fills option(:) with [name, value] on each call, then returns .false. and resets when the list is exhausted.

fortran
use finer
type(file_ini)                :: fini
character(len=:), allocatable :: opt(:)
integer                       :: s

call fini%load(filename='config.ini')

! All options in one section
do while (fini%loop(section_name='database', option_pairs=opt))
  print *, trim(opt(1)), ' = ', trim(opt(2))
end do

! All options in the entire file
do while (fini%loop(option_pairs=opt))
  print *, trim(opt(1)), ' = ', trim(opt(2))
end do

! Loop with section filtering
do s = 1, fini%sections_number()
  if (fini%section(s) /= 'skip-me') then
    do while (fini%loop(section_name=fini%section(s), option_pairs=opt))
      print *, trim(opt(1)), ' = ', trim(opt(2))
    end do
  end if
end do

Pretty-print to a Fortran unit ​

print writes formatted INI output to any Fortran I/O unit. The optional pref= adds a fixed string at the start of each line, and retain_comments=.true. preserves inline comments.

fortran
use finer
type(file_ini) :: fini
integer        :: iostat
character(200) :: iomsg

call fini%load(filename='config.ini')

! Print to stdout (unit 6)
call fini%print(unit=6)

! Print with prefix and inline comments
call fini%print(unit=6, pref='|-->', iostat=iostat, iomsg=iomsg, retain_comments=.true.)
if (iostat /= 0) print *, trim(iomsg)