Coverage for src/package/settings.py: 100.00%
69 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-18 15:02 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-18 15:02 +0000
1#!/usr/bin/env python3
3# Standard libraries
4from configparser import ConfigParser
5from pathlib import Path
6from sys import stdout
7from typing import Optional
9# Components
10from ..package.bundle import Bundle
11from ..prints.colors import Colors
12from ..system.platform import Platform
14# Settings class
15class Settings:
17 # Types
18 Value = int | str
20 # Constants
21 SETTINGS_FILE: str = 'settings.ini'
23 # Members
24 __folder: Path
25 __persistent: bool
26 __path: Path
27 __settings: ConfigParser
29 # Constructor
30 def __init__(self, name: str) -> None:
32 # Prepare paths
33 self.__folder = Platform.userspace(name)
34 self.__path = self.__folder / Settings.SETTINGS_FILE
35 self.__persistent = False
37 # Force settings migration
38 if Platform.userspace(Bundle.ALIAS).exists():
39 raise DeprecationWarning(
40 'Settings:' \
41 f' Please compare and remove \'{Platform.userspace(Bundle.ALIAS)}\''
42 f' once migrated to \'{self.__folder}\'')
44 # Parse settings
45 self.__settings = ConfigParser()
46 self.__settings.read(self.__path)
48 # Prepare missing settings
49 try:
50 if self.get('package', 'name') != name:
51 raise ValueError('Missing settings files')
52 self.__persistent = True
53 except ValueError:
54 try:
55 self.__prepare()
56 self.__reset(name)
57 self.__persistent = True
58 except PermissionError: # pragma: no cover
59 self.__persistent = False
61 # Initialize settings
62 if not Path(self.__path).is_file():
63 self.__write()
65 # Prepare
66 def __prepare(self) -> None:
68 # Prepare folder path
69 if not Platform.IS_SIMULATED:
70 self.__folder.mkdir(parents=True, exist_ok=True)
72 # Reset
73 def __reset(self, name: str) -> None:
75 # Prepare barebone settings
76 self.__settings = ConfigParser()
77 self.set('package', 'name', name)
79 # Writer
80 def __write(self) -> None:
82 # Write initial settings
83 if self.__persistent and not Platform.IS_SIMULATED:
84 with open(self.__path, encoding='utf8', mode='w') as output:
85 self.__settings.write(output)
87 # Has
88 def has(self, group: str, key: str) -> bool:
90 # Check settings key in group
91 return group in self.__settings and key in self.__settings[group]
93 # Get
94 def get(self, group: str, key: str) -> Optional[Value]:
96 # Get settings key in group
97 if group in self.__settings and key in self.__settings[group]:
98 return self.__settings[group][key]
100 # Default fallback
101 return None
103 # Get bool
104 def get_bool(self, group: str, key: str) -> Optional[bool]:
106 # Get settings key as boolean
107 try:
108 value: str = str(self.get(group, key))
109 return value.lower() == 'true' or int(value) == 1
110 except (TypeError, ValueError):
111 return False
113 # Set
114 def set(self, group: str, key: str, value: Value) -> None:
116 # Prepare group
117 if group not in self.__settings:
118 self.__settings[group] = {}
120 # Unset key
121 if str(value) == 'UNSET':
122 del self.__settings[group][key]
124 # Set key
125 else:
126 self.__settings[group][key] = str(value)
128 # Write updated settings
129 self.__write()
131 # Set bool
132 def set_bool(self, group: str, key: str, value: Value) -> None:
134 # Set settings key as boolean
135 self.set(group, key, 1 if value else 0)
137 # Show
138 def show(self) -> None:
140 # Settings file path
141 print(' ')
142 print(
143 f' {Colors.GREEN}===[ {Colors.YELLOW}Settings:' \
144 f' {Colors.BOLD}{self.__path} {Colors.GREEN}]==={Colors.RESET}'
145 )
146 print(' ')
148 # Settings simulated contents
149 if Platform.IS_SIMULATED:
150 self.__settings.write(stdout)
152 # Settings file contents
153 else:
154 with open(self.__path, encoding='utf8', mode='r') as data:
155 print(data.read())
156 Platform.flush()