Coverage for src/robotide/preferences/settings.py: 81%

256 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-06 10:40 +0100

1# Copyright 2008-2015 Nokia Networks 

2# Copyright 2016- Robot Framework Foundation 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); 

5# you may not use this file except in compliance with the License. 

6# You may obtain a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, 

12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

13# See the License for the specific language governing permissions and 

14# limitations under the License. 

15 

16import os 2a sb

17import shutil 2a sb

18 

19from ..context import SETTINGS_DIRECTORY, LIBRARY_XML_DIRECTORY, EXECUTABLE 2a sb

20# from .configobj import ConfigObj, ConfigObjError, Section, UnreprError 

21from . import ConfigObj, ConfigObjError, Section, UnreprError 2a sb

22from .excludes_class import Excludes 2a sb

23from ..publish import RideSettingsChanged 2a sb

24 

25FONT_SIZE = 'font size' 2a sb

26GRID_COLORS = 'Grid Colors' 2a sb

27USE_INSTALLED = 'use installed robot libraries' 2a sb

28 

29 

30def initialize_settings(path, dest_file_name=None): 2a sb

31 if not os.path.exists(SETTINGS_DIRECTORY): 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb Rc0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

32 os.makedirs(SETTINGS_DIRECTORY) 2:b[b]bRc

33 if not os.path.exists(path): 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb Rc0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

34 path = os.path.join(SETTINGS_DIRECTORY, path) 2:bRc

35 (path, error) = _copy_or_migrate_user_settings( 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb Rc0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

36 SETTINGS_DIRECTORY, path, dest_file_name) 

37 if error: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb Abn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

38 raise ConfigurationError(error) 2Ab

39 return path 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

40 

41 

42def _copy_or_migrate_user_settings(settings_dir, source_path, dest_file_name): 2a sb

43 """ Creates settings directory and copies or merges the source to there. 

44 

45 In case source already exists, merge is done. 

46 Destination file name is the source_path's file name unless dest_file_name 

47 is given. 

48 """ 

49 m_error = None 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb Rc0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

50 if not os.path.exists(source_path): 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb Rc0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

51 raise(FileNotFoundError(source_path)) 2Rc

52 else: 

53 try: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb 0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

54 shutil.copyfile(source_path, source_path + '._backup') 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb 0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

55 except (IOError, FileExistsError, PermissionError, OSError): 

56 backup_file = os.path.join(SETTINGS_DIRECTORY, os.path.basename(source_path)) 

57 shutil.copyfile(source_path, backup_file + '._backup') 

58 if not dest_file_name: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb 0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

59 dest_file_name = os.path.basename(source_path) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

60 settings_path = os.path.join(settings_dir, dest_file_name) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb 0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

61 if not os.path.exists(settings_path): 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb 0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

62 shutil.copyfile(source_path, settings_path) 2:b[b]b

63 # print("DEBUG: source %s new settings %s\n" %(source_path,settings_path)) 

64 else: 

65 try: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbb 0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

66 SettingsMigrator(source_path, settings_path).migrate() 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbb 0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

67 # print(f"DEBUG: settings After migration {source_path} with {settings_path}") 

68 except ConfigObjError as parsing_error: 20bAb

69 print("WARNING! corrupted configuration file replaced with defaults\n" 2Ab

70 f"A backup of the settings is at: {source_path + '._backup'}") 

71 print(parsing_error) 2Ab

72 m_error = parsing_error 2Ab

73 shutil.copyfile(settings_path, settings_path + '_old_broken') 2Ab

74 print(f"(backed up corrupted configuration file at: {settings_path + '_old_broken'}") 2Ab

75 shutil.copyfile(source_path, settings_path) 2Ab

76 # print("DEBUG: source %s corrupted settings %s\n" % (source_path, settings_path)) 

77 finally: # DEBUG Try to merge some settings 

78 # print("DEBUG: Finally merge() %s\n" % settings_path) 

79 SettingsMigrator(source_path, settings_path).merge() 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbb 0bAbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

80 return os.path.abspath(settings_path), m_error 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rb:b[b]bb Abn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

81 

82 

83class SettingsMigrator(object): 2a sb

84 

