Appearance
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 =', portLoad 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) ! localhostINFO
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 doTIP
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 = 100fortran
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 optionThe 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 = 22fortran
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 doMulti-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.0Inline 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 serverfortran
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: 8080fortran
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.1The 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 yetSet 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 TReal 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 sectionWARNING
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 FIntrospection
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 ifCheck 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 doGet 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 50Retrieve 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 doIterate 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 doPretty-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)