--- /dev/null
+#!/bin/env python3
+# -*- coding: utf-8 -*-
+
+__version__ = '0.1.1'
+
+# vim: ts=4 et list
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@summary: additional logging formatter for colored output via console
+"""
+
+# Standard modules
+import logging
+# import os.path
+# import sys
+import copy
+
+# Third party modules
+
+# Own modules
+
+# import pb_provisioning.common
+
+# from pb_provisioning.common import to_unicode_or_bust, to_utf8_or_bust
+
+__version__ = '0.1.3'
+
+# =============================================================================
+# Color coding module variables and helper functions
+
+COLOR_CODE = {
+ 'ENDC': 0, # RESET COLOR
+ 'BOLD': 1,
+ 'UNDERLINE': 4,
+ 'BLINK': 5,
+ 'INVERT': 7,
+ 'CONCEALD': 8,
+ 'STRIKE': 9,
+ 'GREY30': 90,
+ 'GREY40': 2,
+ 'GREY65': 37,
+ 'GREY70': 97,
+ 'GREY20_BG': 40,
+ 'GREY33_BG': 100,
+ 'GREY80_BG': 47,
+ 'GREY93_BG': 107,
+ 'DARK_RED': 31,
+ 'RED': 91,
+ 'RED_BG': 41,
+ 'LIGHT_RED_BG': 101,
+ 'DARK_YELLOW': 33,
+ 'YELLOW': 93,
+ 'YELLOW_BG': 43,
+ 'LIGHT_YELLOW_BG': 103,
+ 'DARK_BLUE': 34,
+ 'BLUE': 94,
+ 'BLUE_BG': 44,
+ 'LIGHT_BLUE_BG': 104,
+ 'DARK_MAGENTA': 35,
+ 'PURPLE': 95,
+ 'MAGENTA_BG': 45,
+ 'LIGHT_PURPLE_BG': 105,
+ 'DARK_CYAN': 36,
+ 'AUQA': 96,
+ 'AQUA': 96,
+ 'CYAN_BG': 46,
+ 'LIGHT_AUQA_BG': 106,
+ 'LIGHT_AQUA_BG': 106,
+ 'DARK_GREEN': 32,
+ 'GREEN': 92,
+ 'GREEN_BG': 42,
+ 'LIGHT_GREEN_BG': 102,
+ 'BLACK': 30,
+}
+
+
+# -----------------------------------------------------------------------------
+def termcode(num):
+ """
+ Output of an ANSII terminal code.
+ """
+
+ return('\033[%sm' % (num))
+
+
+# -----------------------------------------------------------------------------
+def colorstr(message, color):
+ """
+ Wrapper function to colorize the message.
+
+ @param message: The message to colorize
+ @type message: 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
+
+ """
+
+ tcode = ''
+ if isinstance(color, (list, tuple)):
+ for clr in color:
+ tcode += termcode(COLOR_CODE[clr])
+ else:
+ tcode = termcode(COLOR_CODE[color])
+
+ return tcode + message + termcode(COLOR_CODE['ENDC'])
+
+LOG = logging.getLogger(__name__)
+
+
+# =============================================================================
+class ColoredFormatter(logging.Formatter):
+ """
+ A variant of code found at:
+ http://stackoverflow.com/questions/384076/how-can-i-make-the-python-logging-output-to-be-colored
+ """
+
+ LEVEL_COLOR = {
+ 'DEBUG': None,
+ 'INFO': 'GREEN',
+ 'WARNING': 'YELLOW',
+ 'ERROR': ('BOLD', 'RED'),
+ 'CRITICAL': 'RED_BG',
+ }
+
+ # -------------------------------------------------------------------------
+ def __init__(self, fmt=None, datefmt=None):
+ """
+ Initialize the formatter with specified format strings.
+
+ Initialize the formatter either with the specified format string, or a
+ default. Allow for specialized date formatting with the optional
+ datefmt argument (if omitted, you get the ISO8601 format).
+ """
+
+ logging.Formatter.__init__(self, fmt, datefmt)
+
+ # -----------------------------------------------------------
+ @property
+ def color_debug(self):
+ """The color used to output debug messages."""
+ return self.LEVEL_COLOR['DEBUG']
+
+ @color_debug.setter
+ def color_debug(self, value):
+ self.LEVEL_COLOR['DEBUG'] = value
+
+ # -----------------------------------------------------------
+ @property
+ def color_info(self):
+ """The color used to output info messages."""
+ return self.LEVEL_COLOR['INFO']
+
+ @color_info.setter
+ def color_info(self, value):
+ self.LEVEL_COLOR['INFO'] = value
+
+ # -----------------------------------------------------------
+ @property
+ def color_warning(self):
+ """The color used to output warning messages."""
+ return self.LEVEL_COLOR['WARNING']
+
+ @color_warning.setter
+ def color_warning(self, value):
+ self.LEVEL_COLOR['WARNING'] = value
+
+ # -----------------------------------------------------------
+ @property
+ def color_error(self):
+ """The color used to output error messages."""
+ return self.LEVEL_COLOR['ERROR']
+
+ @color_error.setter
+ def color_error(self, value):
+ self.LEVEL_COLOR['ERROR'] = value
+
+ # -----------------------------------------------------------
+ @property
+ def color_critical(self):
+ """The color used to output critical messages."""
+ return self.LEVEL_COLOR['CRITICAL']
+
+ @color_critical.setter
+ def color_critical(self, value):
+ self.LEVEL_COLOR['CRITICAL'] = value
+
+ # -------------------------------------------------------------------------
+ def format(self, record):
+ """
+ Format the specified record as text.
+ """
+
+ record = copy.copy(record)
+ levelname = record.levelname
+
+ if levelname in self.LEVEL_COLOR:
+
+ record.name = colorstr(record.name, 'BOLD')
+ record.filename = colorstr(record.filename, 'BOLD')
+ record.module = colorstr(record.module, 'BOLD')
+ record.funcName = colorstr(record.funcName, 'BOLD')
+ record.pathname = colorstr(record.pathname, 'BOLD')
+ record.processName = colorstr(record.processName, 'BOLD')
+ record.threadName = colorstr(record.threadName, 'BOLD')
+
+ if self.LEVEL_COLOR[levelname] is not None:
+ record.levelname = colorstr(
+ levelname, self.LEVEL_COLOR[levelname])
+ record.msg = colorstr(record.msg, self.LEVEL_COLOR[levelname])
+
+ return logging.Formatter.format(self, record)
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+ pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
--- /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 common used functions.
+"""
+
+# Standard modules
+import sys
+import os
+import logging
+import re
+import pprint
+import platform
+
+# Third party modules
+import six
+
+# Own modules
+
+__version__ = '0.2.1'
+
+log = logging.getLogger(__name__)
+
+# =============================================================================
+def pp(value, indent=4, width=99, depth=None):
+ """
+ Returns a pretty print string of the given value.
+
+ @return: pretty print string
+ @rtype: str
+ """
+
+ pretty_printer = pprint.PrettyPrinter(
+ indent=indent, width=width, depth=depth)
+ return pretty_printer.pformat(value)
+
+
+# =============================================================================
+def terminal_can_colors(debug=False):
+ """
+ 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
+
+ """
+
+ cur_term = ''
+ if 'TERM' in os.environ:
+ cur_term = os.environ['TERM'].lower().strip()
+
+ colored_term_list = (
+ r'ansi',
+ r'linux.*',
+ r'screen.*',
+ r'[xeak]term.*',
+ r'gnome.*',
+ r'rxvt.*',
+ r'interix',
+ )
+ term_pattern = r'^(?:' + r'|'.join(colored_term_list) + r')$'
+ re_term = re.compile(term_pattern)
+
+ ansi_term = False
+ env_term_has_colors = False
+
+ if cur_term:
+ if cur_term == 'ansi':
+ env_term_has_colors = True
+ ansi_term = True
+ elif re_term.search(cur_term):
+ env_term_has_colors = True
+ if debug:
+ sys.stderr.write(
+ "ansi_term: %r, env_term_has_colors: %r\n" % (
+ ansi_term, env_term_has_colors))
+
+ has_colors = False
+ if env_term_has_colors:
+ has_colors = True
+ for handle in [sys.stdout, sys.stderr]:
+ if (hasattr(handle, "isatty") and handle.isatty()):
+ if debug:
+ sys.stderr.write("%s is a tty.\n" % (handle.name))
+ if (platform.system() == 'Windows' and not ansi_term):
+ if debug:
+ sys.stderr.write("platform is Windows and not ansi_term.\n")
+ has_colors = False
+ else:
+ if debug:
+ sys.stderr.write("%s is not a tty.\n" % (handle.name))
+ if ansi_term:
+ pass
+ else:
+ has_colors = False
+
+ return has_colors
+
+
+# =============================================================================
+def to_unicode(obj, encoding='utf-8'):
+
+ do_decode = False
+ if six.PY2:
+ if isinstance(obj, str):
+ do_decode = True
+ else:
+ if isinstance(obj, bytes):
+ do_decode = True
+
+ if do_decode:
+ obj = obj.decode(encoding)
+
+ return obj
+
+
+# =============================================================================
+def to_utf8(obj):
+
+ return encode_or_bust(obj, 'utf-8')
+
+
+# =============================================================================
+def encode_or_bust(obj, encoding='utf-8'):
+
+ do_encode = False
+ if six.PY2:
+ if isinstance(obj, unicode):
+ do_encode = True
+ else:
+ if isinstance(obj, str):
+ do_encode = True
+
+ if do_encode:
+ obj = obj.encode(encoding)
+
+ return obj
+
+
+# =============================================================================
+def to_bytes(obj, encoding='utf-8'):
+ "Wrapper for encode_or_bust()"
+
+ return encode_or_bust(obj, encoding)
+
+
+# =============================================================================
+def to_str(obj, encoding='utf-8'):
+ """
+ Transformes the given string-like object into the str-type according
+ to the current Python version.
+ """
+
+ if six.PY2:
+ return encode_or_bust(obj, encoding)
+ else:
+ return to_unicode_or_bust(obj, encoding)
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+
+ pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@summary: module for some common used error classes
+"""
+
+# Standard modules
+import errno
+
+
+__version__ = '0.2.2'
+
+# =============================================================================
+class PpError(Exception):
+ """
+ Base error class for all other self defined exceptions.
+ """
+
+ pass
+
+
+# =============================================================================
+class PpAppError(PpError):
+
+ pass
+
+
+# =============================================================================
+class InvalidMailAddressError(PpError):
+ """Class for a exception in case of a malformed mail address."""
+
+ # -------------------------------------------------------------------------
+ def __init__(self, address, msg=None):
+
+ self.address = address
+ self.msg = msg
+
+ # -------------------------------------------------------------------------
+ def __str__(self):
+
+ msg = "Wrong mail address {a!r} ({c})".format(
+ a=self.address, c=self.address.__class__.__name__)
+ if self.msg:
+ msg += ': ' + self.msg
+ else:
+ msg += '.'
+ return msg
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+ pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2010 - 2016 by Frank Brehm, ProfitBricks GmbH, Berlin
+@summary: The module for the MailAddress object.
+"""
+
+# Standard modules
+import sys
+import os
+import logging
+import re
+
+# Own modules
+from pp_lib.errors import InvalidMailAddressError
+
+from pp_lib.common import to_str
+
+__version__ = '0.2.2'
+log = logging.getLogger(__name__)
+
+
+# =============================================================================
+class MailAddress(object):
+ """
+ Class for encapsulating a mail simple address.
+ """
+
+ pattern_valid_domain = r'@((?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z][a-z]+)$'
+
+ pattern_valid_user = r'^([a-z0-9][a-z0-9_\-\.\+\&@]*[a-z0-9]'
+ pattern_valid_user += r'(?:\+[a-z0-9][a-z0-9_\-\.]*[a-z0-9])*)'
+
+ pattern_valid_address = pattern_valid_user + pattern_valid_domain
+
+ re_valid_user = re.compile(pattern_valid_user + r'$', re.IGNORECASE)
+ re_valid_domain = re.compile(r'^' + pattern_valid_domain, re.IGNORECASE)
+ re_valid_address = re.compile(pattern_valid_address, re.IGNORECASE)
+
+ verbose = 0
+
+ # -------------------------------------------------------------------------
+ @classmethod
+ def valid_address(cls, address, raise_on_failure=False):
+
+ if not address:
+ e = InvalidMailAddressError(address, "Empty address.")
+ if raise_on_failure:
+ raise e
+ elif cls.verbose > 2:
+ log.debug(str(e))
+ return False
+
+ addr = to_str(address)
+ if not isinstance(addr, str):
+ e = InvalidMailAddressError(address, "Wrong type.")
+ if raise_on_failure:
+ raise e
+ elif cls.verbose > 2:
+ log.debug(str(e))
+ return False
+
+ if cls.re_valid_address.search(addr):
+ return True
+
+ e = InvalidMailAddressError(address, "Invalid address.")
+ if raise_on_failure:
+ raise e
+ elif cls.verbose > 2:
+ log.debug(str(e))
+ return False
+
+ # -------------------------------------------------------------------------
+ def __init__(self, user=None, domain=None):
+
+ self._user = ''
+ self._domain = ''
+
+ if not domain:
+ if user:
+ addr = to_str(user)
+ if self.valid_address(addr):
+ match = self.re_valid_address.search(addr)
+ self._user = match.group(1)
+ self._domain = match.group(2)
+ return
+ match = self.re_valid_domain.search(addr)
+ if match:
+ self._domain = match.group(1)
+ return
+ self._user = addr
+ return
+
+ self._user = to_str(user)
+ self._domain = to_str(domain)
+
+ # -----------------------------------------------------------
+ @property
+ def user(self):
+ """The user part of the address."""
+ if self._user is None:
+ return ''
+ return self._user
+
+ # -----------------------------------------------------------
+ @property
+ def domain(self):
+ """The domain part of the address."""
+ if self._domain is None:
+ return ''
+ return self._domain
+
+ # -------------------------------------------------------------------------
+ def __str__(self):
+
+ if not self.user and not self.domain:
+ return ''
+
+ if not self.domain:
+ return self.user
+
+ if not self.user:
+ return '@' + self.domain
+
+ return self.user + '@' + self.domain
+
+ # -------------------------------------------------------------------------
+ def str_for_access(self):
+
+ if not self.user and not self.domain:
+ return None
+
+ if not self.domain:
+ return self.user + '@'
+
+ if not self.user:
+ return self.domain
+
+ return self.user + '@' + self.domain
+
+ # -------------------------------------------------------------------------
+ def __repr__(self):
+ """Typecasting into a string for reproduction."""
+
+ out = "<%s(" % (self.__class__.__name__)
+
+ fields = []
+ fields.append("user={!r}".format(self.user))
+ fields.append("domain={!r}".format(self.domain))
+
+ out += ", ".join(fields) + ")>"
+ return out
+
+
+ # -------------------------------------------------------------------------
+ def __hash__(self):
+ return hash(str(self).lower())
+
+ # -------------------------------------------------------------------------
+ def __eq__(self, other):
+
+ if not isinstance(other, MailAddress):
+ if other is None:
+ return False
+ return str(self).lower() == str(other).lower()
+
+ if not self.user:
+ if other.user:
+ return False
+ if not self.domain:
+ if other.domain:
+ return False
+ return True
+ if not other.domain:
+ return False
+ if self.domain.lower() == other.domain.lower():
+ return True
+ return False
+
+ if not self.domain:
+ if other.domain:
+ return False
+ if not other.user:
+ return False
+ if self.user.lower() == other.user.lower():
+ return True
+ return False
+
+ if not other.user:
+ return False
+ if not other.domain:
+ return False
+ if self.domain.lower() != other.domain.lower():
+ return False
+ if self.user.lower() != other.user.lower():
+ return False
+
+ return True
+
+ # -------------------------------------------------------------------------
+ def __ne__(self, other):
+
+ if self == other:
+ return False
+ return True
+
+ # -------------------------------------------------------------------------
+ def __lt__(self, other):
+
+ if not isinstance(other, MailAddress):
+ if other is None:
+ return False
+ return str(self).lower() < str(other).lower()
+
+ if not self.user:
+ if not self.domain:
+ if other.domain:
+ return False
+ return True
+ if not other.domain:
+ return False
+ if self.domain.lower() != other.domain.lower():
+ return self.domain.lower() < other.domain.lower()
+ if other.user:
+ return False
+ return True
+
+ if not self.domain:
+ if other.domain:
+ return True
+ if not other.user:
+ return False
+ if self.user.lower() != other.user.lower():
+ return self.user.lower() < other.user.lower()
+ return False
+
+ if not other.domain:
+ return False
+ if not other.user:
+ return False
+
+ if self.domain.lower() != other.domain.lower():
+ return self.domain.lower() < other.domain.lower()
+ if self.user.lower() != other.user.lower():
+ return self.user.lower() < other.user.lower()
+
+ return False
+
+ # -------------------------------------------------------------------------
+ def __gt__(self, other):
+
+ if not isinstance(other, MailAddress):
+ return NotImplemented
+
+ if self < other:
+ return False
+ return True
+
+ # -------------------------------------------------------------------------
+ def __copy__(self):
+ "Implementing a wrapper for copy.copy()."
+
+ addr = MailAddress()
+ addr._user = self.user
+ addr._domain = self.domain
+ return addr
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+
+ pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list