# 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 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