Robot Framework Integrated Development Environment (RIDE)
settings.py
Go to the documentation of this file.
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 
16 import os
17 import shutil
18 
19 from ..context import SETTINGS_DIRECTORY, LIBRARY_XML_DIRECTORY
20 from .configobj import ConfigObj, ConfigObjError, Section, UnreprError
21 from .excludes_class import Excludes
22 from ..publish import RideSettingsChanged
23 
24 
25 def initialize_settings(path, dest_file_name=None):
26  if not os.path.exists(SETTINGS_DIRECTORY):
27  os.makedirs(SETTINGS_DIRECTORY)
28  (path, error) = _copy_or_migrate_user_settings(
29  SETTINGS_DIRECTORY, path, dest_file_name)
30  if error:
31  raise ConfigurationError(error)
32  return path
33 
34 
35 
43 def _copy_or_migrate_user_settings(settings_dir, source_path, dest_file_name):
44  m_error = None
45  if not dest_file_name:
46  dest_file_name = os.path.basename(source_path)
47  settings_path = os.path.join(settings_dir, dest_file_name)
48  if not os.path.exists(settings_path):
49  shutil.copyfile(source_path, settings_path)
50  # print("DEBUG: source %s new settings %s\n" %(source_path,settings_path))
51  else:
52  try:
53  SettingsMigrator(source_path, settings_path).migrate()
54  # print(f"DEBUG: settings After migration {source_path} with {settings_path}")
55  except ConfigObjError as parsing_error:
56  print("WARNING! corrupted configuration file replaced with defaults")
57  print(parsing_error)
58  m_error = parsing_error
59  shutil.copyfile(settings_path, settings_path + '_old_broken')
60  print("(backed up corrupted configuration file at: %s)" %
61  (settings_path + '_old_broken'))
62  shutil.copyfile(source_path, settings_path)
63  # print("DEBUG: source %s corrupted settings %s\n" % (source_path, settings_path))
64  finally: # DEBUG Try to merge some settings
65  # print("DEBUG: Finally merge() %s\n" % settings_path)
66  SettingsMigrator(source_path, settings_path).merge()
67  return os.path.abspath(settings_path), m_error
68 
69 
71 
72  SETTINGS_VERSION = 'settings_version'
73 
74  def __init__(self, default_path, user_path):
75  self._default_settings_default_settings = ConfigObj(default_path, unrepr=True)
76  self._user_path_user_path = user_path
77  # print("DEBUG: Settings migrator 1: %s\ndefault_path %s" % (self._default_settings.__repr__(), default_path))
78  try:
79  self._old_settings_old_settings = ConfigObj(user_path, unrepr=True)
80  except UnreprError as err: # DEBUG errored file
81  # print("DEBUG: Settings migrator ERROR -------- %s path %s" %
82  # (self._old_settings.__repr__(), user_path))
83  raise ConfigurationError("Invalid config file '%s': %s" %
84  (user_path, err))
85  # print("DEBUG: Settings migrator old_settings: %s\nuser_path %s" %
86  # (self._old_settings.__repr__(), self._user_path))
87  # print("DEBUG: Settings migrator default_settings: %s\nsettings_path "
88  # "%s" % (self._default_settings.__repr__(), default_path))
89 
90  def migrate(self):
91  # Add migrations here.
92  # idea is that we are able to migrate from any of the previous settings
93  # versions to the current one by applying as many migration scripts as
94  # is needed --> so don't do migrate_from_0_to_3 or something other
95  # that will leap over some versions to save space
96  # NOTE!
97  # Don't count on default settings when giving values in migration scripts
98  # as default values could change in the future --> state after your
99  # migration is something else then what you intended and this could
100  # mess up the next migration script(s)
101  if not self._old_settings_old_settings.get(self.SETTINGS_VERSIONSETTINGS_VERSION):
102  self.migrate_from_0_to_1migrate_from_0_to_1(self._old_settings_old_settings)
103  if self._old_settings_old_settings.get(self.SETTINGS_VERSIONSETTINGS_VERSION) == 1:
104  self.migrate_from_1_to_2migrate_from_1_to_2(self._old_settings_old_settings)
105  if self._old_settings_old_settings.get(self.SETTINGS_VERSIONSETTINGS_VERSION) == 2:
106  self.migrate_from_2_to_3migrate_from_2_to_3(self._old_settings_old_settings)
107  if self._old_settings_old_settings.get(self.SETTINGS_VERSIONSETTINGS_VERSION) == 3:
108  self.migrate_from_3_to_4migrate_from_3_to_4(self._old_settings_old_settings)
109  if self._old_settings_old_settings.get(self.SETTINGS_VERSIONSETTINGS_VERSION) == 4:
110  self.migrate_from_4_to_5migrate_from_4_to_5(self._old_settings_old_settings)
111  if self._old_settings_old_settings.get(self.SETTINGS_VERSIONSETTINGS_VERSION) == 5:
112  self.migrate_from_5_to_6migrate_from_5_to_6(self._old_settings_old_settings)
113  if self._old_settings_old_settings.get(self.SETTINGS_VERSIONSETTINGS_VERSION) == 6:
114  self.migrate_from_6_to_7migrate_from_6_to_7(self._old_settings_old_settings)
115  if self._old_settings_old_settings.get(self.SETTINGS_VERSIONSETTINGS_VERSION) == 7:
116  self.migrate_from_7_to_8migrate_from_7_to_8(self._old_settings_old_settings)
117  # if self._old_settings.get(self.SETTINGS_VERSION) == 8:
118  # self.migrate_from_8_to_9(self._old_settings)
119  self.mergemerge()
120 
121  def merge(self):
122  # print("DEBUG: Merge before: %s\n", self._default_settings.__repr__())
123  self._default_settings_default_settings.merge(self._old_settings_old_settings)
124  # print("DEBUG: Merge after: %s, old%s\n" % (self._default_settings.__repr__(), self._old_settings.__repr__()))
125  self._write_merged_settings_write_merged_settings(self._default_settings_default_settings, self._user_path_user_path)
126 
127  def migrate_from_0_to_1(self, settings):
128  if settings.get('Colors', {}).get('text library keyword') == 'blue':
129  settings['Colors']['text library keyword'] = '#0080C0'
130  settings[self.SETTINGS_VERSIONSETTINGS_VERSION] = 1
131 
132  def migrate_from_1_to_2(self, settings):
133  # See issue http://code.google.com/p/robotframework-ride/issues/detail?id=925
134  # And other reported issues about test run failure after pythonpath was added
135  # to run
136  pythonpath = settings.get('pythonpath', [])
137  if pythonpath:
138  settings['pythonpath'] = [p.strip() for p
139  in pythonpath if p.strip()]
140  settings[self.SETTINGS_VERSIONSETTINGS_VERSION] = 2
141 
142  def migrate_from_2_to_3(self, settings):
143  # See issue http://code.google.com/p/robotframework-ride/issues/detail?id=1107
144  old_excludes = os.path.join(SETTINGS_DIRECTORY, 'excludes')
145  if os.path.isfile(old_excludes):
146  with open(old_excludes) as f:
147  old = f.read()
148  new = '\n'.join(d for d in old.split('\n') if os.path.isdir(d))+'\n'
149  with open(old_excludes, 'wb') as f:
150  f.write(new.encode('UTF-8'))
151  settings[self.SETTINGS_VERSIONSETTINGS_VERSION] = 3
152 
153  def migrate_from_3_to_4(self, settings):
154  # See issue http://code.google.com/p/robotframework-ride/issues/detail?id=1124
155  font_size = settings.get('font size', None)
156  if font_size and font_size == 11:
157  settings['font size'] = 8
158  settings[self.SETTINGS_VERSIONSETTINGS_VERSION] = 4
159 
160  def migrate_from_4_to_5(self, settings):
161  # Changed color section name
162  # see http://code.google.com/p/robotframework-ride/issues/detail?id=1206
163  colors = settings.get('Colors', None)
164  if colors:
165  settings['Grid Colors'] = colors
166  del settings['Colors']
167  settings[self.SETTINGS_VERSIONSETTINGS_VERSION] = 5
168 
169  def migrate_from_5_to_6(self, settings):
170  # Made generic Text Edit and Grid sections.
171  grid_colors = settings.get('Grid Colors', None)
172  if grid_colors:
173  settings['Grid'] = grid_colors
174  del settings['Grid Colors']
175  grid_font_size = settings.get('font size', None)
176  if grid_font_size:
177  settings['Grid']['font size'] = grid_font_size
178  del settings['font size']
179  text_edit_colors = settings.get('Text Edit Colors', None)
180  if text_edit_colors:
181  settings['Text Edit'] = text_edit_colors
182  del settings['Text Edit Colors']
183  text_font_size = settings.get('text edit font size', None)
184  if text_font_size:
185  settings['Text Edit']['font size'] = text_font_size
186  del settings['text edit font size']
187  settings[self.SETTINGS_VERSIONSETTINGS_VERSION] = 6
188 
189  def migrate_from_6_to_7(self, settings):
190  settings['use installed robot libraries'] = True
191  settings[self.SETTINGS_VERSIONSETTINGS_VERSION] = 7
192 
193  def migrate_from_7_to_8(self, settings):
194  installed_rf_libs = settings.get('use installed robot libraries', None)
195  if installed_rf_libs:
196  del settings['use installed robot libraries']
197  for name in [
198  'BuiltIn', 'Collections', 'DateTime', 'Dialogs', 'Easter', 'OperatingSystem', 'Process',
199  'Remote', 'Screenshot', 'String', 'Telnet', 'XML']:
200  lib_xml_path = os.path.join(LIBRARY_XML_DIRECTORY, '{}.xml'.format(name))
201  if os.path.exists(lib_xml_path):
202  os.remove(lib_xml_path)
203  settings[self.SETTINGS_VERSIONSETTINGS_VERSION] = 8
204 
205  """
206  def migrate_from_8_to_9(self, settings):
207  # mainframe_size = settings.get('mainframe size', (1100, 700))
208  parameters = [
209  'mainframe_size',
210  'mainframe_position',
211  'mainframe_maximized',
212  'default_directory',
213  'list_variable_columns',
214  'list_col_min_width',
215  'list_col_max_width',
216  'auto_imports',
217  'library_xml_directories',
218  'txt_number_of_spaces',
219  'txt_format_separator',
220  'line_separator',
221  'default_file_format',
222  'check_for_updates',
223  'last_update_check',
224  'install_root'
225  ]
226  sec_text = ['font size'] # [Text Edit]
227  sec_grid = [ # [Grid]
228  'font_size',
229  'fixed_font',
230  'col_size',
231  'max_col_size',
232  'auto_size_cols',
233  'text_user_keyword',
234  'text_library_keyword',
235  'text_variable',
236  'text_unknown_variable',
237  'text_commented',
238  'text_string',
239  'text_empty',
240  'background_assign',
241  'background_keyword',
242  'background_mandatory',
243  'background_optional',
244  'background_must_be_empty',
245  'background_unknown',
246  'background_error',
247  'background_highlight'
248  ]
249  try:
250  for keyname in parameters:
251  self._key_with_underscore(settings, keyname)
252  for keyname in sec_text:
253  self._key_with_underscore(settings, keyname, 'Text Edit')
254  for keyname in sec_grid:
255  self._key_with_underscore(settings, keyname, 'Grid')
256  except AttributeError: # DEBUG Ignore errors
257  # raise ConfigObjError
258  pass
259  settings[self.SETTINGS_VERSION] = 9
260  """
261 
262  @staticmethod
263  def _key_with_underscore(settings, keyname, section=None):
264  keyname_old = keyname.replace('_', ' ')
265  if not section:
266  value = settings.get(keyname_old, None)
267  if value:
268  settings[keyname] = value
269  del settings[keyname_old]
270  else:
271  value = settings.get(section, {}).get(keyname_old, None)
272  if value:
273  settings[section][keyname] = value
274  del settings[section][keyname_old]
275 
276  @staticmethod
277  def _write_merged_settings(settings, path):
278  try:
279  with open(path, 'wb') as outfile: # DEBUG used to be 'wb'
280  settings.write(outfile) # DEBUG .encoding('UTF-8')
281  except IOError:
282  raise RuntimeError(
283  'Could not open settings file "%s" for writing' % path)
284 
285 
286 
288 
289 
290 
291 class ConfigurationError(Exception):
292 
293 
294 class _Section():
295 
296  def __init__(self, section, parent=None, name=''):
297  self._config_obj_config_obj = section
298  self._parent_parent = parent
299  self._name_name = name
300 
301  def save(self):
302  self._parent_parent.save()
303 
304  def __setitem__(self, name, value):
305  self.setset(name, value)
306 
307  def __getitem__(self, name):
308  value = self._config_obj_config_obj[name]
309  if isinstance(value, Section):
310  return _Section(value, self, name)
311  return value
312 
313  def __iter__(self):
314  return iter(self._config_obj_config_obj)
315 
316  def __len__(self):
317  return len(self._config_obj_config_obj)
318 
319 
320  def iteritems(self):
321  return self._config_obj_config_obj.items()
322 
323  def has_setting(self, name):
324  return name in self._config_obj_config_obj
325 
326 
327  def get(self, name, default):
328  try:
329  return self[name]
330  except KeyError:
331  self.setset(name, default)
332  return default
333 
334 
335  def get_without_default(self, name):
336  try:
337  return self[name]
338  except KeyError:
339  return None
340 
341 
348  def set(self, name, value, autosave=True, override=True):
349  if self._is_section_is_section(name) and not isinstance(value, _Section):
350  raise SectionError("Cannot override section with value.")
351  if isinstance(value, _Section):
352  if override:
353  self._config_obj_config_obj[name] = {}
354  for key, _value in value._config_obj.items():
355  self[name].set(key, _value, autosave, override)
356  elif name not in self._config_obj_config_obj or override:
357  old = self._config_obj_config_obj[name] if name in self._config_obj_config_obj else None
358  self._config_obj_config_obj[name] = value
359  if autosave:
360  self.savesave()
362  keys=[self._name_name, name], old=old, new=value).publish()
363 
364 
368  def set_values(self, settings, autosave=True, override=True):
369  if settings:
370  for key, value in settings.items():
371  self.setset(key, value, autosave=False, override=override)
372  if autosave:
373  self.savesave()
374  return self
375 
376 
377  def set_defaults(self, settings_dict=None, **settings):
378  settings_dict = settings_dict or {}
379  settings_dict.update(settings)
380  return self.set_valuesset_values(settings_dict, override=False)
381 
382 
383  def add_section(self, name, **defaults):
384  if name in self._config_obj_config_obj and \
385  not isinstance(self._config_obj_config_obj[name], Section):
386  raise SectionError('Cannot override value with section.')
387  if name not in self._config_obj_config_obj:
388  self._config_obj_config_obj[name] = {}
389  return self[name].set_defaults(**defaults)
390 
391  def _is_section(self, name):
392  return name in self._config_obj_config_obj and \
393  isinstance(self._config_obj_config_obj[name], Section)
394 
395 
397 
398  def __init__(self, user_path):
399  try:
400  _Section.__init__(self, ConfigObj(user_path, unrepr=True))
401  except UnreprError as error:
402  raise ConfigurationError(error)
403  self.excludesexcludes = Excludes(SETTINGS_DIRECTORY)
404 
405  def save(self):
406  self._config_obj_config_obj.write()
407 
408 
410 
411  def __init__(self, path=None):
412  if path:
413  self._default_path_default_path = path
414  else:
415  self._default_path_default_path = os.path.join(os.path.dirname(__file__), 'settings.cfg')
416  # print("DEBUG: RideSettings, default_path %s\n" % self._default_path)
417  user_path = initialize_settings(self._default_path_default_path)
418  Settings.__init__(self, user_path)
419  self._settings_dir_settings_dir = os.path.dirname(user_path)
420  # print("DEBUG: RideSettings, self._settings_dir %s\n" % self._settings_dir)
421  self.setset('install root', os.path.dirname(os.path.dirname(__file__)))
422 
423 
424  def get_path(self, *parts):
425  return os.path.join(self._settings_dir_settings_dir, *parts)
An object to read, create, and write config files.
Definition: configobj.py:1098
def get_path(self, *parts)
Returns path which combines settings directory and given parts.
Definition: settings.py:424
Used when section is tried to replace with normal value or vice versa.
Definition: settings.py:287
def _key_with_underscore(settings, keyname, section=None)
Definition: settings.py:263
def __init__(self, default_path, user_path)
Definition: settings.py:74
def __setitem__(self, name, value)
Definition: settings.py:304
def iteritems(self)
Returns an iterator over the (key,value) items of the section.
Definition: settings.py:320
def set_defaults(self, settings_dict=None, **settings)
Sets defaults based on dict and kwargs, kwargs having precedence.
Definition: settings.py:377
def get(self, name, default)
Returns specified setting or (automatically set) default.
Definition: settings.py:327
def get_without_default(self, name)
Returns specified setting or None if setting is not defined.
Definition: settings.py:335
def __init__(self, section, parent=None, name='')
Definition: settings.py:296
def set_values(self, settings, autosave=True, override=True)
Set values from settings.
Definition: settings.py:368
def add_section(self, name, **defaults)
Creates section or updates existing section with defaults.
Definition: settings.py:383
def set(self, name, value, autosave=True, override=True)
Sets setting 'name' value to 'value'.
Definition: settings.py:348
Sent when settings are changed.
Definition: messages.py:173
def write(msg, level='INFO', html=False)
Writes the message to the log file using the given level.
Definition: logger.py:86
def initialize_settings(path, dest_file_name=None)
Definition: settings.py:25
def _copy_or_migrate_user_settings(settings_dir, source_path, dest_file_name)
Creates settings directory and copies or merges the source to there.
Definition: settings.py:43