85 SETTINGS_VERSION = 'settings_version' 2a sb

86 

87 def __init__(self, default_path, user_path): 2a sb

88 self._default_settings = ConfigObj(default_path, encoding='UTF-8', unrepr=True) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M PcN O P rbb 0bAbMc^b_b`bn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

89 self._user_path = user_path 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M PcN O P rbb 0bAbMc^b_b`bn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

90 # print("DEBUG: Settings migrator 1: %s\ndefault_path %s" % (self._default_settings.__repr__(), default_path)) 

91 try: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M PcN O P rbb 0bAbMc^b_b`bn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

92 self._old_settings = ConfigObj(user_path, encoding='UTF-8', unrepr=True) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M PcN O P rbb 0bAbMc^b_b`bn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

93 except UnreprError as err: # DEBUG errored file 20bAb

94 # print("DEBUG: Settings migrator ERROR -------- %s path %s" % 

95 # (self._old_settings.__repr__(), user_path)) 

96 raise ConfigurationError("Invalid config file '%s': %s" % 20b

97 (user_path, err)) 

98 

99 def migrate(self): 2a sb

100 # Add migrations here. 

101 # idea is that we are able to migrate from any of the previous settings 

102 # versions to the current one by applying as many migration scripts as 

103 # is needed --> so don't do migrate_from_0_to_3 or something other 

104 # that will leap over some versions to save space 

105 # NOTE! 

106 # Don't count on default settings when giving values in migration scripts 

107 # as default values could change in the future --> state after your 

108 # migration is something else then what you intended and this could 

109 # mess up the next migration script(s) 

110 if not self._old_settings.get(self.SETTINGS_VERSION): 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp #b;b{bbcccecicNcM N O P rbb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

111 self.migrate_from_0_to_1(self._old_settings) 2#bb

112 if self._old_settings.get(self.SETTINGS_VERSION) == 1: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp #b;b{bbcccecicNcM N O P rbb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

113 self.migrate_from_1_to_2(self._old_settings) 2#b;bb

114 if self._old_settings.get(self.SETTINGS_VERSION) == 2: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp #b;b{bbcccecicNcM N O P rbb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

115 self.migrate_from_2_to_3(self._old_settings) 2#b;b{bb

116 if self._old_settings.get(self.SETTINGS_VERSION) == 3: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp #b;b{bbcccecicNcM N O P rbb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

117 self.migrate_from_3_to_4(self._old_settings) 2#b;b{bbcb

118 if self._old_settings.get(self.SETTINGS_VERSION) == 4: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp #b;b{bbcccecicNcM N O P rbb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

119 self.migrate_from_4_to_5(self._old_settings) 2#b;b{bbcccb

120 if self._old_settings.get(self.SETTINGS_VERSION) == 5: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp #b;b{bbcccecicNcM N O P rbb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

121 self.migrate_from_5_to_6(self._old_settings) 2#b;b{bbcccecb

122 if self._old_settings.get(self.SETTINGS_VERSION) == 6: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp #b;b{bbcccecicNcM N O P rbb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

123 self.migrate_from_6_to_7(self._old_settings) 2#b;b{bbcccecicb

124 if self._old_settings.get(self.SETTINGS_VERSION) == 7: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp #b;b{bbcccecicNcM N O P rbb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

125 self.migrate_from_7_to_8(self._old_settings) 2#b;b{bbcccecicNcb

126 self.merge() 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp #b;b{bbcccecicNcM N O P rbb n Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

127 

128 def merge(self): 2a sb

129 # print("DEBUG: Merge before: %s\n", self._default_settings.__repr__()) 

130 self._default_settings.merge(self._old_settings) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M PcN O P rbb AbMc^b_b`bn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

131 # print("DEBUG: Merge after: %s, old%s\n" % (self._default_settings.__repr__(), self._old_settings.__repr__())) 

132 self._write_merged_settings(self._default_settings, self._user_path) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M PcN O P rbb AbMc^b_b`bn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

133 

134 def migrate_from_0_to_1(self, settings): 2a sb

135 if settings.get('Colors', {}).get('text library keyword') == 'blue': 135 ↛ 136line 135 didn't jump to line 136 because the condition on line 135 was never true1b

