Commit 29cac8b5 authored by Adrian DC's avatar Adrian DC
Browse files

feat(main, settings): implement 'Settings' from 'gitlabci-local'

Signed-off-by: default avatarAdrian DC <radian.dc@gmail.com>
parent 2b6da44b
Loading
Loading
Loading
Loading
Loading
+22 −6
Changes for README.md: 22 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -28,12 +28,13 @@ The following step is required before using the tool:
<!-- readme-help-start -->

```yaml
usage: gitlab-projects-settings [-h] [--version] [-t TOKEN] [--dry-run] [--exclude-group] [--exclude-subgroups]
                                [--exclude-projects] [--available-features] [--reset-features [KEEP_FEATURES]]
                                [--disable-features FEATURES] [--enable-features FEATURES] [--reset-members]
                                [--set-avatar FILE] [--set-description TEXT] [--update-description]
                                [--run-housekeeping] [--archive-project | --unarchive-project] [--delete-group]
                                [--delete-project] [--protect-branches] [--protect-tags LEVEL] [--]
usage: gitlab-projects-settings [-h] [--version] [--settings] [--set GROUP KEY VAL] [-t TOKEN] [--dry-run]
                                [--exclude-group] [--exclude-subgroups] [--exclude-projects] [--available-features]
                                [--reset-features [KEEP_FEATURES]] [--disable-features FEATURES]
                                [--enable-features FEATURES] [--reset-members] [--set-avatar FILE]
                                [--set-description TEXT] [--update-description] [--run-housekeeping]
                                [--archive-project | --unarchive-project] [--delete-group] [--delete-project]
                                [--protect-branches] [--protect-tags LEVEL] [--]
                                [gitlab] [path]

gitlab-projects-settings: Configure GitLab groups and projects settings automatically
@@ -41,6 +42,9 @@ gitlab-projects-settings: Configure GitLab groups and projects settings automati
internal arguments:
  -h, --help                        # Show this help message
  --version                         # Show the current version
  --settings                        # Show the current settings path and contents
  --set GROUP KEY VAL               # Set settings specific 'VAL' value to [GROUP] > KEY
                                    # or unset by using 'UNSET' as 'VAL'

credentials arguments:
  -t TOKEN                          # GitLab API token (default: GITLAB_TOKEN environment)
@@ -84,6 +88,18 @@ positional arguments:

---

## Userspace available settings

`gitlab-projects-settings` creates a `settings.ini` configuration file in a userspace folder.

The `settings.ini` file location and contents can be shown with the following command:

```bash
gitlab-projects-settings --settings
```

---

## Dependencies

- [colored](https://pypi.org/project/colored/): Terminal colors and styles
+30 −0
Changes for src/cli/main.py: 30 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ from sys import exit as sys_exit

# Components
from ..package.bundle import Bundle
from ..package.settings import Settings
from ..package.version import Version
from ..prints.colors import Colors
from ..system.platform import Platform
@@ -52,6 +53,21 @@ def main() -> None:
        action='store_true',
        help='Show the current version',
    )
    group.add_argument(
        '--settings',
        dest='settings',
        action='store_true',
        help='Show the current settings path and contents',
    )
    group.add_argument(
        '--set',
        dest='set',
        action='store',
        metavar=('GROUP', 'KEY', 'VAL'),
        nargs=3,
        help='Set settings specific \'VAL\' value to [GROUP] > KEY\n' \
             'or unset by using \'UNSET\' as \'VAL\'',
    )

    # Arguments credentials definitions
    group = parser.add_argument_group('credentials arguments')
@@ -229,9 +245,23 @@ def main() -> None:
        Platform.flush()
        sys_exit(0)

    # Instantiate settings
    settings: Settings = Settings(name=Bundle.NAME)

    # Prepare colors
    Colors.prepare()

    # Settings setter
    if options.set:
        settings.set(options.set[0], options.set[1], options.set[2])
        settings.show()
        sys_exit(0)

    # Settings informations
    if options.settings:
        settings.show()
        sys_exit(0)

    # Version informations
    if options.version:
        print(
+144 −0
Changes for src/package/settings.py: 144 added lines, 0 removed lines.
Original line number Diff line number Diff line
#!/usr/bin/env python3

# Standard libraries
from configparser import ConfigParser
from pathlib import Path
from sys import stdout
from typing import Optional, Union

# Components
from ..prints.colors import Colors
from ..system.platform import Platform

# Settings class
class Settings:

    # Types
    Value = Union[int, str]

    # Constants
    SETTINGS_FILE: str = 'settings.ini'

    # Members
    __folder: Path
    __persistent: bool
    __path: Path
    __settings: ConfigParser

    # Constructor
    def __init__(self, name: str) -> None:

        # Prepare paths
        self.__folder = Platform.userspace(name)
        self.__path = self.__folder / Settings.SETTINGS_FILE
        self.__persistent = False

        # Parse settings
        self.__settings = ConfigParser()
        self.__settings.read(self.__path)

        # Prepare missing settings
        try:
            if self.get('package', 'name') != name:
                raise ValueError('Missing settings files')
            self.__persistent = True
        except ValueError:
            try:
                self.__prepare()
                self.__reset(name)
                self.__persistent = True
            except PermissionError: # pragma: no cover
                self.__persistent = False

    # Prepare
    def __prepare(self) -> None:

        # Prepare folder path
        if not Platform.IS_SIMULATED:
            self.__folder.mkdir(parents=True, exist_ok=True)

    # Reset
    def __reset(self, name: str) -> None:

        # Prepare barebone settings
        self.__settings = ConfigParser()
        self.set('package', 'name', name)

    # Writer
    def __write(self) -> None:

        # Write initial settings
        if self.__persistent and not Platform.IS_SIMULATED:
            with open(self.__path, encoding='utf8', mode='w') as output:
                self.__settings.write(output)

    # Has
    def has(self, group: str, key: str) -> bool:

        # Check settings key in group
        return group in self.__settings and key in self.__settings[group]

    # Get
    def get(self, group: str, key: str) -> Optional[Value]:

        # Get settings key in group
        if group in self.__settings and key in self.__settings[group]:
            return self.__settings[group][key]

        # Default fallback
        return None

    # Get bool
    def get_bool(self, group: str, key: str) -> Optional[bool]:

        # Get settings key as boolean
        try:
            value: str = str(self.get(group, key))
            return value.lower() == 'true' or int(value) == 1
        except (TypeError, ValueError):
            return False

    # Set
    def set(self, group: str, key: str, value: Value) -> None:

        # Prepare group
        if group not in self.__settings:
            self.__settings[group] = {}

        # Unset key
        if str(value) == 'UNSET':
            del self.__settings[group][key]

        # Set key
        else:
            self.__settings[group][key] = str(value)

        # Write updated settings
        self.__write()

    # Set bool
    def set_bool(self, group: str, key: str, value: Value) -> None:

        # Set settings key as boolean
        self.set(group, key, 1 if value else 0)

    # Show
    def show(self) -> None:

        # Settings file path
        print(' ')
        print(
            f' {Colors.GREEN}===[ {Colors.YELLOW}Settings:' \
                f' {Colors.BOLD}{self.__path} {Colors.GREEN}]==={Colors.RESET}'
        )
        print(' ')

        # Settings simulated contents
        if Platform.IS_SIMULATED:
            self.__settings.write(stdout)

        # Settings file contents
        else:
            with open(self.__path, encoding='utf8', mode='r') as data:
                print(data.read())
            Platform.flush()
+41 −1
Changes for src/system/platform.py: 41 added lines, 1 removed line.
Original line number Diff line number Diff line
#!/usr/bin/env python3

# Standard libraries
from os import sep
from os import access, environ, R_OK, sep
from os.path import expanduser
from pathlib import Path
from sys import platform, stdin, stdout

# Platform, pylint: disable=too-few-public-methods
class Platform:

    # Environment
    ENV_SIMULATE_MAC_OS: str = 'SIMULATE_MAC_OS'
    ENV_SUDO_USER: str = 'SUDO_USER'

    # Constants
    IS_LINUX: bool = platform in ['linux', 'linux2']
    IS_MAC_OS: bool = platform in ['darwin'] or ENV_SIMULATE_MAC_OS in environ
    IS_SIMULATED: bool = ENV_SIMULATE_MAC_OS in environ
    IS_WINDOWS: bool = platform in ['win32', 'win64']

    # Separators
@@ -19,9 +27,41 @@ class Platform:
    IS_TTY_STDOUT: bool = stdout.isatty()
    IS_TTY_UTF8: bool = str(stdout.encoding).lower() == 'utf-8'

    # Users
    IS_USER_SUDO: bool = ENV_SUDO_USER in environ
    USER_SUDO: str = environ[ENV_SUDO_USER] if IS_USER_SUDO else ''

    # Flush
    @staticmethod
    def flush() -> None:

        # Flush output
        print('', end='', flush=Platform.IS_TTY_STDOUT)

    # Userspace
    @staticmethod
    def userspace(name: str) -> Path:

        # Variables
        home: None | Path = None

        # Elevated home
        if Platform.IS_USER_SUDO:
            home = Path(expanduser(f'~{Platform.USER_SUDO}'))
            if not access(home, R_OK):
                home = None

        # Default home
        if not home or not home.is_dir():
            home = Path.home()

        # Windows userspace
        if Platform.IS_WINDOWS:
            return home / 'AppData' / 'Local' / name

        # macOS userspace
        if Platform.IS_MAC_OS:
            return home / 'Library' / 'Preferences' / name

        # Linux userspace
        return home / '.config' / name