AutoArchive._infrastructure.configuration._configuration_factory — AutoArchive Developer Manual (ver. 2.1.0.dev20260309)

# Source code for AutoArchive._infrastructure.configuration._configuration_factory

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

    __all__ = ["ConfigurationFactory"]

    import os

    from AutoArchive._infrastructure.utils import OsPaths, ErrorHandling
    from . import Constants as ConfigurationConstants, ArchiverTypes, ConfigurationBase
    from . import Options, OptionsUtils
    from ._cmdline_arguments_processor import _CmdlineArgumentsProcessor
    from ._config_file_processor import _ConfigFileProcessor
    from ._configuration import _Configuration

    # SMELL: Creation of archive specifications directory should perhaps go to Archiving?

    class ConfigurationFactory:
        """Support of configuration options and a persistent storage.

        During :meth:`makeConfiguration` call it creates :class:`.ConfigurationBase` implementation instance.  It also
        creates :term:`user configuration directory` and :term:`archive specifications directory` if either of them does
        not exist.

        The :class:`.ConfigurationBase`-like class provides access to configuration options.  During the
        :meth:`makeConfiguration` call it is instantiated and populated from
        :attr:`.AppEnvironment.commandLineOptions` and from system- and user-configuration files.  The
        :term:`system configuration file` is assembled from :meth:`.OsPaths.getSystemConfigDir()` and
        :attr:`.Constants.CONFIG_FILE_NAME`.  The :term:`user configuration file` location is determined by values of
        :attr:`.Options.USER_CONFIG_FILE` and :attr:`.Options.USER_CONFIG_DIR` options.  The
        :term:`user configuration directory` is automatically created if it does not exists.  The format of configuration
        files is defined by the standard :mod:`configparser` module (without interpolation).

        The :class:`.ConfigurationBase` implementation instance is populated in the way that same options specified in
        multiple sources overrides each other so that the order of precedence from highest to lowest is following:
        command-line, user configuration file, system configuration file.  However the implementation recognizes certain
        types of options that does not follow this rule (see :meth:`.ConfigurationBase.__getitem__()`).

        Some of the options, if not specified in neither of possible sources, has some predefined default value.  The list
        of these options with their predefined value follows:

        * :attr:`.Options.ARCHIVER`\ : :attr:`.ArchiverTypes.TarGz`
        * :attr:`.Options.DEST_DIR`\ : ``os.curdir``
        * :attr:`.Options.RESTART_AFTER_LEVEL`\ : 10
        * :attr:`.Options.ARCHIVE_SPECS_DIR`\ : :attr:`.Options.USER_CONFIG_DIR` + "archive_specs"
        * :attr:`.Options.USER_CONFIG_FILE`\ : :attr:`.Options.USER_CONFIG_DIR` + :attr:`.Constants.CONFIG_FILE_NAME`
        * :attr:`.Options.USER_CONFIG_DIR`\ : result of :meth:`.OsPaths.getUserConfigDir()`
        * :attr:`.Options.NUMBER_OF_OLD_BACKUPS`\ : 1
        * options of type ``bool``\ : ``False``"""

        # system configuration file - full path
        __SYSTEM_CONFIG_FILE = os.path.join(OsPaths.getSystemConfigDir(), ConfigurationConstants.CONFIG_FILE_NAME)

        # user configuration directory
        __FACTORY_USER_CONFIG_DIR = OsPaths.getUserConfigDir()

        @classmethod
        def makeConfiguration(cls, appEnvironment, systemConfigFile = __SYSTEM_CONFIG_FILE) -> ConfigurationBase:
            """Creates and populates configuration.

            :param appEnvironment: Application environment.
            :type appEnvironment: :class:`.AppEnvironment`
            :param systemConfigFile: Path to the system configuration file.
            :type systemConfigFile: `str`
            :rtype: :class:`.ConfigurationBase`"""

            return cls.__createAndPopulateConfig(appEnvironment, systemConfigFile)

        @classmethod
        def __createAndPopulateConfig(cls, appEnvironment, systemConfigFile):

            cmdlineArgumentsProcessor = _CmdlineArgumentsProcessor(appEnvironment.commandLineOptions)

            cmdlineUserConfigDir, cmdlineUserConfigFile = cls.__getUserPathsFromCmdline(
                cmdlineArgumentsProcessor, appEnvironment)

            # create _Configuration which will be registered to Component Accessor
            configuration = _Configuration()

            # populate configuration with config. file options
            configFileProcessor = _ConfigFileProcessor(
                appEnvironment, systemConfigFile, cls.__FACTORY_USER_CONFIG_DIR,
                lambda usrDir: os.path.join(usrDir, ConfigurationConstants.CONFIG_FILE_NAME),
                cmdlineUserConfigDir, cmdlineUserConfigFile)
            configFileProcessor.populateConfiguration(configuration)

            # populate configuration with command line options
            cmdlineArgumentsProcessor.populateConfiguration(configuration)

            cls.__populateWithDefaults(configuration)

            return configuration

        @staticmethod
        def __getUserPathsFromCmdline(
                cmdlineArgumentsProcessor: _CmdlineArgumentsProcessor, appEnvironment) -> tuple[str, str]:

            # create temporary _Configuration and populate it with command line options
            cmdlineConfiguration = _Configuration()
            cmdlineArgumentsProcessor.populateConfiguration(cmdlineConfiguration)

            # determine and create user config directory if it does not exist
            cmdlineUserConfigDir: str = cmdlineConfiguration[Options.USER_CONFIG_DIR]

            # get user config file passed as a command-line option, if any and show error if it does not exist
            cmdlineUserConfigFile: str = cmdlineConfiguration[Options.USER_CONFIG_FILE]
            if cmdlineUserConfigFile:
                if not os.path.isfile(cmdlineUserConfigFile):
                    ErrorHandling.printError(str.format("Configuration file \"{}\" does not exists.",
                                                 cmdlineUserConfigFile), appEnvironment.executableName)

            return cmdlineUserConfigDir, cmdlineUserConfigFile

        @classmethod
        def __populateWithDefaults(cls, configuration):
            configuration._setDefault(Options.ARCHIVER, OptionsUtils.archiverTypeToStr(ArchiverTypes.TarGz))
            configuration._setDefault(Options.DEST_DIR, os.curdir)
            configuration._setDefault(Options.RESTART_AFTER_LEVEL, 10)
            configuration._setDefault(Options.NUMBER_OF_OLD_BACKUPS, 1)
            configuration._setDefault(Options.USER_CONFIG_DIR, cls.__FACTORY_USER_CONFIG_DIR)
            configuration._setDefault(Options.USER_CONFIG_FILE, os.path.join(
                configuration[Options.USER_CONFIG_DIR], ConfigurationConstants.CONFIG_FILE_NAME))
            configuration._setDefault(Options.ARCHIVE_SPECS_DIR, os.path.join(
                configuration[Options.USER_CONFIG_DIR], "archive_specs"))