136 settings['Colors']['text library keyword'] = '#0080C0' 

137 settings[self.SETTINGS_VERSION] = 1 1b

138 

139 def migrate_from_1_to_2(self, settings): 2a sb

140 # See issue http://code.google.com/p/robotframework-ride/issues/detail?id=925 

141 # And other reported issues about test run failure after pythonpath was added 

142 # to run 

143 pythonpath = settings.get('pythonpath', []) 1b

144 if pythonpath: 144 ↛ 145line 144 didn't jump to line 145 because the condition on line 144 was never true1b

145 settings['pythonpath'] = [p.strip() for p 

146 in pythonpath if p.strip()] 

147 settings[self.SETTINGS_VERSION] = 2 1b

148 

149 def migrate_from_2_to_3(self, settings): 2a sb

150 # See issue http://code.google.com/p/robotframework-ride/issues/detail?id=1107 

151 old_excludes = os.path.join(SETTINGS_DIRECTORY, 'excludes') 1b

152 if os.path.isfile(old_excludes): 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true1b

153 with open(old_excludes) as f: 

154 old = f.read() 

155 new = '\n'.join(d for d in old.split('\n') if os.path.isdir(d))+'\n' 

156 with open(old_excludes, 'wb') as f: 

157 f.write(new.encode('UTF-8')) 

158 settings[self.SETTINGS_VERSION] = 3 1b

159 

160 def migrate_from_3_to_4(self, settings): 2a sb

161 # See issue http://code.google.com/p/robotframework-ride/issues/detail?id=1124 

162 font_size = settings.get(FONT_SIZE, None) 1b

163 if font_size and font_size == 11: 163 ↛ 164line 163 didn't jump to line 164 because the condition on line 163 was never true1b

164 settings[FONT_SIZE] = 8 

165 settings[self.SETTINGS_VERSION] = 4 1b

166 

167 def migrate_from_4_to_5(self, settings): 2a sb

168 # Changed color section name 

169 # see http://code.google.com/p/robotframework-ride/issues/detail?id=1206 

170 colors = settings.get('Colors', None) 1b

171 if colors: 171 ↛ 172line 171 didn't jump to line 172 because the condition on line 171 was never true1b

172 settings[GRID_COLORS] = colors 

173 del settings['Colors'] 

174 settings[self.SETTINGS_VERSION] = 5 1b

175 

176 def migrate_from_5_to_6(self, settings): 2a sb

177 # Made generic Text Edit and Grid sections. 

178 grid_colors = settings.get(GRID_COLORS, None) 1b

179 if grid_colors: 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true1b

180 settings['Grid'] = grid_colors 

181 del settings[GRID_COLORS] 

182 grid_font_size = settings.get(FONT_SIZE, None) 1b

183 if grid_font_size: 183 ↛ 184line 183 didn't jump to line 184 because the condition on line 183 was never true1b

184 settings['Grid'][FONT_SIZE] = grid_font_size 

185 del settings[FONT_SIZE] 

186 text_edit_colors = settings.get('Text Edit Colors', None) 1b

187 if text_edit_colors: 187 ↛ 188line 187 didn't jump to line 188 because the condition on line 187 was never true1b

188 settings['Text Edit'] = text_edit_colors 

189 del settings['Text Edit Colors'] 

190 text_font_size = settings.get('text edit font size', None) 1b

191 if text_font_size: 191 ↛ 192line 191 didn't jump to line 192 because the condition on line 191 was never true1b

192 settings['Text Edit'][FONT_SIZE] = text_font_size 

193 del settings['text edit font size'] 

194 settings[self.SETTINGS_VERSION] = 6 1b

195 

196 def migrate_from_6_to_7(self, settings): 2a sb

197 settings[USE_INSTALLED] = True 1b

198 settings[self.SETTINGS_VERSION] = 7 1b

199 

200 def migrate_from_7_to_8(self, settings): 2a sb

201 installed_rf_libs = settings.get(USE_INSTALLED, None) 1b

202 if installed_rf_libs: 202 ↛ 210line 202 didn't jump to line 210 because the condition on line 202 was always true1b

203 del settings[USE_INSTALLED] 1b

