The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Config::INI::Tiny - Parse INI configuration in extremely little code

SYNOPSIS

Code

 use Config::INI::Tiny;
 use File::Slurper 'read_text';
 my $config = Config::INI::Tiny->new->to_hash( read_text 'myapp.ini' );

Configuration

 global = setting
 
 [section]
 name = value
 more = stuff
 
 [empty section]

DESCRIPTION

This is an extremely small amount of code for parsing the INI configuration format, in its simplest form, as known from Microsoft Windows.

Its design focuses on making it easy to extract whichever level of semantics you need from your configuration: whether you want to care about the order of properties; their recurrence; the order of sections; treating each section header as a separate unit; or however else you want to be able to interpret your configuration files — you can.

METHODS

new

Instantiates an INI parser with the specified configuration.

The parsed form consists of a list of sections as they appeared in the configuration, each represented by an array. The first element of each array is the name of the section. It is followed by any number of property/value pairs as they appeared in the configuration.

This format can be modified with the following options:

section0

The section name for properties found before the first section in the configuration.

Defaults to the empty string, which cannot be expressed by valid configuration syntax.

For Config::Tiny compatibility, set this to an underscore (_). Note that a section with this name is valid syntax and can appear in a configuration.

pairs

When true, each property/value pair within the section array is returned as a two-element sub-array. Otherwise all the pairs are returned as a single flat list.

Defaults to false.

to_hash

Takes a configuration in INI format as a string and returns it as a hash of hashes or throws an exception on syntax error.

parse

Takes a configuration in INI format as a string and returns it in parsed form or throws an exception on syntax error.

SYNTAX

  • Leading and trailing whitespace is ignored on every line.

  • Empty lines are ignored.

  • Lines that start with # or ; are ignored.

     ; this is a comment
     # so is this
  • Lines enclosed in paired square brackets with any non-whitespace in between are section lines.

     [section name]
  • Leading and trailing whitespace in section names is ignored.

     ; these are all the same section:
     [ section name ]
     [section name  ]
  • Lines that contain a = are property lines.

     prop name=prop value

    Everything left of the leftmost = is the name of the property. Everything to the right of that = is the value of the property. The name of the property cannot be empty, but the value can.

  • Leading and trailing whitespace in property names and values is ignored.

     ; these all have identical effect:
     prop name=prop value
     prop name  =  prop value
     prop name=    prop value
  • Everything else is a syntax error.

COOKBOOK

Emulating Config::Tiny::Ordered

 my $config;
 for my $s_kv ( Config::INI::Tiny->new( section0 => '_', pairs => 1 )->parse( $content ) ) {
     my $section_name = shift @$s_kv;
     push @{ $config->{ $section_name } }, map +{ key => $_->[0], value => $_->[1] }, @$s_kv;
 };

The resulting data structure is a hash of arrays instead of the hash of hashes that Config::Tiny would produce. The arrays contain key/value pairs in order of their appearance in the input, where each pair is represented by a hash with two keys named key and value.

Multi-value (and order-preserving) properties

 use Hash::MultiValue;
 
 my $config;
 for my $s_kv ( Config::INI::Tiny->new->parse( $content ) ) {
     my $section_name = shift @$s_kv;
     my $section = $config->{ $section_name } ||= Hash::MultiValue->new;
     $section->merge_flat( @$s_kv );
 }

Consider the following configuration:

 [eth0]
 ip = 192.168.0.17
 ip = 10.0.1.253

When using "to_hash", this would simply store 10.0.1.253 as the IP for eth0, with the 192.168.0.17 value irretrievably lost. Hash::MultiValue defaults to the same behaviour, but optionally allows you to still retrieve all other values:

 say for $config->{'eth0'}->get_all('ip');
 # 192.168.0.17
 # 10.0.1.253

Flexibly nestable sections and properties

 sub hash_path_ref (\%;@) {
     my $hash = shift;
     $hash = $hash->{ $_ } ||= {} for @_[ -@_ .. -2 ];
     \$hash->{ $_[-1] };
 }
 
 my $config = {};
 for my $s_kv ( Config::INI::Tiny->new( pairs => 1 )->parse( $content ) ) {
     my $section_name = shift @$s_kv;
     my $section = ${ hash_path_ref %$config, split ' ', $section_name } ||= {};
     ${ hash_path_ref %$section, split ' ', $_->[0] } = $_->[1] for @$s_kv;
 }

This interprets spaces in section and property names as path separators, allowing a value like $config->{'de'}{'error'}{'syntax'} to be specified in any one of the following ways, interchangeably:

 de error syntax = Syntax-Fehler

 [de]
 error syntax = Syntax-Fehler

 [de error]
 syntax = Syntax-Fehler

The first two choices are convenient for sections with very few properties, to avoid having to write a section header line just for one or two properties.

The last one is especially convenient when a section with many properties has a deeply nested path, to avoid having to repeat (part of) the path for each and every property.

Caveat lector: this creates the possibility of inconsistent configurations like the following:

 [foo]
 bar = 1
 
 [foo bar]
 baz = 1

With the given code, a configuration like this will trigger a ref stricture exception while parsing, because it is essentially equivalent to the following code:

 use strict 'refs';
 $config->{'foo'}{'bar'} = 1;
 $config->{'foo'}{'bar'}{'baz'} = 1; # boom

Depending on use case, the ref stricture exception may or may not be sufficient (syntax) error handling.

SEE ALSO

Config::Tiny

The original basis for this module. Its design is focused on making the simplest operations as easy as possible, so it returns the configuration as a blessed hash on which you can call a method to write it back to a file. It does not preserve any information about the order or recurrence of either sections or properties.

Config::Tiny::Ordered

This is a clone of Config::Tiny with a more complex data structure, allowing it to preserve the order and recurrence of properties within sections. Section order and recurrence is still discarded. There is no other change in trade-offs.

The shape of its output data structure is easy to replicate with this module, as shown in the cookbook.

Config::INI

An attempt at making Config::Tiny flexible by breaking out the steps in the parser as methods, which allows parsing a different syntax. Tweaking is done by subclassing, which is cumbersome. If you wish to avoid reimplementing large parts of the module in your subclass, you are constrained by the existing call graph, so you must stay fairly close to both the original format and the default data structure shape. In other words, you can parse any format into any output, so long as it is basically INI parsed into the default output format; or you can write basically your own parser.

By contrast, Config::INI::Tiny offers no way of redefining the core INI syntax but instead provides an easy-to-process full-fidelity representation of its semantics, so that you can implement additional semantics on top of it, as shown in the cookbook.

AUTHOR

Aristotle Pagaltzis <pagaltzis@gmx.de>

COPYRIGHT AND LICENSE

This software is copyright (c) 2020 by Aristotle Pagaltzis.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.