Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import importlib 

2import inspect 

3import re 

4from typing import Any, Dict, List, Union 

5 

6from pystratum_backend.StratumStyle import StratumStyle 

7 

8 

9class ConstantClass: 

10 """ 

11 Helper class for loading and modifying the class that acts like a namespace for constants. 

12 """ 

13 

14 # ------------------------------------------------------------------------------------------------------------------ 

15 def __init__(self, class_name: str, io: StratumStyle): 

16 """ 

17 Object constructor. 

18 

19 :param str class_name: The name of class that acts like a namespace for constants. 

20 :param PyStratumStyle io: The output decorator. 

21 """ 

22 self.__class_name: str = class_name 

23 """ 

24 The name of class that acts like a namespace for constants. 

25 """ 

26 

27 self.__module = None 

28 """ 

29 The module of which the class that acts like a namespace for constants belongs. 

30 

31 :type: module 

32 """ 

33 

34 self.__annotation: str = '# PyStratum' 

35 """ 

36 The comment after which the auto generated constants must be inserted. 

37 """ 

38 

39 self._io: StratumStyle = io 

40 """ 

41 The output decorator. 

42 """ 

43 

44 self.__load() 

45 

46 # ------------------------------------------------------------------------------------------------------------------ 

47 def __load(self) -> None: 

48 """ 

49 Loads dynamically the class that acts like a namespace for constants. 

50 """ 

51 parts = self.__class_name.split('.') 

52 module_name = ".".join(parts[:-1]) 

53 module = __import__(module_name) 

54 modules = [] 

55 for comp in parts[1:]: 

56 module = getattr(module, comp) 

57 modules.append(module) 

58 

59 self.__module = modules[-2] 

60 

61 # ------------------------------------------------------------------------------------------------------------------ 

62 def file_name(self) -> str: 

63 """ 

64 Returns the filename of the module with the class that acts like a namespace for constants. 

65 

66 :rtype: str 

67 """ 

68 return inspect.getfile(self.__module) 

69 

70 # ------------------------------------------------------------------------------------------------------------------ 

71 def source(self) -> str: 

72 """ 

73 Returns the source of the module with the class that acts like a namespace for constants. 

74 

75 :rtype: str 

76 """ 

77 return inspect.getsource(self.__module) 

78 

79 # ------------------------------------------------------------------------------------------------------------------ 

80 def reload(self) -> None: 

81 """ 

82 Reloads the module with the class that acts like a namespace for constants. 

83 """ 

84 importlib.reload(self.__module) 

85 

86 # ------------------------------------------------------------------------------------------------------------------ 

87 def constants(self) -> Dict[str, Any]: 

88 """ 

89 Gets the constants from the class that acts like a namespace for constants. 

90 

91 :rtype: dict<str,*> 

92 """ 

93 ret = {} 

94 

95 name = self.__class_name.split('.')[-1] 

96 constant_class = getattr(self.__module, name) 

97 for name, value in constant_class.__dict__.items(): 

98 if re.match(r'^[A-Z][A-Z0-9_]*$', name): 

99 ret[name] = value 

100 

101 return ret 

102 

103 # ------------------------------------------------------------------------------------------------------------------ 

104 def __extract_info(self, lines: List[str]) -> Dict[str, Union[str, int]]: 

105 """ 

106 Extracts the following info from the source of the module with the class that acts like a namespace for 

107 constants: 

108 * Start line with constants 

109 * Last line with constants 

110 * Indent for constants 

111 

112 :param list[str] lines: The source of the module with the class that acts like a namespace for constants. 

113 

114 :rtype: dict<str,int|str> 

115 """ 

116 ret = {'start_line': 0, 

117 'last_line': 0, 

118 'indent': ''} 

119 

120 mode = 1 

121 count = 0 

122 for line in lines: 

123 if mode == 1: 

124 if line.strip() == self.__annotation: 

125 ret['start_line'] = count + 1 

126 ret['last_line'] = count + 1 

127 parts = re.match(r'^(\s+)', line) 

128 ret['indent'] = parts.group(1) 

129 mode = 2 

130 

131 elif mode == 2: 

132 if line.strip() == '' or line.strip()[0:1] == '#': 

133 mode = 3 

134 else: 

135 ret['last_line'] = count + 1 

136 

137 else: 

138 break 

139 

140 count += 1 

141 

142 if mode != 3: 

143 raise RuntimeError("Unable to find '{}' in file {}".format(self.__annotation, self.source())) 

144 

145 return ret 

146 

147 # ------------------------------------------------------------------------------------------------------------------ 

148 def source_with_constants(self, constants: Dict[str, int]) -> str: 

149 """ 

150 Returns the source of the module with the class that acts like a namespace for constants with new constants. 

151 

152 :param dict[str,int] constants: The new constants. 

153 

154 :rtype: str 

155 """ 

156 old_lines = self.source().split("\n") 

157 info = self.__extract_info(old_lines) 

158 

159 new_lines = old_lines[0:info['start_line']] 

160 

161 for constant, value in sorted(constants.items()): 

162 new_lines.append("{0}{1} = {2}".format(info['indent'], str(constant), str(value))) 

163 

164 new_lines.extend(old_lines[info['last_line']:]) 

165 

166 return "\n".join(new_lines) 

167 

168# ----------------------------------------------------------------------------------------------------------------------