Walk through the module tree.

    This is a helper function for :func:`create_config_tree` and
    :func:`_walk_config`.  It calls `f` once for each module in the
    configuration tree with parameters `parent_config`, `config_name`,
    `prefix`, and `module`.  `parent_config[config_name]` may or may
    not exist (but could be populated, as :func:`create_config_tree`).
    If even the parent configuration doesn't exist, `parent_config`
    could be :const:`None`.

    :param dict parent_config: configuration dictionary holding
      configuration for `modules`, or maybe :const:`None`
    :param modules: modules or Configurable instances to use
    :type modules: iterable of :class:`~yakonfig.configurable.Configurable`
    :param f: callable to call on each module
    :param str prefix: prefix name of `parent_config`
    :return: `parent_config`

def _recurse_config(parent_config, modules, f, prefix=''):
    '''Walk through the module tree.

    This is a helper function for :func:`create_config_tree` and
    :func:`_walk_config`.  It calls `f` once for each module in the
    configuration tree with parameters `parent_config`, `config_name`,
    `prefix`, and `module`.  `parent_config[config_name]` may or may
    not exist (but could be populated, as :func:`create_config_tree`).
    If even the parent configuration doesn't exist, `parent_config`
    could be :const:`None`.

    :param dict parent_config: configuration dictionary holding
      configuration for `modules`, or maybe :const:`None`
    :param modules: modules or Configurable instances to use
    :type modules: iterable of :class:`~yakonfig.configurable.Configurable`
    :param f: callable to call on each module
    :param str prefix: prefix name of `parent_config`
    :return: `parent_config`

    '''
    for module in modules:
        config_name = getattr(module, 'config_name', None)
        if config_name is None:
            raise ProgrammerError('{0!r} must provide a config_name'
                                  .format(module))
        new_name = prefix + config_name

        f(parent_config, config_name, new_name, module)

        try:
            _recurse_config((parent_config or {}).get(config_name, None),
                            getattr(module, 'sub_modules', []),
                            f,
                            new_name + '.')
        except:
            # achieve a sort of stack trace on the way out
            logger.error('exception in _recurse_config of %s', module)
            raise
    return parent_config