Getting started

Attempt to load a system-wide configuration file, whose settings will be overwritten by a user preferences files.

Missing files are silently ignored.

from ihih import IHIH

conf = IHIH(
      os.path.join(os.path.expanduser('~'), '.example.conf')

if conf.get_float('debug', errors='ignore'):
   print 'i am running in debug mode'

Reloading the conf

Assuming conf is a IHIH instance.

# reload on SIGHUP
import signal

signal.signal(signal.SIGHUP, lambda s, f: conf.reload())

Configuration format

By default, IHIH parse files using the following rules:

  • the key is before the first = character
  • the value is everything after the first = character
  • the value might be empty
  • key and value have their leading and trailing spaces stripped
  • values can be quoted (between ' or ")
  • quoted values have their quotes automatically removed (ie: "my value" becomes my value)
  • single quotes are considered as a character
  • lines not matching the key / separator / value are ignored
  • comments (beginning with a # or //) are ignored and deleted from the value except if they are escaped or quoted
  • specials characters (\'"#/) can be escaped by prefixing them with a backslash (\) to not be treated specially
  • other (non-special) characters preceded by the escape character are not treated specially and the escape character is preserved

By default, IHIHI parse files accordingly the following rules:

  • same-same than IHIH
  • add dollar ($) in the special character list
  • every word prefixed by a non-escaped dollar and not embraced by single-quotes (') is considered as a variable
  • strings beginning with ${ and ending with } are also variables, this let you define variables containing non-word characters such as dots hyphens, or spaces
  • variables interpolation is done when using the variable, this let you define (or change) the variable content later
  • when a variable is not found, it resolve as an empty string
  • variable recursion resolve to an empty string

Which mean that it could parse, to a certain extent (see Single-line only), subset of:

  • shell script
  • Postfix main.cf
  • Python
  • INI (will ignore the sections)

That could be convenient if you have to share a configuration file between scripts, given you pay attention to respect both formats.

Examples of configuration files

Parsing a shell script:

# as in shell
FOOBAR=foo-$FOO   # resolve as: foo-bar
FOOBAR="foo-$FOO" # resolve as: foo-bar
FOOBAR='foo-$FOO' # resolve as: foo-$FOO
BAR=${FOO}        # resolve as: bar
ABC="a" 'b' c     # resolve as: a b c
C=hello # world   # resolve as: hello
D=hello \# world  # resolve as: hello # world

# different
DATE=$(date)      # resolve as: $(date)

Parsing a main.cf:

smtpd_banner = $myhostname ESMTP
myhostname = foo.example.net

Parsing some Python:

# same
a = 'AA'
b = "BB"

# notably different
c = 'A' "B"     # resolve as: A B
d = c           # resolve as: c

Parsing an INI file:

; section is ignored
http-socket = :9090
processes = 4

; different, resolve as: localhost:9000
URL = localhost${http-socket}

Examples in the examples directory

You can see / run the examples in the examples directory.

Extending the parsers to parse INI

# vim:set fileencoding=utf8:
'''INI parsing with ihih - proof of concept

A quick-and-dirty, incomplete, INI parsing proof-of-concept using :mod:`ihih`.

import os
import re
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from ihih import IHIH, IHIHI

class _IHIHI(IHIHI):
    _escaped_chars = r'[\\\'\"\#/\;]'
    _comment = r'(\s*%(escape)s(?:\#|//|\;))'

    _section = r'^%(escape)s\[(?P<section>.+?)%(escape)s\]'
    _escaped_chars = r'[\\\'\"\#/\;]'
    _comment = _IHIHI._comment
    _separator = r'[\=\:]'

    def __init__(self, *args, **kwargs):
        self.r_section = re.compile(
            self._section  % {'escape': self._escape},

        super(IHIHINI, self).__init__(*args, **kwargs)

    __setitem__ = dict.__setitem__
    __getitem__ = dict.__getitem__

    def parse(self, filename, force=False, ignore_IOError=True):
        section = None
            fo = open(filename)
        except IOError:
            if ignore_IOError:
                return False

        for line in fo:
            results = self.r_section.match(line)
            if results:
                section = results.group(1)
            results = self.r_extract.match(line)
            if results:
                if section is None:
                    raise KeyError('not in a section')
                elif section not in self:
                    self[section] = _IHIHI(())

                self[section][results.group('key')] = results \

        return True

if __name__ == '__main__':
    import tempfile

    with tempfile.NamedTemporaryFile() as tmp:
        tmp.write('[My section]\nfoodir: $dir/whatever\ndir=frob\n')
        tmp.write('key = "value" ; a comment')

        conf = IHIHINI(tmp.name)

    for section in conf:
        print('%s:' % section)
        for k in conf[section]:
            print('\t%s = %s' % (k, conf[section].get_unicode(k)))