]> Frank Brehm's Git Trees - pixelpark/admin-tools.git/commitdiff
Adding format-du.py, made pp_lib/app.py functional
authorFrank Brehm <frank.brehm@pixelpark.com>
Tue, 14 Mar 2017 10:17:47 +0000 (11:17 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Tue, 14 Mar 2017 10:17:47 +0000 (11:17 +0100)
format-du.py [new file with mode: 0755]
pp_lib/app.py
pp_lib/obj.py

diff --git a/format-du.py b/format-du.py
new file mode 100755 (executable)
index 0000000..42d656f
--- /dev/null
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+
+# Standard modules
+import sys
+import os
+import logging
+
+# own modules:
+cur_dir = os.getcwd()
+base_dir = cur_dir
+
+if sys.argv[0] != '' and sys.argv[0] != '-c':
+    cur_dir = os.path.dirname(sys.argv[0])
+if os.path.exists(os.path.join(cur_dir, 'pp_lib')):
+    sys.path.insert(0, os.path.abspath(cur_dir))
+
+from pp_lib.common import pp
+
+from pp_lib.app import PpApplication
+
+log = logging.getLogger(__name__)
+
+__author__ = 'Frank Brehm <frank.brehm@pixelpark.com>'
+__copyright__ = '(C) 2017 by Frank Brehm, Pixelpark GmbH, Berlin'
+
+
+app = PpApplication()
+#app.initialized = True
+
+if app.verbose > 2:
+    print("{c}-Object:\n{a}".format(c=app.__class__.__name__, a=app))
+
+app()
+
+sys.exit(0)
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
+
+# vim: ts=4
index 749947c6c8784528ebfda9127a1ceee1c07654d1..00767a2cc257388b0172a9c0c2ce505a2dbaeff8 100644 (file)
@@ -34,9 +34,9 @@ except (SystemError, ImportError):
     from errors import FunctionNotImplementedError, PpAppError
 
 try:
-    from .common import pp, terminal_can_colors
+    from .common import pp, terminal_can_colors, to_bytes
 except (SystemError, ImportError):
-    from common import pp, terminal_can_colors
+    from common import pp, terminal_can_colors, to_bytes
 
 try:
     from .colored import ColoredFormatter, colorstr
@@ -58,7 +58,613 @@ class PpApplication(PpBaseObject):
     Class for the application objects.
     """
 
-    pass
+    re_prefix = re.compile(r'^[a-z0-9][a-z0-9_]*$', re.IGNORECASE)
+    re_anum = re.compile(r'[^A-Z0-9_]+', re.IGNORECASE)
+
+    # -------------------------------------------------------------------------
+    def __init__(
+        self, appname=None, verbose=0, version=__version__, base_dir=None,
+            initialized=False, usage=None, description=None,
+            argparse_epilog=None, argparse_prefix_chars='-', env_prefix=None):
+
+        self.arg_parser = None
+        """
+        @ivar: argparser object to parse commandline parameters
+        @type: argparse.ArgumentParser
+        """
+
+        self.args = None
+        """
+        @ivar: an object containing all commandline parameters
+               after parsing them
+        @type: Namespace
+        """
+
+        self._exit_value = 0
+        """
+        @ivar: return value of the application for exiting with sys.exit().
+        @type: int
+        """
+
+        self._usage = usage
+        """
+        @ivar: usage text used on argparse
+        @type: str
+        """
+
+        self._description = description
+        """
+        @ivar: a short text describing the application
+        @type: str
+        """
+
+        self._argparse_epilog = argparse_epilog
+        """
+        @ivar: an epilog displayed at the end of the argparse help screen
+        @type: str
+        """
+
+        self._argparse_prefix_chars = argparse_prefix_chars
+        """
+        @ivar: The set of characters that prefix optional arguments.
+        @type: str
+        """
+
+        self._terminal_has_colors = False
+        """
+        @ivar: flag, that the current terminal understands color ANSI codes
+        @type: bool
+        """
+
+        self.env = {}
+        """
+        @ivar: a dictionary with all application specifiv environment variables,
+               they will detected by the env_prefix property of this object,
+               and their names will transformed before saving their values in
+               self.env by removing the env_prefix from the variable name.
+        @type: dict
+        """
+
+        self._env_prefix = None
+        """
+        @ivar: a prefix for environment variables to detect them and to assign
+               their transformed names and their values in self.env
+        @type: str
+        """
+
+        super(PpApplication, self).__init__(
+            appname=appname,
+            verbose=verbose,
+            version=version,
+            base_dir=base_dir,
+            initialized=False,
+        )
+
+        if env_prefix:
+            ep = str(env_prefix).strip()
+            if not ep:
+                msg = "Invalid env_prefix {!r} given - it may not be empty.".format(env_prefix)
+                raise PpAppError(msg)
+            match = self.re_prefix.search(ep)
+            if not match:
+                msg = (
+                    "Invalid characters found in env_prefix {!r}, only "
+                    "alphanumeric characters and digits and underscore "
+                    "(this not as the first character) are allowed.").format(env_prefix)
+                raise PpAppError(msg)
+            self._env_prefix = ep
+        else:
+            ep = self.appname.upper() + '_'
+            self._env_prefix = self.re_anum.sub('_', ep)
+
+        self._init_arg_parser()
+        self._perform_arg_parser()
+
+        self._init_env()
+        self._perform_env()
+
+    # -----------------------------------------------------------
+    @property
+    def exit_value(self):
+        """The return value of the application for exiting with sys.exit()."""
+        return self._exit_value
+
+    @exit_value.setter
+    def exit_value(self, value):
+        v = int(value)
+        if v >= 0:
+            self._exit_value = v
+        else:
+            LOG.warn("Wrong exit_value {!r}, must be >= 0".format(value))
+
+    # -----------------------------------------------------------
+    @property
+    def exitvalue(self):
+        """The return value of the application for exiting with sys.exit()."""
+        return self._exit_value
+
+    @exitvalue.setter
+    def exitvalue(self, value):
+        self.exit_value = value
+
+    # -----------------------------------------------------------
+    @property
+    def usage(self):
+        """The usage text used on argparse."""
+        return self._usage
+
+    # -----------------------------------------------------------
+    @property
+    def description(self):
+        """A short text describing the application."""
+        return self._description
+
+    # -----------------------------------------------------------
+    @property
+    def argparse_epilog(self):
+        """An epilog displayed at the end of the argparse help screen."""
+        return self._argparse_epilog
+
+    # -----------------------------------------------------------
+    @property
+    def argparse_prefix_chars(self):
+        """The set of characters that prefix optional arguments."""
+        return self._argparse_prefix_chars
+
+    # -----------------------------------------------------------
+    @property
+    def terminal_has_colors(self):
+        """A flag, that the current terminal understands color ANSI codes."""
+        return self._terminal_has_colors
+
+    # -----------------------------------------------------------
+    @property
+    def env_prefix(self):
+        """A prefix for environment variables to detect them."""
+        return self._env_prefix
+
+    # -----------------------------------------------------------
+    @property
+    def usage_term(self):
+        """The localized version of 'usage: '"""
+        return 'Usage: '
+
+    # -----------------------------------------------------------
+    @property
+    def usage_term_len(self):
+        """The length of the localized version of 'usage: '"""
+        return len(self.usage_term)
+
+    # -------------------------------------------------------------------------
+    def exit(self, retval=-1, msg=None, trace=False):
+        """
+        Universal method to call sys.exit(). If fake_exit is set, a
+        FakeExitError exception is raised instead (useful for unittests.)
+
+        @param retval: the return value to give back to theoperating system
+        @type retval: int
+        @param msg: a last message, which should be emitted before exit.
+        @type msg: str
+        @param trace: flag to output a stack trace before exiting
+        @type trace: bool
+
+        @return: None
+
+        """
+
+        retval = int(retval)
+        trace = bool(trace)
+
+        root_log = logging.getLogger()
+        has_handlers = False
+        if root_log.handlers:
+            has_handlers = True
+
+        if msg:
+            if has_handlers:
+                if retval:
+                    LOG.error(msg)
+                else:
+                    LOG.info(msg)
+            if not has_handlers:
+                if hasattr(sys.stderr, 'buffer'):
+                    sys.stderr.buffer.write(str(msg) + "\n")
+                else:
+                    sys.stderr.write(str(msg) + "\n")
+
+        if trace:
+            if has_handlers:
+                if retval:
+                    LOG.error(traceback.format_exc())
+                else:
+                    LOG.info(traceback.format_exc())
+            if self.use_stderr or not has_handlers:
+                traceback.print_exc()
+
+        sys.exit(retval)
+
+    # -------------------------------------------------------------------------
+    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(PpApplication, self).as_dict(short=short)
+        res['exit_value'] = self.exit_value
+        res['usage'] = self.usage
+        res['description'] = self.description
+        res['argparse_epilog'] = self.argparse_epilog
+        res['argparse_prefix_chars'] = self.argparse_prefix_chars
+        res['terminal_has_colors'] = self.terminal_has_colors
+        res['env_prefix'] = self.env_prefix
+
+        return res
+
+    # -------------------------------------------------------------------------
+    def init_logging(self):
+        """
+        Initialize the logger object.
+        It creates a colored loghandler with all output to STDERR.
+        Maybe overridden in descendant classes.
+
+        @return: None
+        """
+
+        root_log = logging.getLogger()
+        root_log.setLevel(logging.INFO)
+        if self.verbose:
+            root_log.setLevel(logging.DEBUG)
+
+        # create formatter
+        format_str = ''
+        if self.verbose > 1:
+            format_str = '[%(asctime)s]: '
+        format_str += self.appname + ': '
+        if self.verbose:
+            if self.verbose > 1:
+                format_str += '%(name)s(%(lineno)d) %(funcName)s() '
+            else:
+                format_str += '%(name)s '
+        format_str += '%(levelname)s - %(message)s'
+        formatter = None
+        if self.terminal_has_colors:
+            formatter = ColoredFormatter(format_str)
+        else:
+            formatter = logging.Formatter(format_str)
+
+        # create log handler for console output
+        lh_console = logging.StreamHandler(sys.stderr)
+        if self.verbose:
+            lh_console.setLevel(logging.DEBUG)
+        else:
+            lh_console.setLevel(logging.INFO)
+        lh_console.setFormatter(formatter)
+
+        root_log.addHandler(lh_console)
+
+        return
+
+    # -------------------------------------------------------------------------
+    def terminal_can_color(self):
+        """
+        Method to detect, whether the current terminal (stdout and stderr)
+        is able to perform ANSI color sequences.
+
+        @return: both stdout and stderr can perform ANSI color sequences
+        @rtype: bool
+
+        """
+
+        term_debug = False
+        if self.verbose > 3:
+            term_debug = True
+        return terminal_can_colors(debug=term_debug)
+
+    # -------------------------------------------------------------------------
+    def post_init(self):
+        """
+        Method to execute before calling run(). Here could be done some
+        finishing actions after reading in commandline parameters,
+        configuration a.s.o.
+
+        This method could be overwritten by descendant classes, these
+        methhods should allways include a call to post_init() of the
+        parent class.
+
+        """
+
+        self.perform_arg_parser()
+        self.init_logging()
+
+        self.initialized = True
+
+    # -------------------------------------------------------------------------
+    def pre_run(self):
+        """
+        Dummy function to run before the main routine.
+        Could be overwritten by descendant classes.
+
+        """
+
+        if self.verbose > 1:
+            LOG.info("executing pre_run() ...")
+
+    # -------------------------------------------------------------------------
+    def _run(self):
+        """
+        Dummy function as main routine.
+
+        MUST be overwritten by descendant classes.
+
+        """
+
+        raise FunctionNotImplementedError('_run', self.__class__.__name__)
+
+    # -------------------------------------------------------------------------
+    def __call__(self):
+        """
+        Helper method to make the resulting object callable, e.g.::
+
+            app = PBApplication(...)
+            app()
+
+        @return: None
+
+        """
+
+        self.run()
+
+    # -------------------------------------------------------------------------
+    def run(self):
+        """
+        The visible start point of this object.
+
+        @return: None
+
+        """
+
+        if not self.initialized:
+            self.handle_error(
+                "The application is not completely initialized.", '', True)
+            self.exit(9)
+
+        try:
+            self.pre_run()
+        except Exception as e:
+            self.handle_error(str(e), e.__class__.__name__, True)
+            self.exit(98)
+
+        if not self.initialized:
+            raise PpApplicationError(
+                "Object {!r} seems not to be completely initialized.".format(
+                    self.__class__.__name__))
+
+        try:
+            self._run()
+        except Exception as e:
+            self.handle_error(str(e), e.__class__.__name__, True)
+            self.exit_value = 99
+
+        if self.verbose > 1:
+            LOG.info("Ending.")
+
+        try:
+            self.post_run()
+        except Exception as e:
+            self.handle_error(str(e), e.__class__.__name__, True)
+            self.exit_value = 97
+
+        self.exit(self.exit_value)
+
+    # -------------------------------------------------------------------------
+    def post_run(self):
+        """
+        Dummy function to run after the main routine.
+        Could be overwritten by descendant classes.
+
+        """
+
+        if self.verbose > 1:
+            LOG.info("executing post_run() ...")
+
+    # -------------------------------------------------------------------------
+    def _init_arg_parser(self):
+        """
+        Local called method to initiate the argument parser.
+
+        @raise PBApplicationError: on some errors
+
+        """
+
+        self.arg_parser = argparse.ArgumentParser(
+            prog=self.appname,
+            description=self.description,
+            usage=self.usage,
+            epilog=self.argparse_epilog,
+            prefix_chars=self.argparse_prefix_chars,
+            add_help=False,
+        )
+
+        self.init_arg_parser()
+
+        general_group = self.arg_parser.add_argument_group('General options')
+        general_group.add_argument(
+            '--color',
+            action="store",
+            dest='color',
+            const='yes',
+            default='auto',
+            nargs='?',
+            choices=['yes', 'no', 'auto'],
+            help="Use colored output for messages.",
+        )
+        general_group.add_argument(
+            "-v", "--verbose",
+            action="count",
+            dest='verbose',
+            help='Increase the verbosity level',
+        )
+        general_group.add_argument(
+            "-h", "--help",
+            action='help',
+            dest='help',
+            help='Show this help message and exit'
+        )
+        general_group.add_argument(
+            "--usage",
+            action='store_true',
+            dest='usage',
+            help="Display brief usage message and exit"
+        )
+        general_group.add_argument(
+            "-V", '--version',
+            action='version',
+            version='Version of %(prog)s: {}'.format(self.version),
+            help="Show program's version number and exit"
+        )
+
+    # -------------------------------------------------------------------------
+    def init_arg_parser(self):
+        """
+        Public available method to initiate the argument parser.
+
+        Note::
+             avoid adding the general options '--verbose', '--help', '--usage'
+             and '--version'. These options are allways added after executing
+             this method.
+
+        Descendant classes may override this method.
+
+        """
+
+        pass
+
+    # -------------------------------------------------------------------------
+    def _perform_arg_parser(self):
+        """
+        Underlaying method for parsing arguments.
+        """
+
+        self.args = self.arg_parser.parse_args()
+
+        if self.args.usage:
+            self.arg_parser.print_usage(sys.stdout)
+            self.exit(0)
+
+        if self.args.verbose is not None and self.args.verbose > self.verbose:
+            self.verbose = self.args.verbose
+
+        if self.args.color == 'yes':
+            self._terminal_has_colors = True
+        elif self.args.color == 'no':
+            self._terminal_has_colors = False
+        else:
+            self._terminal_has_colors = self.terminal_can_color()
+
+    # -------------------------------------------------------------------------
+    def perform_arg_parser(self):
+        """
+        Public available method to execute some actions after parsing
+        the command line parameters.
+
+        Descendant classes may override this method.
+        """
+
+        pass
+
+    # -------------------------------------------------------------------------
+    def _init_env(self):
+        """
+        Initialization of self.env by application specific environment
+        variables.
+
+        It calls self.init_env(), after it has done his job.
+
+        """
+
+        for (key, value) in list(os.environ.items()):
+
+            if not key.startswith(self.env_prefix):
+                continue
+
+            newkey = key.replace(self.env_prefix, '', 1)
+            self.env[newkey] = value
+
+        self.init_env()
+
+    # -------------------------------------------------------------------------
+    def init_env(self):
+        """
+        Public available method to initiate self.env additional to the implicit
+        initialization done by this module.
+        Maybe it can be used to import environment variables, their
+        names not starting with self.env_prefix.
+
+        Currently a dummy method, which ca be overriden by descendant classes.
+
+        """
+
+        pass
+
+    # -------------------------------------------------------------------------
+    def _perform_env(self):
+        """
+        Method to do some useful things with the found environment.
+
+        It calls self.perform_env(), after it has done his job.
+
+        """
+
+        # try to detect verbosity level from environment
+        if 'VERBOSE' in self.env and self.env['VERBOSE']:
+            v = 0
+            try:
+                v = int(self.env['VERBOSE'])
+            except ValueError:
+                v = 1
+            if v > self.verbose:
+                self.verbose = v
+
+        self.perform_env()
+
+    # -------------------------------------------------------------------------
+    def perform_env(self):
+        """
+        Public available method to perform found environment variables after
+        initialization of self.env.
+
+        Currently a dummy method, which ca be overriden by descendant classes.
+
+        """
+
+        pass
+
+    # -------------------------------------------------------------------------
+    def colored(self, msg, color):
+        """
+        Wrapper function to colorize the message. Depending, whether the current
+        terminal can display ANSI colors, the message is colorized or not.
+
+        @param msg: The message to colorize
+        @type msg: str
+        @param color: The color to use, must be one of the keys of COLOR_CODE
+        @type color: str
+
+        @return: the colorized message
+        @rtype: str
+
+        """
+
+        if not self.terminal_has_colors:
+            return msg
+        return colorstr(msg, color)
+
 
 # =============================================================================
 
index 4e776406ca0dcd081b3e27826939a22238abf4b2..dc264efdcceb983a9043ab4041c3da56ee44e3bf 100644 (file)
@@ -19,9 +19,9 @@ import six
 
 # Own modules
 try:
-    from .common import pp
+    from .common import pp, to_bytes
 except (SystemError, ImportError):
-    from common import pp
+    from common import pp, to_bytes
 
 try:
     from .errors import PpError
@@ -83,7 +83,7 @@ class PpBaseObject(object):
         @type: int
         """
         if self._verbose < 0:
-            msg = _("Wrong verbose level %r, must be >= 0") % (verbose)
+            msg = "Wrong verbose level {!r}, must be >= 0".format(verbose)
             raise ValueError(msg)
 
         self._initialized = False
@@ -101,11 +101,11 @@ class PpBaseObject(object):
         """
         if base_dir:
             if not os.path.exists(base_dir):
-                msg = _("Base directory %r does not exists.") % (base_dir)
+                msg = "Base directory {!r} does not exists.".format(base_dir)
                 self.handle_error(msg)
                 self._base_dir = None
             elif not os.path.isdir(base_dir):
-                msg = _("Base directory %r is not a directory.") % (base_dir)
+                msg = "Base directory {!r} is not a directory.".format(base_dir)
                 self.handle_error(msg)
                 self._base_dir = None
         if not self._base_dir:
@@ -144,7 +144,7 @@ class PpBaseObject(object):
         if v >= 0:
             self._verbose = v
         else:
-            LOG.warn(_("Wrong verbose level %r, must be >= 0"), value)
+            LOG.warn("Wrong verbose level {!r}, must be >= 0".format(value))
 
     # -----------------------------------------------------------
     @property
@@ -167,10 +167,10 @@ class PpBaseObject(object):
         if value.startswith('~'):
             value = os.path.expanduser(value)
         if not os.path.exists(value):
-            msg = _("Base directory %r does not exists.") % (value)
+            msg = "Base directory {!r} does not exists.".format(value)
             LOG.error(msg)
         elif not os.path.isdir(value):
-            msg = _("Base directory %r is not a directory.") % (value)
+            msg = "Base directory {!r} is not a directory.".format(value)
             LOG.error(msg)
         else:
             self._base_dir = value
@@ -204,7 +204,7 @@ class PpBaseObject(object):
         return out
 
     # -------------------------------------------------------------------------
-    def as_dict(self, short=False):
+    def as_dict(self, short=True):
         """
         Transforms the elements of the object into a dict
 
@@ -221,7 +221,7 @@ class PpBaseObject(object):
             if short and key.startswith('_') and not key.startswith('__'):
                 continue
             val = self.__dict__[key]
-            if isinstance(val, PbBaseObject):
+            if isinstance(val, PpBaseObject):
                 res[key] = val.as_dict(short=short)
             else:
                 res[key] = val
@@ -261,7 +261,7 @@ class PpBaseObject(object):
         if error_message:
             msg += str(error_message)
         else:
-            msg += _('undefined error.')
+            msg += 'undefined error.'
 
         root_log = logging.getLogger()
         has_handlers = False
@@ -272,13 +272,12 @@ class PpBaseObject(object):
             LOG.error(msg)
             if do_traceback:
                 LOG.error(traceback.format_exc())
-
-        if not has_handlers:
+        else:
             curdate = datetime.datetime.now()
             curdate_str = "[" + curdate.isoformat(' ') + "]: "
             msg = curdate_str + msg + "\n"
             if hasattr(sys.stderr, 'buffer'):
-                sys.stderr.buffer.write(msg)
+                sys.stderr.buffer.write(to_bytes(msg))
             else:
                 sys.stderr.write(msg)
             if do_traceback:
@@ -313,13 +312,12 @@ class PpBaseObject(object):
 
         if has_handlers:
             LOG.info(msg)
-
-        if not has_handlers:
+        else:
             curdate = datetime.datetime.now()
             curdate_str = "[" + curdate.isoformat(' ') + "]: "
             msg = curdate_str + msg + "\n"
             if hasattr(sys.stderr, 'buffer'):
-                sys.stderr.buffer.write(msg)
+                sys.stderr.buffer.write(to_bytes(msg))
             else:
                 sys.stderr.write(msg)