--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2017 by Frank Brehm, Berlin
+@summary: The module for the application object.
+"""
+from __future__ import absolute_import
+
+# Standard modules
+import sys
+import os
+import logging
+import re
+import traceback
+import textwrap
+import datetime
+
+# Third party modules
+import six
+
+from six import StringIO
+from six.moves import configparser
+
+from configparser import Error as ConfigParseError
+
+import argparse
+
+# Own modules
+from .global_version import __version__ as __global_version__
+
+from .errors import FunctionNotImplementedError, PpAppError
+
+from .common import pp, terminal_can_colors, to_bytes
+
+from .rec_dict import RecursiveDictionary
+
+from .app import PpApplication
+
+__version__ = '0.2.1'
+LOG = logging.getLogger(__name__)
+
+
+# =============================================================================
+class PpCfgAppError(PpAppError):
+ """Base error class for all exceptions happened during
+ execution this configured application"""
+
+ pass
+
+
+# =============================================================================
+class PpConfigApplication(PpApplication):
+ """
+ Class for configured application objects.
+ """
+
+ # -------------------------------------------------------------------------
+ def __init__(
+ self, appname=None, verbose=0, version=__version__, base_dir=None,
+ initialized=None, usage=None, description=None,
+ argparse_epilog=None, argparse_prefix_chars='-', env_prefix=None,
+ cfg_dir=None, cfg_stem=None, cfg_encoding='utf-8', need_config_file=False):
+
+ self._cfg_encoding = cfg_encoding
+ self._need_config_file = bool(need_config_file)
+
+ self.cfg = RecursiveDictionary()
+
+ self._cfg_dir = None
+ self._cfg_stem = None
+ self.cfg_files = []
+
+ super(PpConfigApplication, self).__init__(
+ appname=appname, verbose=verbose, version=version, base_dir=base_dir,
+ initialized=False, usage=usage, description=description,
+ argparse_epilog=argparse_epilog, argparse_prefix_chars=argparse_prefix_chars,
+ env_prefix=env_prefix,
+ )
+
+ if cfg_dir is None:
+ self._cfg_dir = self.appname
+ else:
+ d = str(cfg_dir).strip()
+ if d == '':
+ self._cfg_dir = None
+ else:
+ self._cfg_dir = d
+
+ if cfg_stem:
+ s = str(cfg_stem).strip()
+ if not s:
+ msg = "Invalid configuration stem {!r} given.".format(cfg_stem)
+ raise PbCfgAppError(msg)
+ self._cfg_stem = s
+ else:
+ self._cfg_stem = self.appname
+
+ self.init_cfgfiles()
+
+ enc = getattr(self.args, 'cfg_encoding', None)
+ if enc:
+ enc = enc.lower()
+ if enc != self.cfg_encoding:
+ self._cfg_encoding = enc
+
+ self.post_init()
+ self._read_config()
+
+ # -----------------------------------------------------------
+ @property
+ def need_config_file(self):
+ """
+ hide command line parameter --default-config and
+ don't execute generation of default config
+ """
+ return getattr(self, '_need_config_file', False)
+
+ # -----------------------------------------------------------
+ @property
+ def cfg_encoding(self):
+ """The encoding character set of the configuration files."""
+ return self._cfg_encoding
+
+ # -----------------------------------------------------------
+ @property
+ def cfg_dir(self):
+ """The directory containing the configuration files."""
+ return self._cfg_dir
+
+ # -----------------------------------------------------------
+ @property
+ def cfg_stem(self):
+ """The basename of the configuration file without any file extension."""
+ return self._cfg_stem
+
+ # -------------------------------------------------------------------------
+ def as_dict(self, short=True):
+ """
+ Transforms the elements of the object into a dict
+
+ @param short: don't include local properties in resulting dict.
+ @type short: bool
+
+ @return: structure as dict
+ @rtype: dict
+ """
+
+ res = super(PpConfigApplication, self).as_dict(short=short)
+ res['need_config_file'] = self.need_config_file
+ res['cfg_encoding'] = self.cfg_encoding
+ res['cfg_dir'] = self.cfg_dir
+ res['cfg_stem'] = self.cfg_stem
+
+ return res
+
+ # -------------------------------------------------------------------------
+ def init_arg_parser(self):
+ """
+ Method to initiate the argument parser.
+
+ This method should be explicitely called by all init_arg_parser()
+ methods in descendant classes.
+ """
+ self.arg_parser.add_argument(
+ "-C", "--cfgfile", "--cfg-file", "--config",
+ metavar="FILE",
+ dest="cfg_file",
+ help="Configuration file to use additional to the standard configuration files.",
+ )
+
+ self.arg_parser.add_argument(
+ "--cfg-encoding",
+ metavar="ENCODING",
+ dest="cfg_encoding",
+ default=self.cfg_encoding,
+ help=("The encoding character set of the configuration files "
+ "(default: %(default)r)."),
+ )
+
+ # -------------------------------------------------------------------------
+ def init_cfgfiles(self):
+ """Method to generate the self.cfg_files list."""
+
+ self.cfg_files = []
+ cfg_basename = '{}.ini'.format(self.cfg_stem)
+
+ # add /etc/app/app.ini or $VIRTUAL_ENV/etc/app/app.ini
+ etc_dir = os.sep + 'etc'
+ if 'VIRTUAL_ENV' in os.environ:
+ etc_dir = os.path.join(os.environ['VIRTUAL_ENV'], 'etc')
+ syscfg_fn = None
+ if self.cfg_dir:
+ syscfg_fn = os.path.join(etc_dir, self.cfg_dir, cfg_basename)
+ else:
+ syscfg_fn = os.path.join(etc_dir, cfg_basename)
+ self.cfg_files.append(syscfg_fn)
+
+ # add <WORKDIR>/etc/app.ini
+ mod_dir = os.path.dirname(__file__)
+ work_dir = os.path.abspath(os.path.join(mod_dir, '..'))
+ work_etc_dir = os.path.join(work_dir, 'etc')
+ if self.verbose > 1:
+ LOG.debug("Searching for {!r} ...".format(work_etc_dir))
+ self.cfg_files.append(os.path.join(work_etc_dir, cfg_basename))
+
+ # add $HOME/.config/app.ini
+ usercfg_fn = None
+ user_cfg_dir = os.path.expanduser('~/.config')
+ if user_cfg_dir:
+ if self.cfg_dir:
+ user_cfg_dir = os.path.join(user_cfg_dir, self.cfg_dir)
+ if self.verbose > 1:
+ LOG.debug("user_cfg_dir: {!r}".format(user_cfg_dir))
+ usercfg_fn = os.path.join(user_cfg_dir, cfg_basename)
+ self.cfg_files.append(usercfg_fn)
+
+ # add a configfile given on command line with --cfg-file
+ cmdline_cfg = getattr(self.args, 'cfg_file', None)
+ if cmdline_cfg:
+ self.cfg_files.append(cmdline_cfg)
+
+ # -------------------------------------------------------------------------
+ def _read_config(self):
+
+ if self.verbose > 0:
+ LOG.debug("Reading config files with character set {!r} ...".format(
+ self.cfg_encoding))
+
+ open_opts = {}
+ if six.PY3 and self.cfg_encoding:
+ open_opts['encoding'] = self.cfg_encoding
+ open_opts['errors'] = 'surrogateescape'
+
+ found_cfg_files = False
+ for cfg_file in self.cfg_files:
+ if self.verbose > 2:
+ LOG.debug("Searching for {!r} ...".format(cfg_file))
+ if not os.path.isfile(cfg_file):
+ if self.verbose > 3:
+ LOG.debug("Config file {!r} not found.".format(cfg_file))
+ continue
+ if self.verbose > 1:
+ LOG.debug("Reading {!r} ...".format(cfg_file))
+ found_cfg_files = True
+
+ config = configparser.ConfigParser()
+ try:
+ with open(cfg_file, 'r', **open_opts) as fh:
+ stream = StringIO("[default]\n" + fh.read())
+ if six.PY2:
+ config.readfp(stram)
+ else:
+ config.read_file(stram)
+ except ConfigParseError as e:
+ msg = "Wrong configuration in {!r} found: ".format(cfg_file)
+ msg += str(e)
+ self.handle_error(msg, "Configuration error")
+ continue
+
+# =============================================================================
+
+if __name__ == "__main__":
+
+ pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@summary: The module provides an object class with a dict, which can
+ be updated in a recursive way.
+ It is originated by Jannis Andrija Schnitzer::
+ https://gist.github.com/114831
+"""
+
+# Standard modules
+# import sys
+# import os
+import logging
+
+__author__ = 'jannis@itisme.org (Jannis Andrija Schnitzer)'
+__copyright__ = '(c) 2009 Jannis Andrija Schnitzer'
+__contact__ = 'jannis@itisme.org'
+__version__ = '0.2.1'
+__license__ = 'GPL3'
+
+log = logging.getLogger(__name__)
+
+
+# =============================================================================
+class RecursiveDictionary(dict):
+ """RecursiveDictionary provides the methods rec_update and iter_rec_update
+ that can be used to update member dictionaries rather than overwriting
+ them."""
+
+ # -------------------------------------------------------------------------
+ def rec_update(self, other, **third):
+ """Recursively update the dictionary with the contents of other and
+ third like dict.update() does - but don't overwrite sub-dictionaries.
+ Example:
+ >>> d = RecursiveDictionary({'foo': {'bar': 42}})
+ >>> d.rec_update({'foo': {'baz': 36}})
+ >>> d
+ {'foo': {'baz': 36, 'bar': 42}}
+ """
+
+ try:
+ iterator = iter(other.items())
+ except AttributeError:
+ iterator = other
+
+ self.iter_rec_update(iterator)
+ self.iter_rec_update(iter(third.items()))
+
+ # -------------------------------------------------------------------------
+ def iter_rec_update(self, iterator):
+ for (key, value) in iterator:
+ if key in self and \
+ isinstance(self[key], dict) and isinstance(value, dict):
+ self[key] = RecursiveDictionary(self[key])
+ self[key].rec_update(value)
+ else:
+ self[key] = value
+
+ # -------------------------------------------------------------------------
+ def __repr__(self):
+ return super(self.__class__, self).__repr__()
+
+# =============================================================================
+
+if __name__ == "__main__":
+
+ pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4