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

1#!/usr/bin/env python3 

2 

3# Standard libraries 

4from configparser import ConfigParser 

5from pathlib import Path 

6from sys import stdout 

7from typing import Optional 

8 

9# Components 

10from ..package.bundle import Bundle 

11from ..prints.colors import Colors 

12from ..system.platform import Platform 

13 

14# Settings class 

15class Settings: 

16 

17 # Types 

18 Value = int | str 

19 

20 # Constants 

21 SETTINGS_FILE: str = 'settings.ini' 

22 

23 # Members 

24 __folder: Path 

25 __persistent: bool 

26 __path: Path 

27 __settings: ConfigParser 

28 

29 # Constructor 

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

31 

32 # Prepare paths 

33 self.__folder = Platform.userspace(name) 

34 self.__path = self.__folder / Settings.SETTINGS_FILE 

35 self.__persistent = False 

36 

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}\'') 

43 

44 # Parse settings 

45 self.__settings = ConfigParser() 

46 self.__settings.read(self.__path) 

47 

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 

60 

61 # Initialize settings 

62 if not Path(self.__path).is_file(): 

63 self.__write() 

64 

65 # Prepare 

66 def __prepare(self) -> None: 

67 

68 # Prepare folder path 

69 if not Platform.IS_SIMULATED: 

70 self.__folder.mkdir(parents=True, exist_ok=True) 

71 

72 # Reset 

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

74 

75 # Prepare barebone settings 

76 self.__settings = ConfigParser() 

77 self.set('package', 'name', name) 

78 

79 # Writer 

80 def __write(self) -> None: 

81 

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) 

86 

87 # Has 

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

89 

90 # Check settings key in group 

91 return group in self.__settings and key in self.__settings[group] 

92 

93 # Get 

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

95 

96 # Get settings key in group 

97 if group in self.__settings and key in self.__settings[group]: 

98 return self.__settings[group][key] 

99 

100 # Default fallback 

101 return None 

102 

103 # Get bool 

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

105 

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 

112 

113 # Set 

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

115 

116 # Prepare group 

117 if group not in self.__settings: 

118 self.__settings[group] = {} 

119 

120 # Unset key 

121 if str(value) == 'UNSET': 

122 del self.__settings[group][key] 

123 

124 # Set key 

125 else: 

126 self.__settings[group][key] = str(value) 

127 

128 # Write updated settings 

129 self.__write() 

130 

131 # Set bool 

132 def set_bool(self, group: str, key: str, value: Value) -> None: 

133 

134 # Set settings key as boolean 

135 self.set(group, key, 1 if value else 0) 

136 

137 # Show 

138 def show(self) -> None: 

139 

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(' ') 

147 

148 # Settings simulated contents 

149 if Platform.IS_SIMULATED: 

150 self.__settings.write(stdout) 

151 

152 # Settings file contents 

153 else: 

154 with open(self.__path, encoding='utf8', mode='r') as data: 

155 print(data.read()) 

156 Platform.flush()