diff --git a/.github/scripts/check_verbs.py b/.github/scripts/check_verbs.py index 0bb815cd..8bb218f4 100644 --- a/.github/scripts/check_verbs.py +++ b/.github/scripts/check_verbs.py @@ -4,9 +4,9 @@ import re import subprocess -from typing import Generator from pathlib import Path from tempfile import mkdtemp +from collections.abc import Generator # 'gui' is a virtual verb for opening the Winetricks GUI # 'vd=1280x720' is a setting for the virtual desktop and valid diff --git a/config.py b/config.py index 0154ca37..d0e779c5 100644 --- a/config.py +++ b/config.py @@ -5,14 +5,32 @@ from pathlib import Path class Config(ConfigBase): + """Configuration for umu-protonfix""" + @dataclass class MainSection: + """General parameters + + Attributes: + enable_checks (bool): Run checks (`checks.py`) before the fix is executed. + enable_splash (bool): Enables splash screen, that is shown while a fix is executed. + enable_global_fixes (bool): Enables included fixes. If deactivated, only local fixes (`~/.config/protonfixes/localfixes`) are executed. + + """ + enable_checks: bool = True enable_splash: bool = False enable_global_fixes: bool = True @dataclass class PathSection: + """Path parameters + + Attributes: + cache_dir (Path): The path that should be used to create temporary and cached files. + + """ + cache_dir: Path = Path.home() / '.cache/protonfixes' main: MainSection diff --git a/config_base.py b/config_base.py index 232c26b9..64774eb2 100644 --- a/config_base.py +++ b/config_base.py @@ -3,7 +3,7 @@ import re from configparser import ConfigParser -from dataclasses import dataclass, is_dataclass +from dataclasses import is_dataclass from pathlib import Path from typing import Any @@ -12,11 +12,25 @@ from logger import log, LogLevelType class ConfigBase: + """Base class for configuration objects. + + This reflects a given config file and populates the object with it's values. + It also injects attributes from the sub classes, this isn't compatible with static type checking though. + You can define the attributes accordingly to satisfy type checkers. + """ + __CAMEL_CASE_PATTERN: re.Pattern = re.compile('((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))') @classmethod def snake_case(cls, input: str) -> str: - # Convert CamelCase to snake_case + """Converts CamelCase to snake_case. + + Args: + input (str): The string to convert. + + Returns: + str: The converted string. + """ return cls.__CAMEL_CASE_PATTERN.sub(r'_\1', input).lower() @@ -26,6 +40,17 @@ def __log(message: str, level: LogLevelType = 'INFO') -> None: def __init__(self, path: Path) -> None: + """Initialize the instance from a given config file. + + Defaults will be used if the file doesn't exist. + The file will also be created in this case. + + Args: + path (Path): The reflected config file's path. + + Raises: + IsADirectoryError: If the path exists, but isn't a file. + """ assert path if path.is_file(): self.parse_config_file(path) @@ -37,6 +62,14 @@ def __init__(self, path: Path) -> None: def init_sections(self, force: bool = False) -> None: + """Find sub-classes and initialize them as attributes. + + Sub-classes are initialized and injected as attributes. + Example: `MainSection` will be injected as `main` to the config (this) object. + + Args: + force (bool, optional): Force initialization? This results in a reset. Defaults to False. + """ for (member_name, member) in self.__class__.__dict__.items(): # Find non private section definitions if not member_name.endswith('Section') or member_name.startswith('_'): @@ -57,6 +90,16 @@ def init_sections(self, force: bool = False) -> None: def parse_config_file(self, file: Path) -> bool: + """Parse a config file. + + This resets the data in the sections, regardless if the file exists or is loaded. + + Args: + file (Path): The reflected config file's path. + + Returns: + bool: True, if the config file was successfully loaded. + """ # Initialize / reset sections to defaults self.init_sections(True) @@ -88,7 +131,7 @@ def _get_parse_function(type_name: str) -> Callable[[str], Any]: }.get(type_name, None) if not value: value = parser_items.get - self.__log(f'Unknown type "{type_name}", falling back to "str".') + self.__log(f'Unknown type "{type_name}", falling back to "str".', 'WARN') return value # Iterate over the option objects in this section @@ -105,6 +148,14 @@ def _get_parse_function(type_name: str) -> Callable[[str], Any]: def write_config_file(self, file: Path) -> bool: + """Write the current config to a file. + + Args: + file (Path): The file path to write to. + + Returns: + bool: True, if the file was successfully written. + """ # Only precede if the parent directory exists if not file.parent.is_dir(): self.__log(f'Parent directory "{file.parent}" does not exist. Abort.', 'WARN') diff --git a/engine.py b/engine.py index 8975dceb..a3850874 100644 --- a/engine.py +++ b/engine.py @@ -8,7 +8,8 @@ class Engine: """Game engines""" - def __init__(self) -> None: # noqa: D107 + def __init__(self) -> None: + """Trys to figure out which engine is used in the current game""" self.engine_name = '' self.supported = { 'Dunia 2': 'https://pcgamingwiki.com/wiki/Engine:Dunia_2', diff --git a/ruff.toml b/ruff.toml index 100a5f39..55b1226c 100644 --- a/ruff.toml +++ b/ruff.toml @@ -57,20 +57,8 @@ select = [ "D" ] ignore = [ - # Requiring a type for self is deprecated - "ANN101", - # Ignore starting with 'This' - "D404", - # Ignore imperative mood - "D401", # Ignore period endings - "D400", - # Ignore punctuation "D415", - # Ignore '1 blank line required before class docstring' - "D203", - # Ignore 'Multi-line docstring summary should start at the second line' - "D213", # Ignore 'Missing docstring in public package' "D104", ] @@ -82,9 +70,17 @@ unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +[lint.pydocstyle] +convention = "google" + [lint.per-file-ignores] # Relax docstring-related lint rules for gamefixes -"gamefixes-*" = ["D103", "D205"] +"gamefixes-*" = [ + # Ignore 'Missing docstring in public function' + "D103", + # Ignore '1 blank line required between summary line and description' + "D205" +] [format] # Like Black, use double quotes for strings. diff --git a/util.py b/util.py index de9aca96..048bf750 100644 --- a/util.py +++ b/util.py @@ -14,8 +14,8 @@ from io import TextIOWrapper from pathlib import Path from socket import socket, AF_INET, SOCK_DGRAM -from typing import Literal, Any, Callable, Union, Optional -from collections.abc import Mapping, Generator +from typing import Literal, Any, Union, Optional +from collections.abc import Mapping, Generator, Callable try: from .logger import log