204 for name in [ 1b

205 'BuiltIn', 'Collections', 'DateTime', 'Dialogs', 'Easter', 'OperatingSystem', 'Process', 

206 'Remote', 'Screenshot', 'String', 'Telnet', 'XML']: 

207 lib_xml_path = os.path.join(LIBRARY_XML_DIRECTORY, '{}.xml'.format(name)) 1b

208 if os.path.exists(lib_xml_path): 208 ↛ 209line 208 didn't jump to line 209 because the condition on line 208 was never true1b

209 os.remove(lib_xml_path) 

210 settings[self.SETTINGS_VERSION] = 8 1b

211 

212 @staticmethod 2a sb

213 def _key_with_underscore(settings, keyname, section=None): 2a sb

214 keyname_old = keyname.replace('_', ' ') 

215 if not section: 

216 value = settings.get(keyname_old, None) 

217 if value: 

218 settings[keyname] = value 

219 del settings[keyname_old] 

220 else: 

221 value = settings.get(section, {}).get(keyname_old, None) 

222 if value: 

223 settings[section][keyname] = value 

224 del settings[section][keyname_old] 

225 

226 @staticmethod 2a sb

227 def _write_merged_settings(settings, path): 2a sb

228 try: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M PcN O P rbb AbMc^b_b`bn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

229 with open(path, 'wb') as outfile: # DEBUG used to be 'wb' 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M PcN O P rbb AbMc^b_b`bn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

230 settings.write(outfile) # DEBUG .encoding('UTF-8') 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M PcN O P rbb Ab^b_b`bn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

231 except IOError: 2Mc

232 raise RuntimeError( 2Mc

233 'Could not open settings file "%s" for writing' % path) 

234 

235 

236class SectionError(Exception): 2a sb

237 """Used when section is tried to replace with normal value or vice versa.""" 

238 

239 

240class ConfigurationError(Exception): 2a sb

241 """Used when settings file is invalid""" 

242 

243 

244class _Section(object): 2a sb

245 

246 def __init__(self, section, parent=None, name=''): 2a sb

247 self.config_obj = section 2a # jcy kcq r s t u $ Cbo % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k Gbl _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F lcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcG v H I J K L w x DbibjbkblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzbM N O P rbb 1b^b_b`b|b}bTbEbUb~btb4bubFbSb$b%b'b(b)b*b+b,b-b.bQc=bVbWbXbSc2bYb?bac@bOcZbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

248 self._parent = parent 2a # jcy kcq r s t u $ Cbo % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k Gbl _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F lcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcG v H I J K L w x DbibjbkblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzbM N O P rbb 1b^b_b`b|b}bTbEbUb~btb4bubFbSb$b%b'b(b)b*b+b,b-b.bQc=bVbWbXbSc2bYb?bac@bOcZbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

249 self._name = name 2a # jcy kcq r s t u $ Cbo % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k Gbl _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F lcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcG v H I J K L w x DbibjbkblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzbM N O P rbb 1b^b_b`b|b}bTbEbUb~btb4bubFbSb$b%b'b(b)b*b+b,b-b.bQc=bVbWbXbSc2bYb?bac@bOcZbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

250 

251 def save(self): 2a sb

252 self._parent.save() 2a /bwbBbvbxbybzbTbEbUbtbubFbSbZb

253 

254 def __setitem__(self, name, value): 2a sb

255 self.set(name, value) 2a 1bdcTbUbSb$b%b'b(b)b*b+b,b-b.bZbLc

256 

257 def __getitem__(self, name): 2a sb

258 value = self.config_obj[name] 2a # 3bjcUcVcWcy kc/bXcq r s t u $ Zc0c1cCbo % ' ( Tc) * + , - . / : ; = ? @ 2cd e f g h i [ ] ^ j k Gbl _ ` { c | } 3c~ abbb4c5c6c7c8c9c!c#c$c%c'c(c)c*c+c,c-c.c/c:c;c=c?c@c5b6b7b8b9b!b[ccbdbebfbgbhbz A B C D E F lcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcG v H I J K L w x ]c^c_cDb`cibjb{ckblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzbM N O P rb1b|b}bTbEbUb~btb4bubFbSbQcZbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

