1 """Provides access to stored IDLE configuration information.
3 Refer to the comments at the beginning of config-main.def for a description of
4 the available configuration files and the design implemented to update user
5 configuration information. In particular, user configuration choices which
6 duplicate the defaults will be removed from the user's configuration files,
7 and if a file becomes empty, it will be deleted.
9 The contents of the user files may be altered using the Options/Configure IDLE
10 menu to access the configuration GUI (configDialog.py), or manually.
12 Throughout this module there is an emphasis on returning useable defaults
13 when a problem occurs in returning a requested configuration value back to
14 idle. This is to allow IDLE to continue to function in spite of errors in
15 the retrieval of config information. When a default is returned instead of
16 a requested config value, a message is printed to stderr to aid in
17 configuration problem notification and resolution.
23 from ConfigParser
import ConfigParser
, NoOptionError
, NoSectionError
25 class InvalidConfigType(Exception): pass
26 class InvalidConfigSet(Exception): pass
27 class InvalidFgBg(Exception): pass
28 class InvalidTheme(Exception): pass
30 class IdleConfParser(ConfigParser
):
32 A ConfigParser specialised for idle configuration file handling
34 def __init__(self
, cfgFile
, cfgDefaults
=None):
36 cfgFile - string, fully specified configuration file name
39 ConfigParser
.__init
__(self
,defaults
=cfgDefaults
)
41 def Get(self
, section
, option
, type=None, default
=None):
43 Get an option value for given section/option or return default.
44 If type is specified, return as type.
47 getVal
=self
.getboolean
52 if self
.has_option(section
,option
):
53 #return getVal(section, option, raw, vars, default)
54 return getVal(section
, option
)
58 def GetOptionList(self
,section
):
60 Get an option list for given section
62 if self
.has_section(section
):
63 return self
.options(section
)
64 else: #return a default value
69 Load the configuration file from disk
73 class IdleUserConfParser(IdleConfParser
):
75 IdleConfigParser specialised for user configuration handling.
78 def AddSection(self
,section
):
80 if section doesn't exist, add it
82 if not self
.has_section(section
):
83 self
.add_section(section
)
85 def RemoveEmptySections(self
):
87 remove any sections that have no options
89 for section
in self
.sections():
90 if not self
.GetOptionList(section
):
91 self
.remove_section(section
)
95 Remove empty sections and then return 1 if parser has no sections
98 self
.RemoveEmptySections()
104 def RemoveOption(self
,section
,option
):
106 If section/option exists, remove it.
107 Returns 1 if option was removed, 0 otherwise.
109 if self
.has_section(section
):
110 return self
.remove_option(section
,option
)
112 def SetOption(self
,section
,option
,value
):
114 Sets option to value, adding section if required.
115 Returns 1 if option was added or changed, otherwise 0.
117 if self
.has_option(section
,option
):
118 if self
.get(section
,option
)==value
:
121 self
.set(section
,option
,value
)
124 if not self
.has_section(section
):
125 self
.add_section(section
)
126 self
.set(section
,option
,value
)
129 def RemoveFile(self
):
131 Removes the user config file from disk if it exists.
133 if os
.path
.exists(self
.file):
137 """Update user configuration file.
139 Remove empty sections. If resulting config isn't empty, write the file
140 to disk. If config is empty, remove the file from disk if it exists.
143 if not self
.IsEmpty():
144 cfgFile
=open(self
.file,'w')
151 holds config parsers for all idle config files:
153 (idle install dir)/config-main.def
154 (idle install dir)/config-extensions.def
155 (idle install dir)/config-highlight.def
156 (idle install dir)/config-keys.def
158 (user home dir)/.idlerc/config-main.cfg
159 (user home dir)/.idlerc/config-extensions.cfg
160 (user home dir)/.idlerc/config-highlight.cfg
161 (user home dir)/.idlerc/config-keys.cfg
167 self
.CreateConfigHandlers()
171 def CreateConfigHandlers(self
):
173 set up a dictionary of config parsers for default and user
174 configurations respectively
176 #build idle install path
177 if __name__
!= '__main__': # we were imported
178 idleDir
=os
.path
.dirname(__file__
)
179 else: # we were exec'ed (for testing only)
180 idleDir
=os
.path
.abspath(sys
.path
[0])
181 userDir
=self
.GetUserCfgDir()
182 configTypes
=('main','extensions','highlight','keys')
185 for cfgType
in configTypes
: #build config file names
186 defCfgFiles
[cfgType
]=os
.path
.join(idleDir
,'config-'+cfgType
+'.def')
187 usrCfgFiles
[cfgType
]=os
.path
.join(userDir
,'config-'+cfgType
+'.cfg')
188 for cfgType
in configTypes
: #create config parsers
189 self
.defaultCfg
[cfgType
]=IdleConfParser(defCfgFiles
[cfgType
])
190 self
.userCfg
[cfgType
]=IdleUserConfParser(usrCfgFiles
[cfgType
])
192 def GetUserCfgDir(self
):
194 Creates (if required) and returns a filesystem directory for storing
198 userDir
=os
.path
.expanduser('~')
199 if userDir
!= '~': #'HOME' exists as a key in os.environ
200 if not os
.path
.exists(userDir
):
201 warn
=('\n Warning: HOME environment variable points to\n '+
202 userDir
+'\n but the path does not exist.\n')
203 sys
.stderr
.write(warn
)
205 if userDir
=='~': #we still don't have a home directory
206 #traditionally idle has defaulted to os.getcwd(), is this adeqate?
207 userDir
= os
.getcwd() #hack for no real homedir
208 userDir
=os
.path
.join(userDir
,cfgDir
)
209 if not os
.path
.exists(userDir
):
210 try: #make the config dir if it doesn't exist yet
213 warn
=('\n Warning: unable to create user config directory\n '+
215 sys
.stderr
.write(warn
)
218 def GetOption(self
, configType
, section
, option
, default
=None, type=None,
219 warn_on_default
=True):
221 Get an option value for given config type and given general
222 configuration section/option or return a default. If type is specified,
223 return as type. Firstly the user configuration is checked, with a
224 fallback to the default configuration, and a final 'catch all'
225 fallback to a useable passed-in default if the option isn't present in
226 either the user or the default configuration.
227 configType must be one of ('main','extensions','highlight','keys')
228 If a default is returned, and warn_on_default is True, a warning is
232 if self
.userCfg
[configType
].has_option(section
,option
):
233 return self
.userCfg
[configType
].Get(section
, option
, type=type)
234 elif self
.defaultCfg
[configType
].has_option(section
,option
):
235 return self
.defaultCfg
[configType
].Get(section
, option
, type=type)
236 else: #returning default, print warning
238 warning
= ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
239 ' problem retrieving configration option %r\n'
240 ' from section %r.\n'
241 ' returning default value: %r\n' %
242 (option
, section
, default
))
243 sys
.stderr
.write(warning
)
246 def SetOption(self
, configType
, section
, option
, value
):
247 """In user's config file, set section's option to value.
250 self
.userCfg
[configType
].SetOption(section
, option
, value
)
252 def GetSectionList(self
, configSet
, configType
):
254 Get a list of sections from either the user or default config for
255 the given config type.
256 configSet must be either 'user' or 'default'
257 configType must be one of ('main','extensions','highlight','keys')
259 if not (configType
in ('main','extensions','highlight','keys')):
260 raise InvalidConfigType
, 'Invalid configType specified'
261 if configSet
== 'user':
262 cfgParser
=self
.userCfg
[configType
]
263 elif configSet
== 'default':
264 cfgParser
=self
.defaultCfg
[configType
]
266 raise InvalidConfigSet
, 'Invalid configSet specified'
267 return cfgParser
.sections()
269 def GetHighlight(self
, theme
, element
, fgBg
=None):
271 return individual highlighting theme elements.
272 fgBg - string ('fg'or'bg') or None, if None return a dictionary
273 containing fg and bg colours (appropriate for passing to Tkinter in,
274 e.g., a tag_config call), otherwise fg or bg colour only as specified.
276 if self
.defaultCfg
['highlight'].has_section(theme
):
277 themeDict
=self
.GetThemeDict('default',theme
)
279 themeDict
=self
.GetThemeDict('user',theme
)
280 fore
=themeDict
[element
+'-foreground']
281 if element
=='cursor': #there is no config value for cursor bg
282 back
=themeDict
['normal-background']
284 back
=themeDict
[element
+'-background']
285 highlight
={"foreground": fore
,"background": back
}
286 if not fgBg
: #return dict of both colours
288 else: #return specified colour only
290 return highlight
["foreground"]
292 return highlight
["background"]
294 raise InvalidFgBg
, 'Invalid fgBg specified'
296 def GetThemeDict(self
,type,themeName
):
298 type - string, 'default' or 'user' theme type
299 themeName - string, theme name
300 Returns a dictionary which holds {option:value} for each element
301 in the specified theme. Values are loaded over a set of ultimate last
302 fallback defaults to guarantee that all theme elements are present in
303 a newly created theme.
306 cfgParser
=self
.userCfg
['highlight']
307 elif type == 'default':
308 cfgParser
=self
.defaultCfg
['highlight']
310 raise InvalidTheme
, 'Invalid theme type specified'
311 #foreground and background values are provded for each theme element
312 #(apart from cursor) even though all these values are not yet used
313 #by idle, to allow for their use in the future. Default values are
314 #generally black and white.
315 theme
={ 'normal-foreground':'#000000',
316 'normal-background':'#ffffff',
317 'keyword-foreground':'#000000',
318 'keyword-background':'#ffffff',
319 'builtin-foreground':'#000000',
320 'builtin-background':'#ffffff',
321 'comment-foreground':'#000000',
322 'comment-background':'#ffffff',
323 'string-foreground':'#000000',
324 'string-background':'#ffffff',
325 'definition-foreground':'#000000',
326 'definition-background':'#ffffff',
327 'hilite-foreground':'#000000',
328 'hilite-background':'gray',
329 'break-foreground':'#ffffff',
330 'break-background':'#000000',
331 'hit-foreground':'#ffffff',
332 'hit-background':'#000000',
333 'error-foreground':'#ffffff',
334 'error-background':'#000000',
335 #cursor (only foreground can be set)
336 'cursor-foreground':'#000000',
338 'stdout-foreground':'#000000',
339 'stdout-background':'#ffffff',
340 'stderr-foreground':'#000000',
341 'stderr-background':'#ffffff',
342 'console-foreground':'#000000',
343 'console-background':'#ffffff' }
344 for element
in theme
.keys():
345 if not cfgParser
.has_option(themeName
,element
):
346 #we are going to return a default, print warning
347 warning
=('\n Warning: configHandler.py - IdleConf.GetThemeDict'
348 ' -\n problem retrieving theme element %r'
349 '\n from theme %r.\n'
350 ' returning default value: %r\n' %
351 (element
, themeName
, theme
[element
]))
352 sys
.stderr
.write(warning
)
353 colour
=cfgParser
.Get(themeName
,element
,default
=theme
[element
])
354 theme
[element
]=colour
357 def CurrentTheme(self
):
359 Returns the name of the currently active theme
361 return self
.GetOption('main','Theme','name',default
='')
363 def CurrentKeys(self
):
365 Returns the name of the currently active key set
367 return self
.GetOption('main','Keys','name',default
='')
369 def GetExtensions(self
, active_only
=True, editor_only
=False, shell_only
=False):
371 Gets a list of all idle extensions declared in the config files.
372 active_only - boolean, if true only return active (enabled) extensions
374 extns
=self
.RemoveKeyBindNames(
375 self
.GetSectionList('default','extensions'))
376 userExtns
=self
.RemoveKeyBindNames(
377 self
.GetSectionList('user','extensions'))
378 for extn
in userExtns
:
379 if extn
not in extns
: #user has added own extension
384 if self
.GetOption('extensions', extn
, 'enable', default
=True,
386 #the extension is enabled
387 if editor_only
or shell_only
:
389 option
= "enable_editor"
391 option
= "enable_shell"
392 if self
.GetOption('extensions', extn
,option
,
393 default
=True, type='bool',
394 warn_on_default
=False):
395 activeExtns
.append(extn
)
397 activeExtns
.append(extn
)
402 def RemoveKeyBindNames(self
,extnNameList
):
403 #get rid of keybinding section names
407 if name
.endswith('_bindings') or name
.endswith('_cfgBindings'):
408 kbNameIndicies
.append(names
.index(name
))
409 kbNameIndicies
.sort()
410 kbNameIndicies
.reverse()
411 for index
in kbNameIndicies
: #delete each keybinding section name
415 def GetExtnNameForEvent(self
,virtualEvent
):
417 Returns the name of the extension that virtualEvent is bound in, or
418 None if not bound in any extension.
419 virtualEvent - string, name of the virtual event to test for, without
420 the enclosing '<< >>'
423 vEvent
='<<'+virtualEvent
+'>>'
424 for extn
in self
.GetExtensions(active_only
=0):
425 for event
in self
.GetExtensionKeys(extn
).keys():
430 def GetExtensionKeys(self
,extensionName
):
432 returns a dictionary of the configurable keybindings for a particular
433 extension,as they exist in the dictionary returned by GetCurrentKeySet;
434 that is, where previously used bindings are disabled.
436 keysName
=extensionName
+'_cfgBindings'
437 activeKeys
=self
.GetCurrentKeySet()
439 if self
.defaultCfg
['extensions'].has_section(keysName
):
440 eventNames
=self
.defaultCfg
['extensions'].GetOptionList(keysName
)
441 for eventName
in eventNames
:
442 event
='<<'+eventName
+'>>'
443 binding
=activeKeys
[event
]
444 extKeys
[event
]=binding
447 def __GetRawExtensionKeys(self
,extensionName
):
449 returns a dictionary of the configurable keybindings for a particular
450 extension, as defined in the configuration files, or an empty dictionary
451 if no bindings are found
453 keysName
=extensionName
+'_cfgBindings'
455 if self
.defaultCfg
['extensions'].has_section(keysName
):
456 eventNames
=self
.defaultCfg
['extensions'].GetOptionList(keysName
)
457 for eventName
in eventNames
:
458 binding
=self
.GetOption('extensions',keysName
,
459 eventName
,default
='').split()
460 event
='<<'+eventName
+'>>'
461 extKeys
[event
]=binding
464 def GetExtensionBindings(self
,extensionName
):
466 Returns a dictionary of all the event bindings for a particular
467 extension. The configurable keybindings are returned as they exist in
468 the dictionary returned by GetCurrentKeySet; that is, where re-used
469 keybindings are disabled.
471 bindsName
=extensionName
+'_bindings'
472 extBinds
=self
.GetExtensionKeys(extensionName
)
473 #add the non-configurable bindings
474 if self
.defaultCfg
['extensions'].has_section(bindsName
):
475 eventNames
=self
.defaultCfg
['extensions'].GetOptionList(bindsName
)
476 for eventName
in eventNames
:
477 binding
=self
.GetOption('extensions',bindsName
,
478 eventName
,default
='').split()
479 event
='<<'+eventName
+'>>'
480 extBinds
[event
]=binding
484 def GetKeyBinding(self
, keySetName
, eventStr
):
486 returns the keybinding for a specific event.
487 keySetName - string, name of key binding set
488 eventStr - string, the virtual event we want the binding for,
489 represented as a string, eg. '<<event>>'
491 eventName
=eventStr
[2:-2] #trim off the angle brackets
492 binding
=self
.GetOption('keys',keySetName
,eventName
,default
='').split()
495 def GetCurrentKeySet(self
):
496 return self
.GetKeySet(self
.CurrentKeys())
498 def GetKeySet(self
,keySetName
):
500 Returns a dictionary of: all requested core keybindings, plus the
501 keybindings for all currently active extensions. If a binding defined
502 in an extension is already in use, that binding is disabled.
504 keySet
=self
.GetCoreKeys(keySetName
)
505 activeExtns
=self
.GetExtensions(active_only
=1)
506 for extn
in activeExtns
:
507 extKeys
=self
.__GetRawExtensionKeys
(extn
)
508 if extKeys
: #the extension defines keybindings
509 for event
in extKeys
.keys():
510 if extKeys
[event
] in keySet
.values():
511 #the binding is already in use
512 extKeys
[event
]='' #disable this binding
513 keySet
[event
]=extKeys
[event
] #add binding
516 def IsCoreBinding(self
,virtualEvent
):
518 returns true if the virtual event is bound in the core idle keybindings.
519 virtualEvent - string, name of the virtual event to test for, without
520 the enclosing '<< >>'
522 return ('<<'+virtualEvent
+'>>') in self
.GetCoreKeys().keys()
524 def GetCoreKeys(self
, keySetName
=None):
526 returns the requested set of core keybindings, with fallbacks if
528 Keybindings loaded from the config file(s) are loaded _over_ these
529 defaults, so if there is a problem getting any core binding there will
530 be an 'ultimate last resort fallback' to the CUA-ish bindings
534 '<<copy>>': ['<Control-c>', '<Control-C>'],
535 '<<cut>>': ['<Control-x>', '<Control-X>'],
536 '<<paste>>': ['<Control-v>', '<Control-V>'],
537 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
538 '<<center-insert>>': ['<Control-l>'],
539 '<<close-all-windows>>': ['<Control-q>'],
540 '<<close-window>>': ['<Alt-F4>'],
541 '<<do-nothing>>': ['<Control-x>'],
542 '<<end-of-file>>': ['<Control-d>'],
543 '<<python-docs>>': ['<F1>'],
544 '<<python-context-help>>': ['<Shift-F1>'],
545 '<<history-next>>': ['<Alt-n>'],
546 '<<history-previous>>': ['<Alt-p>'],
547 '<<interrupt-execution>>': ['<Control-c>'],
548 '<<view-restart>>': ['<F6>'],
549 '<<restart-shell>>': ['<Control-F6>'],
550 '<<open-class-browser>>': ['<Alt-c>'],
551 '<<open-module>>': ['<Alt-m>'],
552 '<<open-new-window>>': ['<Control-n>'],
553 '<<open-window-from-file>>': ['<Control-o>'],
554 '<<plain-newline-and-indent>>': ['<Control-j>'],
555 '<<print-window>>': ['<Control-p>'],
556 '<<redo>>': ['<Control-y>'],
557 '<<remove-selection>>': ['<Escape>'],
558 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
559 '<<save-window-as-file>>': ['<Alt-s>'],
560 '<<save-window>>': ['<Control-s>'],
561 '<<select-all>>': ['<Alt-a>'],
562 '<<toggle-auto-coloring>>': ['<Control-slash>'],
563 '<<undo>>': ['<Control-z>'],
564 '<<find-again>>': ['<Control-g>', '<F3>'],
565 '<<find-in-files>>': ['<Alt-F3>'],
566 '<<find-selection>>': ['<Control-F3>'],
567 '<<find>>': ['<Control-f>'],
568 '<<replace>>': ['<Control-h>'],
569 '<<goto-line>>': ['<Alt-g>'],
570 '<<smart-backspace>>': ['<Key-BackSpace>'],
571 '<<newline-and-indent>>': ['<Key-Return> <Key-KP_Enter>'],
572 '<<smart-indent>>': ['<Key-Tab>'],
573 '<<indent-region>>': ['<Control-Key-bracketright>'],
574 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
575 '<<comment-region>>': ['<Alt-Key-3>'],
576 '<<uncomment-region>>': ['<Alt-Key-4>'],
577 '<<tabify-region>>': ['<Alt-Key-5>'],
578 '<<untabify-region>>': ['<Alt-Key-6>'],
579 '<<toggle-tabs>>': ['<Alt-Key-t>'],
580 '<<change-indentwidth>>': ['<Alt-Key-u>']
583 for event
in keyBindings
.keys():
584 binding
=self
.GetKeyBinding(keySetName
,event
)
586 keyBindings
[event
]=binding
587 else: #we are going to return a default, print warning
588 warning
=('\n Warning: configHandler.py - IdleConf.GetCoreKeys'
589 ' -\n problem retrieving key binding for event %r'
590 '\n from key set %r.\n'
591 ' returning default value: %r\n' %
592 (event
, keySetName
, keyBindings
[event
]))
593 sys
.stderr
.write(warning
)
596 def GetExtraHelpSourceList(self
,configSet
):
597 """Fetch list of extra help sources from a given configSet.
599 Valid configSets are 'user' or 'default'. Return a list of tuples of
600 the form (menu_item , path_to_help_file , option), or return the empty
601 list. 'option' is the sequence number of the help resource. 'option'
602 values determine the position of the menu items on the Help menu,
603 therefore the returned list must be sorted by 'option'.
607 if configSet
=='user':
608 cfgParser
=self
.userCfg
['main']
609 elif configSet
=='default':
610 cfgParser
=self
.defaultCfg
['main']
612 raise InvalidConfigSet
, 'Invalid configSet specified'
613 options
=cfgParser
.GetOptionList('HelpFiles')
614 for option
in options
:
615 value
=cfgParser
.Get('HelpFiles',option
,default
=';')
616 if value
.find(';')==-1: #malformed config entry with no ';'
617 menuItem
='' #make these empty
618 helpPath
='' #so value won't be added to list
619 else: #config entry contains ';' as expected
620 value
=string
.split(value
,';')
621 menuItem
=value
[0].strip()
622 helpPath
=value
[1].strip()
623 if menuItem
and helpPath
: #neither are empty strings
624 helpSources
.append( (menuItem
,helpPath
,option
) )
625 helpSources
.sort(self
.__helpsort
)
628 def __helpsort(self
, h1
, h2
):
629 if int(h1
[2]) < int(h2
[2]):
631 elif int(h1
[2]) > int(h2
[2]):
636 def GetAllExtraHelpSourcesList(self
):
638 Returns a list of tuples containing the details of all additional help
639 sources configured, or an empty list if there are none. Tuples are of
640 the format returned by GetExtraHelpSourceList.
642 allHelpSources
=( self
.GetExtraHelpSourceList('default')+
643 self
.GetExtraHelpSourceList('user') )
644 return allHelpSources
646 def LoadCfgFiles(self
):
648 load all configuration files.
650 for key
in self
.defaultCfg
.keys():
651 self
.defaultCfg
[key
].Load()
652 self
.userCfg
[key
].Load() #same keys
654 def SaveUserCfgFiles(self
):
656 write all loaded user configuration files back to disk
658 for key
in self
.userCfg
.keys():
659 self
.userCfg
[key
].Save()
664 if __name__
== '__main__':
667 for key
in cfg
.keys():
668 sections
=cfg
[key
].sections()
671 for section
in sections
:
672 options
=cfg
[key
].options(section
)
675 for option
in options
:
676 print option
, '=', cfg
[key
].Get(section
,option
)
677 dumpCfg(idleConf
.defaultCfg
)
678 dumpCfg(idleConf
.userCfg
)
679 print idleConf
.userCfg
['main'].Get('Theme','name')
680 #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')