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):
220 Get an option value for given config type and given general
221 configuration section/option or return a default. If type is specified,
222 return as type. Firstly the user configuration is checked, with a
223 fallback to the default configuration, and a final 'catch all'
224 fallback to a useable passed-in default if the option isn't present in
225 either the user or the default configuration.
226 configType must be one of ('main','extensions','highlight','keys')
227 If a default is returned a warning is printed to stderr.
229 if self
.userCfg
[configType
].has_option(section
,option
):
230 return self
.userCfg
[configType
].Get(section
, option
, type=type)
231 elif self
.defaultCfg
[configType
].has_option(section
,option
):
232 return self
.defaultCfg
[configType
].Get(section
, option
, type=type)
233 else: #returning default, print warning
234 warning
=('\n Warning: configHandler.py - IdleConf.GetOption -\n'+
235 ' problem retrieving configration option '+`option`
+'\n'+
236 ' from section '+`section`
+'.\n'+
237 ' returning default value: '+`default`
+'\n')
238 sys
.stderr
.write(warning
)
241 def GetSectionList(self
, configSet
, configType
):
243 Get a list of sections from either the user or default config for
244 the given config type.
245 configSet must be either 'user' or 'default'
246 configType must be one of ('main','extensions','highlight','keys')
248 if not (configType
in ('main','extensions','highlight','keys')):
249 raise InvalidConfigType
, 'Invalid configType specified'
250 if configSet
== 'user':
251 cfgParser
=self
.userCfg
[configType
]
252 elif configSet
== 'default':
253 cfgParser
=self
.defaultCfg
[configType
]
255 raise InvalidConfigSet
, 'Invalid configSet specified'
256 return cfgParser
.sections()
258 def GetHighlight(self
, theme
, element
, fgBg
=None):
260 return individual highlighting theme elements.
261 fgBg - string ('fg'or'bg') or None, if None return a dictionary
262 containing fg and bg colours (appropriate for passing to Tkinter in,
263 e.g., a tag_config call), otherwise fg or bg colour only as specified.
265 if self
.defaultCfg
['highlight'].has_section(theme
):
266 themeDict
=self
.GetThemeDict('default',theme
)
268 themeDict
=self
.GetThemeDict('user',theme
)
269 fore
=themeDict
[element
+'-foreground']
270 if element
=='cursor': #there is no config value for cursor bg
271 back
=themeDict
['normal-background']
273 back
=themeDict
[element
+'-background']
274 highlight
={"foreground": fore
,"background": back
}
275 if not fgBg
: #return dict of both colours
277 else: #return specified colour only
279 return highlight
["foreground"]
281 return highlight
["background"]
283 raise InvalidFgBg
, 'Invalid fgBg specified'
285 def GetThemeDict(self
,type,themeName
):
287 type - string, 'default' or 'user' theme type
288 themeName - string, theme name
289 Returns a dictionary which holds {option:value} for each element
290 in the specified theme. Values are loaded over a set of ultimate last
291 fallback defaults to guarantee that all theme elements are present in
292 a newly created theme.
295 cfgParser
=self
.userCfg
['highlight']
296 elif type == 'default':
297 cfgParser
=self
.defaultCfg
['highlight']
299 raise InvalidTheme
, 'Invalid theme type specified'
300 #foreground and background values are provded for each theme element
301 #(apart from cursor) even though all these values are not yet used
302 #by idle, to allow for their use in the future. Default values are
303 #generally black and white.
304 theme
={ 'normal-foreground':'#000000',
305 'normal-background':'#ffffff',
306 'keyword-foreground':'#000000',
307 'keyword-background':'#ffffff',
308 'comment-foreground':'#000000',
309 'comment-background':'#ffffff',
310 'string-foreground':'#000000',
311 'string-background':'#ffffff',
312 'definition-foreground':'#000000',
313 'definition-background':'#ffffff',
314 'hilite-foreground':'#000000',
315 'hilite-background':'gray',
316 'break-foreground':'#ffffff',
317 'break-background':'#000000',
318 'hit-foreground':'#ffffff',
319 'hit-background':'#000000',
320 'error-foreground':'#ffffff',
321 'error-background':'#000000',
322 #cursor (only foreground can be set)
323 'cursor-foreground':'#000000',
325 'stdout-foreground':'#000000',
326 'stdout-background':'#ffffff',
327 'stderr-foreground':'#000000',
328 'stderr-background':'#ffffff',
329 'console-foreground':'#000000',
330 'console-background':'#ffffff' }
331 for element
in theme
.keys():
332 if not cfgParser
.has_option(themeName
,element
):
333 #we are going to return a default, print warning
334 warning
=('\n Warning: configHandler.py - IdleConf.GetThemeDict'+
335 ' -\n problem retrieving theme element '+`element`
+
336 '\n from theme '+`themeName`
+'.\n'+
337 ' returning default value: '+`theme
[element
]`
+'\n')
338 sys
.stderr
.write(warning
)
339 colour
=cfgParser
.Get(themeName
,element
,default
=theme
[element
])
340 theme
[element
]=colour
343 def CurrentTheme(self
):
345 Returns the name of the currently active theme
347 return self
.GetOption('main','Theme','name',default
='')
349 def CurrentKeys(self
):
351 Returns the name of the currently active key set
353 return self
.GetOption('main','Keys','name',default
='')
355 def GetExtensions(self
, activeOnly
=1):
357 Gets a list of all idle extensions declared in the config files.
358 activeOnly - boolean, if true only return active (enabled) extensions
360 extns
=self
.RemoveKeyBindNames(
361 self
.GetSectionList('default','extensions'))
362 userExtns
=self
.RemoveKeyBindNames(
363 self
.GetSectionList('user','extensions'))
364 for extn
in userExtns
:
365 if extn
not in extns
: #user has added own extension
370 if self
.GetOption('extensions',extn
,'enable',default
=1,
372 #the extension is enabled
373 activeExtns
.append(extn
)
378 def RemoveKeyBindNames(self
,extnNameList
):
379 #get rid of keybinding section names
383 if name
.endswith('_bindings') or name
.endswith('_cfgBindings'):
384 kbNameIndicies
.append(names
.index(name
))
385 kbNameIndicies
.sort()
386 kbNameIndicies
.reverse()
387 for index
in kbNameIndicies
: #delete each keybinding section name
391 def GetExtnNameForEvent(self
,virtualEvent
):
393 Returns the name of the extension that virtualEvent is bound in, or
394 None if not bound in any extension.
395 virtualEvent - string, name of the virtual event to test for, without
396 the enclosing '<< >>'
399 vEvent
='<<'+virtualEvent
+'>>'
400 for extn
in self
.GetExtensions(activeOnly
=0):
401 for event
in self
.GetExtensionKeys(extn
).keys():
406 def GetExtensionKeys(self
,extensionName
):
408 returns a dictionary of the configurable keybindings for a particular
409 extension,as they exist in the dictionary returned by GetCurrentKeySet;
410 that is, where previously used bindings are disabled.
412 keysName
=extensionName
+'_cfgBindings'
413 activeKeys
=self
.GetCurrentKeySet()
415 if self
.defaultCfg
['extensions'].has_section(keysName
):
416 eventNames
=self
.defaultCfg
['extensions'].GetOptionList(keysName
)
417 for eventName
in eventNames
:
418 event
='<<'+eventName
+'>>'
419 binding
=activeKeys
[event
]
420 extKeys
[event
]=binding
423 def __GetRawExtensionKeys(self
,extensionName
):
425 returns a dictionary of the configurable keybindings for a particular
426 extension, as defined in the configuration files, or an empty dictionary
427 if no bindings are found
429 keysName
=extensionName
+'_cfgBindings'
431 if self
.defaultCfg
['extensions'].has_section(keysName
):
432 eventNames
=self
.defaultCfg
['extensions'].GetOptionList(keysName
)
433 for eventName
in eventNames
:
434 binding
=self
.GetOption('extensions',keysName
,
435 eventName
,default
='').split()
436 event
='<<'+eventName
+'>>'
437 extKeys
[event
]=binding
440 def GetExtensionBindings(self
,extensionName
):
442 Returns a dictionary of all the event bindings for a particular
443 extension. The configurable keybindings are returned as they exist in
444 the dictionary returned by GetCurrentKeySet; that is, where re-used
445 keybindings are disabled.
447 bindsName
=extensionName
+'_bindings'
448 extBinds
=self
.GetExtensionKeys(extensionName
)
449 #add the non-configurable bindings
450 if self
.defaultCfg
['extensions'].has_section(bindsName
):
451 eventNames
=self
.defaultCfg
['extensions'].GetOptionList(bindsName
)
452 for eventName
in eventNames
:
453 binding
=self
.GetOption('extensions',bindsName
,
454 eventName
,default
='').split()
455 event
='<<'+eventName
+'>>'
456 extBinds
[event
]=binding
460 def GetKeyBinding(self
, keySetName
, eventStr
):
462 returns the keybinding for a specific event.
463 keySetName - string, name of key binding set
464 eventStr - string, the virtual event we want the binding for,
465 represented as a string, eg. '<<event>>'
467 eventName
=eventStr
[2:-2] #trim off the angle brackets
468 binding
=self
.GetOption('keys',keySetName
,eventName
,default
='').split()
471 def GetCurrentKeySet(self
):
472 return self
.GetKeySet(self
.CurrentKeys())
474 def GetKeySet(self
,keySetName
):
476 Returns a dictionary of: all requested core keybindings, plus the
477 keybindings for all currently active extensions. If a binding defined
478 in an extension is already in use, that binding is disabled.
480 keySet
=self
.GetCoreKeys(keySetName
)
481 activeExtns
=self
.GetExtensions(activeOnly
=1)
482 for extn
in activeExtns
:
483 extKeys
=self
.__GetRawExtensionKeys
(extn
)
484 if extKeys
: #the extension defines keybindings
485 for event
in extKeys
.keys():
486 if extKeys
[event
] in keySet
.values():
487 #the binding is already in use
488 extKeys
[event
]='' #disable this binding
489 keySet
[event
]=extKeys
[event
] #add binding
492 def IsCoreBinding(self
,virtualEvent
):
494 returns true if the virtual event is bound in the core idle keybindings.
495 virtualEvent - string, name of the virtual event to test for, without
496 the enclosing '<< >>'
498 return ('<<'+virtualEvent
+'>>') in self
.GetCoreKeys().keys()
500 def GetCoreKeys(self
, keySetName
=None):
502 returns the requested set of core keybindings, with fallbacks if
504 Keybindings loaded from the config file(s) are loaded _over_ these
505 defaults, so if there is a problem getting any core binding there will
506 be an 'ultimate last resort fallback' to the CUA-ish bindings
510 '<<copy>>': ['<Control-c>', '<Control-C>'],
511 '<<cut>>': ['<Control-x>', '<Control-X>'],
512 '<<paste>>': ['<Control-v>', '<Control-V>'],
513 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
514 '<<center-insert>>': ['<Control-l>'],
515 '<<close-all-windows>>': ['<Control-q>'],
516 '<<close-window>>': ['<Alt-F4>'],
517 '<<do-nothing>>': ['<Control-x>'],
518 '<<end-of-file>>': ['<Control-d>'],
519 '<<python-docs>>': ['<F1>'],
520 '<<python-context-help>>': ['<Shift-F1>'],
521 '<<history-next>>': ['<Alt-n>'],
522 '<<history-previous>>': ['<Alt-p>'],
523 '<<interrupt-execution>>': ['<Control-c>'],
524 '<<view-restart>>': ['<F6>'],
525 '<<restart-shell>>': ['<Control-F6>'],
526 '<<open-class-browser>>': ['<Alt-c>'],
527 '<<open-module>>': ['<Alt-m>'],
528 '<<open-new-window>>': ['<Control-n>'],
529 '<<open-window-from-file>>': ['<Control-o>'],
530 '<<plain-newline-and-indent>>': ['<Control-j>'],
531 '<<print-window>>': ['<Control-p>'],
532 '<<redo>>': ['<Control-y>'],
533 '<<remove-selection>>': ['<Escape>'],
534 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
535 '<<save-window-as-file>>': ['<Alt-s>'],
536 '<<save-window>>': ['<Control-s>'],
537 '<<select-all>>': ['<Alt-a>'],
538 '<<toggle-auto-coloring>>': ['<Control-slash>'],
539 '<<undo>>': ['<Control-z>'],
540 '<<find-again>>': ['<Control-g>', '<F3>'],
541 '<<find-in-files>>': ['<Alt-F3>'],
542 '<<find-selection>>': ['<Control-F3>'],
543 '<<find>>': ['<Control-f>'],
544 '<<replace>>': ['<Control-h>'],
545 '<<goto-line>>': ['<Alt-g>'],
546 '<<smart-backspace>>': ['<Key-BackSpace>'],
547 '<<newline-and-indent>>': ['<Key-Return> <Key-KP_Enter>'],
548 '<<smart-indent>>': ['<Key-Tab>'],
549 '<<indent-region>>': ['<Control-Key-bracketright>'],
550 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
551 '<<comment-region>>': ['<Alt-Key-3>'],
552 '<<uncomment-region>>': ['<Alt-Key-4>'],
553 '<<tabify-region>>': ['<Alt-Key-5>'],
554 '<<untabify-region>>': ['<Alt-Key-6>'],
555 '<<toggle-tabs>>': ['<Alt-Key-t>'],
556 '<<change-indentwidth>>': ['<Alt-Key-u>']
559 for event
in keyBindings
.keys():
560 binding
=self
.GetKeyBinding(keySetName
,event
)
562 keyBindings
[event
]=binding
563 else: #we are going to return a default, print warning
564 warning
=('\n Warning: configHandler.py - IdleConf.GetCoreKeys'+
565 ' -\n problem retrieving key binding for event '+
566 `event`
+'\n from key set '+`keySetName`
+'.\n'+
567 ' returning default value: '+`keyBindings
[event
]`
+'\n')
568 sys
.stderr
.write(warning
)
571 def GetExtraHelpSourceList(self
,configSet
):
572 """Fetch list of extra help sources from a given configSet.
574 Valid configSets are 'user' or 'default'. Return a list of tuples of
575 the form (menu_item , path_to_help_file , option), or return the empty
576 list. 'option' is the sequence number of the help resource. 'option'
577 values determine the position of the menu items on the Help menu,
578 therefore the returned list must be sorted by 'option'.
582 if configSet
=='user':
583 cfgParser
=self
.userCfg
['main']
584 elif configSet
=='default':
585 cfgParser
=self
.defaultCfg
['main']
587 raise InvalidConfigSet
, 'Invalid configSet specified'
588 options
=cfgParser
.GetOptionList('HelpFiles')
589 for option
in options
:
590 value
=cfgParser
.Get('HelpFiles',option
,default
=';')
591 if value
.find(';')==-1: #malformed config entry with no ';'
592 menuItem
='' #make these empty
593 helpPath
='' #so value won't be added to list
594 else: #config entry contains ';' as expected
595 value
=string
.split(value
,';')
596 menuItem
=value
[0].strip()
597 helpPath
=value
[1].strip()
598 if menuItem
and helpPath
: #neither are empty strings
599 helpSources
.append( (menuItem
,helpPath
,option
) )
600 helpSources
.sort(self
.__helpsort
)
603 def __helpsort(self
, h1
, h2
):
604 if int(h1
[2]) < int(h2
[2]):
606 elif int(h1
[2]) > int(h2
[2]):
611 def GetAllExtraHelpSourcesList(self
):
613 Returns a list of tuples containing the details of all additional help
614 sources configured, or an empty list if there are none. Tuples are of
615 the format returned by GetExtraHelpSourceList.
617 allHelpSources
=( self
.GetExtraHelpSourceList('default')+
618 self
.GetExtraHelpSourceList('user') )
619 return allHelpSources
621 def LoadCfgFiles(self
):
623 load all configuration files.
625 for key
in self
.defaultCfg
.keys():
626 self
.defaultCfg
[key
].Load()
627 self
.userCfg
[key
].Load() #same keys
629 def SaveUserCfgFiles(self
):
631 write all loaded user configuration files back to disk
633 for key
in self
.userCfg
.keys():
634 self
.userCfg
[key
].Save()
639 if __name__
== '__main__':
642 for key
in cfg
.keys():
643 sections
=cfg
[key
].sections()
646 for section
in sections
:
647 options
=cfg
[key
].options(section
)
650 for option
in options
:
651 print option
, '=', cfg
[key
].Get(section
,option
)
652 dumpCfg(idleConf
.defaultCfg
)
653 dumpCfg(idleConf
.userCfg
)
654 print idleConf
.userCfg
['main'].Get('Theme','name')
655 #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')