JrGoodle/clowder

View on GitHub
clowder/config.py

Summary

Maintainability
A
0 mins
Test Coverage
"""Clowder config class

.. codeauthor:: Joe DeCapo <joe@polka.cat>

"""

from configparser import ConfigParser
from enum import auto, unique
from functools import wraps
from typing import Any, List, Optional, Tuple

import clowder.util.filesystem as fs
from clowder.util.console import CONSOLE
from clowder.util.enum import AutoLowerName
from clowder.util.format import Format
from clowder.util.git import Protocol

import clowder.util.formatting as fmt
from clowder.environment import ENVIRONMENT
from clowder.util.error import MissingFileError, UnknownProjectError


def print_config(func):
    """Print config after wrapped function returrns"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper"""
        retval = func(*args, **kwargs)
        Config().print_config()
        return retval

    return wrapper


@unique
class CommandConfigType(AutoLowerName):
    JOBS = auto()
    PROJECTS = auto()

    @staticmethod
    def section_name() -> str:
        return 'command'


@unique
class GitConfigType(AutoLowerName):
    FETCH = auto()
    PROTOCOL = auto()
    REBASE = auto()

    @classmethod
    def section_name(cls) -> str:
        return 'git'


class Config:
    """Config class

    :ivar str name: Name of clowder
    :ivar Optional[Tuple[str, ...]] projects: Default projects
    :ivar Optional[GitProtocol] protocol: Default protocol
    :ivar Optional[bool] rebase: Default rebase
    :ivar Optional[int] jobs: Default number of jobs
    """

    def __init__(self):
        """Config __init__"""

        self._config: ConfigParser = ConfigParser()

        if ENVIRONMENT.clowder_config is not None and ENVIRONMENT.clowder_config.exists():
            self._config.read(ENVIRONMENT.clowder_config)

        git_section = GitConfigType.section_name()
        if git_section not in self._config:
            self._config[git_section] = {}
        self._git_config = self._config[git_section]

        command_section = CommandConfigType.section_name()
        if command_section not in self._config:
            self._config[command_section] = {}
        self._command_config = self._config[command_section]

        self._validate_config_projects_defined(self.projects)

        # if defaults is not None:
        #     projects = defaults.get('projects', None)
        #     self.projects: Optional[Tuple[str, ...]] = None if projects is None else tuple(sorted(projects))
        #     protocol = defaults.get('protocol', None)
        #     self.protocol: Optional[GitProtocol] = None if protocol is None else GitProtocol(protocol)
        #     self.rebase: Optional[bool] = defaults.get('rebase', None)
        #     self.jobs: Optional[int] = defaults.get('jobs', None)

    @property
    def jobs(self) -> Optional[int]:
        jobs = str(CommandConfigType.JOBS.value)
        return self._command_config.getint(jobs)

    @jobs.setter
    def jobs(self, jobs: Optional[int]):
        self._set_command_option(CommandConfigType.JOBS, jobs)

    @property
    def projects(self) -> Optional[Tuple[str, ...]]:
        projects = self._command_config.get(str(CommandConfigType.PROJECTS.value))
        if projects is None:
            return None
        projects = [p for p in projects.strip().split(", ")]
        return tuple(sorted(projects))

    @projects.setter
    def projects(self, projects: Optional[List[str]]):
        if not projects:
            self._set_command_option(CommandConfigType.PROJECTS, None)
            return
        self._set_command_option(CommandConfigType.PROJECTS, ", ".join(projects))

    @property
    def protocol(self) -> Optional[Protocol]:
        protocol = str(GitConfigType.PROTOCOL.value)
        protocol = self._git_config.get(protocol)
        if protocol is None:
            return None
        return Protocol(protocol)

    @protocol.setter
    def protocol(self, protocol: Optional[Protocol]):
        self._set_git_option(GitConfigType.PROTOCOL, protocol.value)

    @property
    def rebase(self) -> Optional[bool]:
        rebase = str(GitConfigType.REBASE.value)
        return self._git_config.getboolean(rebase)

    @rebase.setter
    def rebase(self, rebase: Optional[bool]):
        self._set_git_option(GitConfigType.REBASE, rebase)

    @staticmethod
    def clear() -> None:
        """Clear all config settings"""

        if ENVIRONMENT.clowder_config is not None and ENVIRONMENT.clowder_config.exists():
            fs.remove_file(ENVIRONMENT.clowder_config)

    @staticmethod
    def print_config() -> None:
        """Print current config file contents"""

        if ENVIRONMENT.clowder_config is None or not ENVIRONMENT.clowder_config.exists():
            CONSOLE.stdout(' - No config file found')
            return

        CONSOLE.stdout(Format.bold('Current config\n'))
        text = ENVIRONMENT.clowder_config.read_text()
        CONSOLE.stdout(Format.escape(f"{text.strip()}\n"))

    def process_projects_arg(self, projects: List[str]) -> Tuple[str, ...]:
        """Process project args based on parameters and config

        :param List[str] projects: Projects to filter
        :return: Projects in groups matching given names
        """

        if projects != ['default']:
            return tuple(sorted(projects))

        if not self.projects:
            return ('all',)  # noqa

        return self.projects

    def save(self) -> None:
        """Save configuration to file"""

        # if not ENVIRONMENT.clowder_config_dir.exists():
        #     make_dir(ENVIRONMENT.clowder_config_dir)

        if ENVIRONMENT.clowder_config is None:
            raise MissingFileError('No clowder config file path found')

        with open(ENVIRONMENT.clowder_config, 'w') as configfile:
            self._config.write(configfile)

    def _set_command_option(self, option: CommandConfigType, value: Optional[Any]) -> None:
        if value is None:
            self._config.remove_option(CommandConfigType.section_name(), option.value)
        else:
            self._command_config[option.value] = str(value)

    def _set_git_option(self, option: GitConfigType, value: Optional[Any]) -> None:
        if value is None:
            self._config.remove_option(GitConfigType.section_name(), option.value)
        else:
            self._git_config[option.value] = str(value)

    def _validate_config_projects_defined(self, project_options: Tuple[str, ...]) -> None:
        """Validate all projects were defined in clowder yaml file

        :param Tuple[str, ...] project_options: Projects to validate against
        :raise UnknownProjectError:
        """

        if self.projects is None:
            return

        for project in self.projects:
            if project not in project_options:
                message = f"{Format.path(ENVIRONMENT.clowder_config)}\n" \
                          f"Clowder config file appears to be invalid" \
                          f"Unknown project {fmt.project_name(project)}"
                raise UnknownProjectError(message)

        # FIXME: Assemble all undefined projects in message rather than raising on first instance not found