259 if isinstance(value, Section): 2a # 3bjcUcVcWcy kcXcq r s t u $ Zc0c1cCbo % ' ( Tc) * + , - . / : ; = ? @ 2cd e f g h i [ ] ^ j k Gbl _ ` { c | } 3c~ abbb4c5c6c7c8c9c!c#c$c%c'c(c)c*c+c,c-c.c/c:c;c=c?c@c5b6b7b8b9b!b[ccbdbebfbgbhbz A B C D E F lcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcG v H I J K L w x ]c^c_cDb`cibjb{ckblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzbM N O P rb1b|b}bTbEbUb~btb4bubFbSbQcZbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

260 return _Section(value, self, name) 2a jcy kcq r s t u Cbd e f g h i j k Gbl c z A B C D E F lcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcG v H I J K L w x DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzbM |b}bTbEbUb~btb4bubFbSbZbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

261 return value 2a # 3bjcUcVcWcy kcXcq r s t u $ Zc0c1cCbo % ' ( Tc) * + , - . / : ; = ? @ 2cd e f g h i [ ] ^ j k Gbl _ ` { c | } 3c~ abbb4c5c6c7c8c9c!c#c$c%c'c(c)c*c+c,c-c.c/c:c;c=c?c@c5b6b7b8b9b!b[ccbdbebfbgbhbz A B C D E F lcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcG v H I J K L w x ]c^c_cDb`cibjb{ckblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp wbvbxbybzbM N O P rb1bQcn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

262 

263 def __iter__(self): 2a sb

264 return iter(self.config_obj) 1m

265 

266 def __len__(self): 2a sb

267 return len(self.config_obj) 2a 3bTc

268 

269 def iteritems(self): 2a sb

270 """Returns an iterator over the (key,value) items of the section""" 

271 return self.config_obj.items() 2a CbDb

272 

273 def has_setting(self, name): 2a sb

274 return name in self.config_obj 2a fcgchcv w x wbBbvbxbybzb

275 

276 def get(self, name, default): 2a sb

277 """Returns specified setting or (automatically set) default.""" 

278 try: 2a # 3bjcUcVcWcy kc/bXcq r s t u $ Cbo % ' ( Tc) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k Gbl _ ` { c | } ~ abbb5b6b7b8b9b!bcbdbebfbgbhbz A B C D E F lcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcG v H I J K L w x DbibjbkblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

279 return self[name] 2a # 3bjcUcVcWcy kc/bXcq r s t u $ Cbo % ' ( Tc) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k Gbl _ ` { c | } ~ abbb5b6b7b8b9b!bcbdbebfbgbhbz A B C D E F lcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcG v H I J K L w x DbibjbkblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

280 except KeyError: 2a 3b/bc 5b6b7b8b9b!bm

281 self.set(name, default) 2a 3b/bc 5b6b7b8b9b!bm

282 return default 2a 3b/bc 5b6b7b8b9b!bm

283 

284 def get_without_default(self, name): 2a sb

285 """Returns specified setting or None if setting is not defined.""" 

286 try: 2a jckclcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcn

287 return self[name] 2a jckclcmcncocpcqcrcsctcucvcwcxcyczcAcfcBcCcgcDchcEcFcGcHcIcJcKcn

288 except KeyError: 

289 return None 

290 

291 def set(self, name, value, autosave=True, override=True): 2a sb

292 """Sets setting 'name' value to 'value'. 

293 

294 'autosave' can be used to define whether to save automatically 

295 after values are changed. 'override' can be used to specify 

296 whether to override existing value or not. Setting which does 

297 not exist is anyway always created. 

298 """ 

299 if self._is_section(name) and not isinstance(value, _Section): 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbwbBbvbxbybzb1bdcTbEbUbtb4bubFbSb$b%b'b(b)b*b+b,b-b.b=bVbWbXb2bYb?bac@bOcZbLcm

300 raise SectionError("Cannot override section with value.") 24b

301 if isinstance(value, _Section): 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbwbBbvbxbybzb1bdcTbEbUbtbubFbSb$b%b'b(b)b*b+b,b-b.b=bVbWbXb2bYb?bac@bOcZbLcm

