Coverage for python/univention/info_tools.py: 99%

108 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-09-11 14:11 +0000

1# 

2# Univention Configuration Registry 

3# dictionary class for localized keys 

4# 

5# SPDX-FileCopyrightText: 2007-2026 Univention GmbH 

6# SPDX-License-Identifier: AGPL-3.0-only 

7 

8import configparser 

9import re 

10from typing import TypeVar, overload 

11 

12 

13_VT = TypeVar('_VT') 

14 

15# default locale 

16_locale = 'de' 

17MYPY = False 

18 

19 

20if MYPY: # pragma: no cover 

21 __LVD = dict[str, str] 

22else: 

23 __LVD = dict 

24 

25 

26class LocalizedValue(__LVD): 

27 """Localized description entry.""" 

28 

29 def __init__(self, *args, **kwargs): 

30 tmp = dict(*args, **kwargs) 

31 self.__default = tmp.pop('__default', '') 

32 dict.__init__(self, tmp) 

33 

34 def __repr__(self): 

35 return '%s(%s, __default=%r)' % ( 

36 self.__class__.__name__, 

37 dict.__repr__(self), 

38 self.__default, 

39 ) 

40 

41 def get(self, locale: str | None = None) -> str: # type: ignore 

42 if not locale: 

43 locale = _locale 

44 if locale in self: 

45 return self[locale] 

46 return self.__default 

47 

48 def set(self, value: str, locale: str | None = None) -> None: 

49 self[locale or _locale] = value 

50 

51 def set_default(self, default: str) -> None: 

52 self.__default = default 

53 

54 def get_default(self) -> str: 

55 return self.__default 

56 

57 

58if MYPY: # pragma: no cover 

59 __LD = dict[str, str] 

60else: 

61 __LD = dict 

62 

63 

64class LocalizedDictionary(__LD): # noqa: PLW1641 

65 """Localized descriptions.""" 

66 

67 _LOCALE_REGEX = re.compile(r'(?P<key>[a-zA-Z]*)\[(?P<lang>[a-z]*)\]$') 

68 

69 def __init__(self) -> None: 

70 dict.__init__(self) 

71 

72 def __setitem__(self, key: str, value: str) -> None: 

73 key = key.lower() 

74 matches = LocalizedDictionary._LOCALE_REGEX.match(key) 

75 # localized value? 

76 if matches: 

77 key, lang = matches.groups() 

78 

79 val = self.setdefault(key, LocalizedValue()) # type: ignore 

80 if matches: 

81 val.set(value, lang) # type: ignore 

82 else: 

83 val.set_default(value) # type: ignore 

84 

85 def __getitem__(self, key: str) -> str: 

86 key = key.lower() 

87 lang: str | None = None 

88 matches = LocalizedDictionary._LOCALE_REGEX.match(key) 

89 # localized value? 

90 if matches: 

91 key, lang = matches.groups() 

92 

93 return dict.__getitem__(self, key).get(lang) # type: ignore 

94 

95 @overload 

96 def get(self, key: str) -> str | None: # pragma: no cover 

97 pass 

98 

99 @overload 

100 def get(self, key: str, default: _VT) -> str | _VT: # pragma: no cover 

101 pass 

102 

103 def get(self, key: str, default: _VT | None = None) -> str | _VT: 

104 try: 

105 value = self.__getitem__(key) or default 

106 return value # type: ignore 

107 except KeyError: 

108 return default # type: ignore 

109 

110 def __contains__(self, key: str) -> bool: # type: ignore 

111 key = key.lower() 

112 matches = LocalizedDictionary._LOCALE_REGEX.match(key) 

113 if matches: 

114 key = matches.group(1) 

115 return dict.__contains__(self, key) # type: ignore[operator] 

116 has_key = __contains__ # type: ignore 

117 

118 def __normalize_key(self, key: str) -> dict[str, str]: 

119 if key not in self: 

120 return {} 

121 

122 temp = {} 

123 variable: LocalizedValue = dict.__getitem__(self, key) # type: ignore 

124 for locale, value in variable.items(): 

125 temp['%s[%s]' % (key, locale)] = value 

126 

127 if variable.get_default(): # type: ignore 

128 temp[key] = variable.get_default() 

129 

130 return temp 

131 

132 def normalize(self, key: str | None = None) -> dict[str, str]: 

133 if key: 

134 return self.__normalize_key(key) 

135 temp: dict[str, str] = {} 

136 for key2 in self.keys(): 

137 temp.update(self.__normalize_key(key2)) 

138 return temp 

139 

140 def get_dict(self, key: str) -> dict[str, str]: 

141 if key not in self: 

142 return {} 

143 return dict.__getitem__(self, key) # type: ignore 

144 

145 def __eq__(self, other): 

146 if not isinstance(other, dict): 

147 return False 

148 me = self.normalize() 

149 you = other.normalize() 

150 return dict.__eq__(me, you) 

151 

152 def __ne__(self, other): 

153 return not self.__eq__(other) 

154 

155 

156# my config parser 

157class UnicodeConfig(configparser.ConfigParser): 

158 

159 def __init__(self): 

160 configparser.ConfigParser.__init__(self, strict=False, interpolation=None) 

161 

162 def read(self, filename, encoding='UTF-8'): 

163 return configparser.ConfigParser.read(self, filename, encoding=encoding) 

164 

165 def write(self, fp): 

166 """Write an .ini-format representation of the configuration state.""" 

167 if self._defaults: 

168 fp.write("[%s]\n" % configparser.DEFAULTSECT) 

169 for (key, value) in self._defaults.items(): 

170 fp.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t'))) 

171 fp.write("\n") 

172 for section in self._sections: 

173 fp.write("[%s]\n" % section) 

174 for (key, value) in self._sections[section].items(): 

175 if key != "__name__": 175 ↛ 174line 175 didn't jump to line 174 because the condition on line 175 was always true

176 fp.write("%s = %s\n" % (key, value.replace('\n', '\n\t'))) 

177 fp.write("\n") 

178 

179 

180def set_language(lang): 

181 global _locale 

182 _locale = lang