Coverage for src/package/settings.py: 100.00%

66 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 21:20 +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 ..prints.colors import Colors 

11from ..system.platform import Platform 

12 

13# Settings class 

14class Settings: 

15 

16 # Types 

17 Value = int | str 

18 

19 # Constants 

20 SETTINGS_FILE: str = 'settings.ini' 

21 

22 # Members 

23 __folder: Path 

24 __persistent: bool 

25 __path: Path 

26 __settings: ConfigParser 

27 

28 # Constructor 

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

30 

31 # Prepare paths 

32 self.__folder = Platform.userspace(name) 

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

34 self.__persistent = False 

35 

36 # Parse settings 

37 self.__settings = ConfigParser() 

38 self.__settings.read(self.__path) 

39 

40 # Prepare missing settings 

41 try: 

42 if self.get('package', 'name') != name: 

43 raise ValueError('Missing settings files') 

44 self.__persistent = True 

45 except ValueError: 

46 try: 

47 self.__prepare() 

48 self.__reset(name) 

49 self.__persistent = True 

50 except PermissionError: # pragma: no cover 

51 self.__persistent = False 

52 

53 # Initialize settings 

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

55 self.__write() 

56 

57 # Prepare 

58 def __prepare(self) -> None: 

59 

60 # Prepare folder path 

61 if not Platform.IS_SIMULATED: 

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

63 

64 # Reset 

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

66 

67 # Prepare barebone settings 

68 self.__settings = ConfigParser() 

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

70 

71 # Writer 

72 def __write(self) -> None: 

73 

74 # Write initial settings 

75 if self.__persistent and not Platform.IS_SIMULATED: 

76 with open(self.__path, encoding='utf8', mode='w') as output: 

77 self.__settings.write(output) 

78 

79 # Has 

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

81 

82 # Check settings key in group 

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

84 

85 # Get 

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

87 

88 # Get settings key in group 

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

90 return self.__settings[group][key] 

91 

92 # Default fallback 

93 return None 

94 

95 # Get bool 

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

97 

98 # Get settings key as boolean 

99 try: 

100 value: str = str(self.get(group, key)) 

101 return value.lower() == 'true' or int(value) == 1 

102 except (TypeError, ValueError): 

103 return False 

104 

105 # Set 

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

107 

108 # Prepare group 

109 if group not in self.__settings: 

110 self.__settings[group] = {} 

111 

112 # Unset key 

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

114 del self.__settings[group][key] 

115 

116 # Set key 

117 else: 

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

119 

120 # Write updated settings 

121 self.__write() 

122 

123 # Set bool 

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

125 

126 # Set settings key as boolean 

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

128 

129 # Show 

130 def show(self) -> None: 

131 

132 # Settings file path 

133 print(' ') 

134 print( 

135 f' {Colors.GREEN}===[ {Colors.YELLOW}Settings:' \ 

136 f' {Colors.BOLD}{self.__path} {Colors.GREEN}]==={Colors.RESET}' 

137 ) 

138 print(' ') 

139 

140 # Settings simulated contents 

141 if Platform.IS_SIMULATED: 

142 self.__settings.write(stdout) 

143 

144 # Settings file contents 

145 else: 

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

147 print(data.read()) 

148 Platform.flush()