302 if override: 2tbub

303 self.config_obj[name] = {} 2tb

304 for key, _value in value.config_obj.items(): 2tbub

305 self[name].set(key, _value, autosave, override) 2tbub

306 elif name not in self.config_obj or override: 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbwbBbvbxbybzb1bdcTbEbUbtbubFbSb$b%b'b(b)b*b+b,b-b.b=bVbWbXb2bYb?bac@bOcZbLcm

307 old = self.config_obj[name] if name in self.config_obj else None 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbwbBbvbxbybzb1bdcTbEbUbtbubFbSb$b%b'b(b)b*b+b,b-b.b=bVbWbXb2bYb?bac@bZbLcm

308 self.config_obj[name] = value 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbwbBbvbxbybzb1bdcTbEbUbtbubFbSb$b%b'b(b)b*b+b,b-b.b=bVbWbXb2bYb?bac@bZbLcm

309 if autosave: 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbwbBbvbxbybzb1bdcTbEbUbtbubFbSb$b%b'b(b)b*b+b,b-b.b=bVbWbXb2bYb?bac@bZbLcm

310 self.save() 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbvb1bdcTbUbtbubSb$b%b'b(b)b*b+b,b-b.b=b?b@bZbLcm

311 RideSettingsChanged( 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbwbBbvbxbybzb1bdcTbEbUbtbubFbSb$b%b'b(b)b*b+b,b-b.b=bVbWbXb2bYb?bac@bZbLcm

312 keys=[self._name, name], old=old, new=value).publish() 

313 

314 def set_values(self, settings, autosave=True, override=True): 2a sb

315 """Set values from settings. 'settings' needs to be a dictionary. 

316 

317 See method set for more info about 'autosave' and 'override'. 

318 """ 

319 if settings: 2a Cbd e f g h i j k Gbl c DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzb|b}bTbEbUb~btb4bubFbSbVbWbXbSc2bYbn

320 for key, value in settings.items(): 2a wbBbvbxbybzbEbtbubFbVbWbXb2bYb

321 self.set(key, value, autosave=False, override=override) 2a wbBbvbxbybzbEbtbubFbVbWbXb2bYb

322 if autosave: 2a wbBbvbxbybzbEbtbubFbVbWbXb2bYb

323 self.save() 2a wbBbvbxbybzbEbtbubFbVbWbXbYb

324 return self 2a Cbd e f g h i j k Gbl c DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzb|b}bTbEbUb~btb4bubFbSbVbWbXbSc2bYbn

325 

326 def set_defaults(self, settings_dict=None, **settings): 2a sb

327 """Sets defaults based on dict and kwargs, kwargs having precedence.""" 

328 settings_dict = settings_dict or {} 2a Cbd e f g h i j k Gbl c DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzb|b}bTbEbUb~btb4bubFbSbVbWbn

329 settings_dict.update(settings) 2a Cbd e f g h i j k Gbl c DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzb|b}bTbEbUb~btb4bubFbSbVbWbn

330 return self.set_values(settings_dict, override=False) 2a Cbd e f g h i j k Gbl c DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzb|b}bTbEbUb~btb4bubFbSbVbWbn

331 

332 def add_section(self, name, **defaults): 2a sb

333 """Creates section or updates existing section with defaults.""" 

334 if name in self.config_obj and \ 2a Cbd e f g h i j k Gbl c DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzb|b}bdcTbEbUb~btb4bubFbSbn

335 not isinstance(self.config_obj[name], Section): 

336 raise SectionError('Cannot override value with section.') 2dc

337 if name not in self.config_obj: 2a Cbd e f g h i j k Gbl c DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzb|b}bTbEbUb~btb4bubFbSbn

338 self.config_obj[name] = {} 2a Cbd e f g h i j k Gbl c DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzb|b}bTbEbUb~btb4bubFbSbn

339 return self[name].set_defaults(**defaults) 2a Cbd e f g h i j k Gbl c DbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzb|b}bTbEbUb~btb4bubFbSbn

340 

341 def _is_section(self, name): 2a sb

342 return name in self.config_obj and \ 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbwbBbvbxbybzb1bdcTbEbUbtb4bubFbSb$b%b'b(b)b*b+b,b-b.b=bVbWbXb2bYb?bac@bOcZbLcm

