Source code for AutoArchive._infrastructure.configuration.options

# options.py
#
# Project: AutoArchive
# License: GNU GPLv3
#
# Copyright (C) 2003 - 2021 Róbert Čerňanský



""":class:`Options`, :class:`OptionsUtils` and :class:`SpecialOptionTypes` static classes. :class:`Option` class
and :data:`ArchiverTypes` enum."""



__all__ = ["ArchiverTypes", "Options", "OptionsUtils", "Option", "SpecialOptionTypes"]



# {{{ INCLUDES

from abc import ABCMeta, abstractmethod
import os.path
from AutoArchive._infrastructure.py_additions import Enum

# }}} INCLUDES



# {{{ CONSTANTS

#: Archiver types.
ArchiverTypes = Enum(
    "Tar",
    "TarGz",
    "TarBz2",
    "TarXz",
    "TarZst",
    "TarInternal",
    "TarGzInternal",
    "TarBz2Internal")

# }}} CONSTANTS



# {{{ CLASSES

[docs]class Option: """Represents a configuration option. :param name: Option name. :type name: ``str`` :param optType: Type of the option. :type optType: ``type`` or ``str``""" def __init__(self, name, optType): self.__name = name self.__optType = optType def __str__(self): return self.__name def __repr__(self): return str.format("<Option {} of type {}>", self.__name, self._optType) @property def _optType(self): """Gets the option type. :rtype: ``type`` or ``str``""" return self.__optType
# all option type constants defined in this class has to have uppercase names; there can not be any other constants # defined that has uppercase names and are type of str, besides those representing option types
[docs]class SpecialOptionTypes(metaclass = ABCMeta): """Constants for special option types. Normally, options are of some standard type, such as ``int``, ``str``, etc. Some of them however, requires special handling for which the special option types are defined in this class. .. note:: It is not allowed to change values of these constants.""" # {{{ special option types constants #: A filesystem path. PATH = "path" # }}} special option types constants @abstractmethod def __init__(self): pass
# all option constants defined in this class has to have uppercase names; there can not be any other constants defined # that has uppercase names and are type of Option, besides those representing options
[docs]class Options(metaclass = ABCMeta): """Constants for configuration options. These constants should be used to access options in the :class:`.IConfiguration` implementation provided by the :term:`Configuration` :term:`component`. .. note:: It is not allowed to change values of these constants.""" # {{{ option constants # {{{ archiving related options #: Archiver type. Guaranteed to be defined. ARCHIVER = Option("archiver", ArchiverTypes) #: Compression strength level. COMPRESSION_LEVEL = Option("compression-level", int) #: Directory where the backup will be created. Guaranteed to be defined. DEST_DIR = Option("dest-dir", SpecialOptionTypes.PATH) #: If enabled, backups are overwritten at the start of creation. If disabled (default), backups are overwritten #: at the end of creation. Enabling this option can be useful with big backups and low free space on the backup #: volume. OVERWRITE_AT_START = Option("overwrite-at-start", bool) # }}} archiving related options # {{{ incremental archiving related options #: Incremental backup. INCREMENTAL = Option("incremental", bool) #: Backup level used in incremental archiving. LEVEL = Option("level", int) #: Turn on backup level restarting. RESTARTING = Option("restarting", bool) #: Maximal backup level. If reached, it will be restarted back to a lower level (which is typically level 1 but it #: depends on :attr:`MAX_RESTART_LEVEL_SIZE`). Guaranteed to be defined. RESTART_AFTER_LEVEL = Option("restart-after-level", int) #: Number of days after which the backup level is restarted. Similarly to :attr:`RESTART_AFTER_LEVEL` it will be #: restarted to level 1 or higher. RESTART_AFTER_AGE = Option("restart-after-age", int) #: Number of backup level restarts after which the level is restarted to 0. FULL_RESTART_AFTER_COUNT = Option("full-restart-after-count", int) #: Number of days after which the backup level is restarted to 0. FULL_RESTART_AFTER_AGE = Option("full-restart-after-age", int) #: Maximal percentage size of a :term:`backup` (of level > 0) to which level is allowed restart to. The size is #: percentage of size of the level 0 backup file. If a backup of particular level has its size bigger than #: defined percentage, restart to that level will not be allowed. MAX_RESTART_LEVEL_SIZE = Option("max-restart-level-size", int) #: Turn on removing backups of levels that are no longer valid due to the backup level restart. All backups of #: the backup level higher than the one currently being created will be removed. REMOVE_OBSOLETE_BACKUPS = Option("remove-obsolete-backups", bool) # }}} incremental archiving related options # {{{ options related to old backups keeping #: Turn on backup keeping. When a backup is about to be overwritten, it is renamed instead. KEEP_OLD_BACKUPS = Option("keep-old-backups", bool) #: Number of old backups to keep when :attr:`KEEP_OLD_BACKUPS` is enabled. Guaranteed to be defined. NUMBER_OF_OLD_BACKUPS = Option("number-of-old-backups", int) # }}} options related to old backups keeping # {{{ command execution options #: Arbitrary command that will be executed before backup creation for the set of selected archives. COMMAND_BEFORE_ALL_BACKUPS = Option("command-before-all-backups", str) #: Arbitrary command that will be executed after backup creation for the set of selected archives. COMMAND_AFTER_ALL_BACKUPS = Option("command-after-all-backups", str) #: Arbitrary command to execute prior to each backup creation. COMMAND_BEFORE_BACKUP = Option("command-before-backup", str) #: Arbitrary command to execute after each backup creation. COMMAND_AFTER_BACKUP = Option("command-after-backup", str) # }}} command execution options # {{{ general options #: Turn on verbose output. VERBOSE = Option("verbose", int) #: Turn on quiet output. Only errors will be shown. If QUIET is turned on at the same level as VERBOSE #: (e. g. both are specified on the command line) then QUIET has higher priority than VERBOSE. QUIET = Option("quiet", bool) #: Operate on all configured archive specification files. ALL = Option("all", bool) #: Directory where :term:`archive specification files <archive specification file>` will be searched. #: Guaranteed to be defined. ARCHIVE_SPECS_DIR = Option("archive-specs-dir", SpecialOptionTypes.PATH) #: User configuration file. Guaranteed to be defined. USER_CONFIG_FILE = Option("user-config-file", SpecialOptionTypes.PATH) #: User configuration directory. Guaranteed to be defined. USER_CONFIG_DIR = Option("user-config-dir", SpecialOptionTypes.PATH) # }}} general options # {{{ force options #: Force archiver type regardless to what is specified in the :term:`archive specification file`. FORCE_ARCHIVER = Option("force-archiver", ArchiverTypes) #: Force incremental backup regardless to what is specified in the :term:`archive specification file`. FORCE_INCREMENTAL = Option("force-incremental", bool) #: Force the backup level restarting regardless to what is specified in the :term:`archive specification file`. FORCE_RESTARTING = Option("force-restarting", bool) #: Force compression level regardless to what is specified in the :term:`archive specification file`. FORCE_COMPRESSION_LEVEL = Option("force-compression-level", int) #: Force the directory where the backup will be created. FORCE_DEST_DIR = Option("force-dest-dir", SpecialOptionTypes.PATH) #: Force configuration of the command to execute prior to each backup creation. FORCE_COMMAND_BEFORE_BACKUP = Option("force-command-before-backup", str) #: Force configuration of the command to execute after each backup creation. FORCE_COMMAND_AFTER_BACKUP = Option("force-command-after-backup", str) #: Force backup overwriting behavior regardless to what is specified in the :term:`archive specification file`. FORCE_OVERWRITE_AT_START = Option("force-overwrite-at-start", bool) # }}} force options # {{{ negation options #: Disable incremental backup. NO_INCREMENTAL = Option("no-incremental", bool) #: Turn off backup level restarting. NO_RESTARTING = Option("no-restarting", bool) #: Turn off obsolete backups removing. NO_REMOVE_OBSOLETE_BACKUPS = Option("no-remove-obsolete-backups", bool) #: Turn off backup keeping. NO_KEEP_OLD_BACKUPS = Option("no-keep-old-backups", bool) #: Do not operate on all configured archive specification files. NO_ALL = Option("no-all", bool) #: Do not overwrite backup at the start of creation. Overwrite after the new backup is created. NO_OVERWRITE_AT_START = Option("no-overwrite-at-start", bool) # }}} negation options # }}} option constants @abstractmethod def __init__(self): pass
[docs]class OptionsUtils(metaclass = ABCMeta): "Various utility methods working with :class:`Options`." __ARCHIVER_OPTION_ENUM_MAP = {"tar": ArchiverTypes.Tar, "targz": ArchiverTypes.TarGz, "tarbz2": ArchiverTypes.TarBz2, "tarxz": ArchiverTypes.TarXz, "tarzst": ArchiverTypes.TarZst, "tar_internal": ArchiverTypes.TarInternal, "targz_internal": ArchiverTypes.TarGzInternal, "tarbz2_internal": ArchiverTypes.TarBz2Internal} __NEGATION_PREFIX = "no-" __FORCE_PREFIX = "force-" @abstractmethod def __init__(self): pass
[docs] @staticmethod def getAllOptions(): """Iterator over all known options. :return: All options defined in :class:`Options`. :rtype: ``Iterator<Option>``""" for member in Options.__dict__: if member.isupper and isinstance(Options.__dict__[member], Option): yield Options.__dict__[member]
[docs] @staticmethod def getAllSpecialOptionTypes(): """Iterator over all known special option types. :return: All option types defined in :class:`SpecialOptionTypes`. :rtype: ``Iterator<str>``""" for member in SpecialOptionTypes.__dict__: if member.isupper and isinstance( SpecialOptionTypes.__dict__[member], str): yield SpecialOptionTypes.__dict__[member]
[docs] @classmethod def getOption(cls, optionName): """Return option with given name. :param optionName: Name of the option that shall be returned. :type optionName: ``str`` :return: First option from :meth:`getAllOptions()` which name is ``optionName``. :rtype: :class:`Option` :raise KeyError: If option with name ``optionName`` does not exist.""" try: return next(filter(lambda opt: str(opt) == optionName, cls.getAllOptions())) except StopIteration: raise KeyError(str.format("Unknown option with name: {}", optionName))
[docs] @classmethod def isExistingOption(cls, optionName): """Check whether an option with name ``optionName`` does exists in :class:`OptionsUtils`. :param optionName: Name of the option which existence shall be checked. :type optionName: ``str`` :return: ``True`` if option with name ``optionName`` exists; ``False`` otherwise. :rtype: ``bool``""" try: cls.getOption(optionName) return True except KeyError: return False
[docs] @classmethod def tryGetNegationForm(cls, option): """Returns :term:`negation form <negation option form>` for ``option`` or ``None``. :param option: An option in the :term:`normal form <normal option form>` for which the negation form for shall be returned. :type option: :class:`Option` :return: Negation form of the passed ``option`` or ``None`` if it does not have a negation form. :rtype: :class:`Option`""" negationFormName = cls.__NEGATION_PREFIX + str(option) if cls.isExistingOption(negationFormName): return cls.getOption(negationFormName) return None
[docs] @classmethod def tryGetForceForm(cls, option): """Returns :term:`force form <force option form>` for ``option`` or ``None``. :param option: An option in the :term:`normal form <normal option form>` for which the force form for shall be returned. :type option: :class:`Option` :return: Force form of the passed ``option`` or ``None`` if it does not have a force form. :rtype: :class:`Option`""" forceFormName = cls.__FORCE_PREFIX + str(option) if cls.isExistingOption(forceFormName): return cls.getOption(forceFormName) return None
[docs] @classmethod def strToOptionType(cls, option, optionValue): """Converts string option value to its proper, defined type. :param option: Option which value shall be converted. :type option: :class:`Option` :param optionValue: Value to be converted. :type optionValue: ``str`` :return: Converted ``optionValue``. :rtype: ``object`` :raise ValueError: If ``optionValue`` can not be converted to ``option``\ 's type. :raise RuntimeError: If ``option``\ 's type is not supported.""" result = None if optionValue is None: result = optionValue elif optionValue == "" and not issubclass(option._optType, str): result = None elif isinstance(option._optType, type): if issubclass(option._optType, str): result = optionValue elif issubclass(option._optType, bool): if optionValue.strip().lower() in ("false", "0", "no"): result = False else: result = bool(optionValue) elif issubclass(option._optType, int): result = int(optionValue) elif issubclass(option._optType, float): result = float(optionValue) elif option._optType in cls.getAllSpecialOptionTypes(): if option._optType == SpecialOptionTypes.PATH: result = os.path.expanduser(optionValue) else: if option._optType is ArchiverTypes: result = cls.__strToArchiverType(optionValue) else: raise(RuntimeError("Unknown option type.")) return result
[docs] @classmethod def archiverTypeToStr(cls, archiverType): """Converts :data:`.ArchiverTypes` to string representation. Value of the ``archiverType`` parameter is converted to a string representation that is accepted by the :meth:`strToOptionType()` method. :param archiverType: Archiver type that shall be converted. :type archiverType: :data:`.ArchiverTypes` :return: String form of passed ``archiverType``. :rtype: ``str`` :raise ValueError: If ``archiverType`` is not known.""" try: return next(filter( lambda key: cls.__ARCHIVER_OPTION_ENUM_MAP[key] == archiverType, cls.__ARCHIVER_OPTION_ENUM_MAP.keys())) except StopIteration: raise ValueError("Unknown archiver type: {}.", archiverType)
@classmethod def __strToArchiverType(cls, archiverStr): "Converts string to :data:`.ArchiverTypes`." if archiverStr in cls.__ARCHIVER_OPTION_ENUM_MAP: return cls.__ARCHIVER_OPTION_ENUM_MAP[archiverStr] else: raise ValueError(str.format("Unknown archiver type: \"{}\".", archiverStr))
# }}} CLASSES