343 isinstance(self.config_obj[name], Section) 

344 

345 

346class Settings(_Section): 2a sb

347 

348 def __init__(self, user_path): 2a sb

349 try: 2a # y q r s t u $ Cbo % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k Gbl _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x DbibjbkblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzbM N O P rbb Yc1b^b_b`bEbtbubFbSb$b%b'b(b)b*b+b,b-b.bQc=bVbWbXbSc2bYb?bac@bOcZbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

350 _Section.__init__(self, ConfigObj(user_path, encoding='UTF-8', unrepr=True)) 2a # y q r s t u $ Cbo % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k Gbl _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x DbibjbkblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzbM N O P rbb Yc1b^b_b`bEbtbubFbSb$b%b'b(b)b*b+b,b-b.bQc=bVbWbXbSc2bYb?bac@bOcZbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

351 except UnreprError as error: 2Yc

352 raise ConfigurationError(error) 2Yc

353 self.excludes = Excludes(SETTINGS_DIRECTORY) 2a # y q r s t u $ Cbo % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k Gbl _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x DbibjbkblbmbnbobpbqbHbIbJbKbLbMbNbObPbQbRbp wbBbvbxbybzbM N O P rbb 1b^b_b`bEbtbubFbSb$b%b'b(b)b*b+b,b-b.bQc=bVbWbXbSc2bYb?bac@bOcZbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

354 

355 def save(self): 2a sb

356 self.config_obj.write() 2a 3b/bCbo d e f g h i j k Gbl c 5b6b7b8b9b!bDbHbIbJbKbLbMbNbObPbQbRbwbBbvbxbybzb1bdcTbEbUbtbubFbSb$b%b'b(b)b*b+b,b-b.b=bVbWbXb2bYb?bac@bZbLcm

357 

358 

359class RideSettings(Settings): 2a sb

360 

361 def __init__(self, path=None): 2a sb

362 if path: 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

363 self._default_path = path 

364 else: 

365 path = os.getenv('RIDESETTINGS', 'user') 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

366 if path == 'user': 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

367 self._default_path = os.path.join(os.path.dirname(__file__), 'settings.cfg') 2a # y $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

368 elif path.endswith('.cfg') and os.path.exists(path): 368 ↛ 371line 368 didn't jump to line 371 because the condition on line 368 was always true1qrstu

369 self._default_path = path 1qrstu

370 # print(f"DEBUG: settings.py RideSettings SETTINGS {self._default_path=}") 

371 self.user_path = initialize_settings(self._default_path) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

372 Settings.__init__(self, self.user_path) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

373 self._settings_dir = os.path.dirname(self.user_path) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

374 # print(f"DEBUG: RideSettings, self._settings_dir={self._settings_dir}") 

375 self.get('install root', os.path.dirname(os.path.dirname(__file__))) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

376 self.executable = self.get('executable', EXECUTABLE) 2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

377 if self.executable != EXECUTABLE: 377 ↛ 378line 377 didn't jump to line 378 because the condition on line 377 was never true2a # y q r s t u $ o % ' ( ) * + , - . / : ; = ? @ d e f g h i [ ] ^ j k l _ ` { c | } ~ abbbcbdbebfbgbhbz A B C D E F G v H I J K L w x ibjbkblbmbnbobpbqbp M N O P rbn Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! m

378 digest = 0 

379 for c in EXECUTABLE: 

380 digest += ord(c) 

381 new_user_path = self.user_path.replace("settings.cfg", f"settings_{digest}.cfg") 

382 new_user_path = initialize_settings(self.user_path, new_user_path) 

383 Settings.__init__(self, new_user_path) 

384 self._settings_dir = os.path.dirname(new_user_path) 

385 self.set('install root', os.path.dirname(os.path.dirname(__file__))) 

386 self.set('executable', EXECUTABLE) 

387 self.set('last_settings_path', new_user_path) 

388 self.user_path = new_user_path 

389 

390 def get_path(self, *parts): 2a sb

391 """Returns path which combines settings directory and given parts.""" 

392 return os.path.join(self._settings_dir, *parts) 1aNOP