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.
24 from ConfigParser
import ConfigParser
, NoOptionError
, NoSectionError
26 class InvalidConfigType(Exception): pass
27 class InvalidConfigSet(Exception): pass
28 class InvalidFgBg(Exception): pass
29 class InvalidTheme(Exception): pass
31 class IdleConfParser(ConfigParser
):
33 A ConfigParser specialised for idle configuration file handling
35 def __init__(self
, cfgFile
, cfgDefaults
=None):
37 cfgFile - string, fully specified configuration file name
40 ConfigParser
.__init
__(self
,defaults
=cfgDefaults
)
42 def Get(self
, section
, option
, type=None, default
=None, raw
=False):
44 Get an option value for given section/option or return default.
45 If type is specified, return as type.
47 if not self
.has_option(section
, option
):
50 return self
.getboolean(section
, option
)
52 return self
.getint(section
, option
)
54 return self
.get(section
, option
, raw
=raw
)
56 def GetOptionList(self
,section
):
58 Get an option list for given section
60 if self
.has_section(section
):
61 return self
.options(section
)
62 else: #return a default value
67 Load the configuration file from disk
71 class IdleUserConfParser(IdleConfParser
):
73 IdleConfigParser specialised for user configuration handling.
76 def AddSection(self
,section
):
78 if section doesn't exist, add it
80 if not self
.has_section(section
):
81 self
.add_section(section
)
83 def RemoveEmptySections(self
):
85 remove any sections that have no options
87 for section
in self
.sections():
88 if not self
.GetOptionList(section
):
89 self
.remove_section(section
)
93 Remove empty sections and then return 1 if parser has no sections
96 self
.RemoveEmptySections()
102 def RemoveOption(self
,section
,option
):
104 If section/option exists, remove it.
105 Returns 1 if option was removed, 0 otherwise.
107 if self
.has_section(section
):
108 return self
.remove_option(section
,option
)
110 def SetOption(self
,section
,option
,value
):
112 Sets option to value, adding section if required.
113 Returns 1 if option was added or changed, otherwise 0.
115 if self
.has_option(section
,option
):
116 if self
.get(section
,option
)==value
:
119 self
.set(section
,option
,value
)
122 if not self
.has_section(section
):
123 self
.add_section(section
)
124 self
.set(section
,option
,value
)
127 def RemoveFile(self
):
129 Removes the user config file from disk if it exists.
131 if os
.path
.exists(self
.file):
135 """Update user configuration file.
137 Remove empty sections. If resulting config isn't empty, write the file
138 to disk. If config is empty, remove the file from disk if it exists.
141 if not self
.IsEmpty():
144 cfgFile
= open(fname
, 'w')
147 cfgFile
= open(fname
, 'w')
154 holds config parsers for all idle config files:
156 (idle install dir)/config-main.def
157 (idle install dir)/config-extensions.def
158 (idle install dir)/config-highlight.def
159 (idle install dir)/config-keys.def
161 (user home dir)/.idlerc/config-main.cfg
162 (user home dir)/.idlerc/config-extensions.cfg
163 (user home dir)/.idlerc/config-highlight.cfg
164 (user home dir)/.idlerc/config-keys.cfg
170 self
.CreateConfigHandlers()
174 def CreateConfigHandlers(self
):
176 set up a dictionary of config parsers for default and user
177 configurations respectively
179 #build idle install path
180 if __name__
!= '__main__': # we were imported
181 idleDir
=os
.path
.dirname(__file__
)
182 else: # we were exec'ed (for testing only)
183 idleDir
=os
.path
.abspath(sys
.path
[0])
184 userDir
=self
.GetUserCfgDir()
185 configTypes
=('main','extensions','highlight','keys')
188 for cfgType
in configTypes
: #build config file names
189 defCfgFiles
[cfgType
]=os
.path
.join(idleDir
,'config-'+cfgType
+'.def')
190 usrCfgFiles
[cfgType
]=os
.path
.join(userDir
,'config-'+cfgType
+'.cfg')
191 for cfgType
in configTypes
: #create config parsers
192 self
.defaultCfg
[cfgType
]=IdleConfParser(defCfgFiles
[cfgType
])
193 self
.userCfg
[cfgType
]=IdleUserConfParser(usrCfgFiles
[cfgType
])
195 def GetUserCfgDir(self
):
197 Creates (if required) and returns a filesystem directory for storing
202 userDir
= os
.path
.expanduser('~')
203 if userDir
!= '~': # expanduser() found user home dir
204 if not os
.path
.exists(userDir
):
205 warn
= ('\n Warning: os.path.expanduser("~") points to\n '+
206 userDir
+',\n but the path does not exist.\n')
208 sys
.stderr
.write(warn
)
212 if userDir
== "~": # still no path to home!
213 # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
214 userDir
= os
.getcwd()
215 userDir
= os
.path
.join(userDir
, cfgDir
)
216 if not os
.path
.exists(userDir
):
219 except (OSError, IOError):
220 warn
= ('\n Warning: unable to create user config directory\n'+
221 userDir
+'\n Check path and permissions.\n Exiting!\n\n')
222 sys
.stderr
.write(warn
)
226 def GetOption(self
, configType
, section
, option
, default
=None, type=None,
227 warn_on_default
=True, raw
=False):
229 Get an option value for given config type and given general
230 configuration section/option or return a default. If type is specified,
231 return as type. Firstly the user configuration is checked, with a
232 fallback to the default configuration, and a final 'catch all'
233 fallback to a useable passed-in default if the option isn't present in
234 either the user or the default configuration.
235 configType must be one of ('main','extensions','highlight','keys')
236 If a default is returned, and warn_on_default is True, a warning is
240 if self
.userCfg
[configType
].has_option(section
,option
):
241 return self
.userCfg
[configType
].Get(section
, option
,
243 elif self
.defaultCfg
[configType
].has_option(section
,option
):
244 return self
.defaultCfg
[configType
].Get(section
, option
,
246 else: #returning default, print warning
248 warning
= ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
249 ' problem retrieving configuration option %r\n'
250 ' from section %r.\n'
251 ' returning default value: %r\n' %
252 (option
, section
, default
))
254 sys
.stderr
.write(warning
)
259 def SetOption(self
, configType
, section
, option
, value
):
260 """In user's config file, set section's option to value.
263 self
.userCfg
[configType
].SetOption(section
, option
, value
)
265 def GetSectionList(self
, configSet
, configType
):
267 Get a list of sections from either the user or default config for
268 the given config type.
269 configSet must be either 'user' or 'default'
270 configType must be one of ('main','extensions','highlight','keys')
272 if not (configType
in ('main','extensions','highlight','keys')):
273 raise InvalidConfigType
, 'Invalid configType specified'
274 if configSet
== 'user':
275 cfgParser
=self
.userCfg
[configType
]
276 elif configSet
== 'default':
277 cfgParser
=self
.defaultCfg
[configType
]
279 raise InvalidConfigSet
, 'Invalid configSet specified'
280 return cfgParser
.sections()
282 def GetHighlight(self
, theme
, element
, fgBg
=None):
284 return individual highlighting theme elements.
285 fgBg - string ('fg'or'bg') or None, if None return a dictionary
286 containing fg and bg colours (appropriate for passing to Tkinter in,
287 e.g., a tag_config call), otherwise fg or bg colour only as specified.
289 if self
.defaultCfg
['highlight'].has_section(theme
):
290 themeDict
=self
.GetThemeDict('default',theme
)
292 themeDict
=self
.GetThemeDict('user',theme
)
293 fore
=themeDict
[element
+'-foreground']
294 if element
=='cursor': #there is no config value for cursor bg
295 back
=themeDict
['normal-background']
297 back
=themeDict
[element
+'-background']
298 highlight
={"foreground": fore
,"background": back
}
299 if not fgBg
: #return dict of both colours
301 else: #return specified colour only
303 return highlight
["foreground"]
305 return highlight
["background"]
307 raise InvalidFgBg
, 'Invalid fgBg specified'
309 def GetThemeDict(self
,type,themeName
):
311 type - string, 'default' or 'user' theme type
312 themeName - string, theme name
313 Returns a dictionary which holds {option:value} for each element
314 in the specified theme. Values are loaded over a set of ultimate last
315 fallback defaults to guarantee that all theme elements are present in
316 a newly created theme.
319 cfgParser
=self
.userCfg
['highlight']
320 elif type == 'default':
321 cfgParser
=self
.defaultCfg
['highlight']
323 raise InvalidTheme
, 'Invalid theme type specified'
324 #foreground and background values are provded for each theme element
325 #(apart from cursor) even though all these values are not yet used
326 #by idle, to allow for their use in the future. Default values are
327 #generally black and white.
328 theme
={ 'normal-foreground':'#000000',
329 'normal-background':'#ffffff',
330 'keyword-foreground':'#000000',
331 'keyword-background':'#ffffff',
332 'builtin-foreground':'#000000',
333 'builtin-background':'#ffffff',
334 'comment-foreground':'#000000',
335 'comment-background':'#ffffff',
336 'string-foreground':'#000000',
337 'string-background':'#ffffff',
338 'definition-foreground':'#000000',
339 'definition-background':'#ffffff',
340 'hilite-foreground':'#000000',
341 'hilite-background':'gray',
342 'break-foreground':'#ffffff',
343 'break-background':'#000000',
344 'hit-foreground':'#ffffff',
345 'hit-background':'#000000',
346 'error-foreground':'#ffffff',
347 'error-background':'#000000',
348 #cursor (only foreground can be set)
349 'cursor-foreground':'#000000',
351 'stdout-foreground':'#000000',
352 'stdout-background':'#ffffff',
353 'stderr-foreground':'#000000',
354 'stderr-background':'#ffffff',
355 'console-foreground':'#000000',
356 'console-background':'#ffffff' }
357 for element
in theme
.keys():
358 if not cfgParser
.has_option(themeName
,element
):
359 #we are going to return a default, print warning
360 warning
=('\n Warning: configHandler.py - IdleConf.GetThemeDict'
361 ' -\n problem retrieving theme element %r'
362 '\n from theme %r.\n'
363 ' returning default value: %r\n' %
364 (element
, themeName
, theme
[element
]))
366 sys
.stderr
.write(warning
)
369 colour
=cfgParser
.Get(themeName
,element
,default
=theme
[element
])
370 theme
[element
]=colour
373 def CurrentTheme(self
):
375 Returns the name of the currently active theme
377 return self
.GetOption('main','Theme','name',default
='')
379 def CurrentKeys(self
):
381 Returns the name of the currently active key set
383 return self
.GetOption('main','Keys','name',default
='')
385 def GetExtensions(self
, active_only
=True, editor_only
=False, shell_only
=False):
387 Gets a list of all idle extensions declared in the config files.
388 active_only - boolean, if true only return active (enabled) extensions
390 extns
=self
.RemoveKeyBindNames(
391 self
.GetSectionList('default','extensions'))
392 userExtns
=self
.RemoveKeyBindNames(
393 self
.GetSectionList('user','extensions'))
394 for extn
in userExtns
:
395 if extn
not in extns
: #user has added own extension
400 if self
.GetOption('extensions', extn
, 'enable', default
=True,
402 #the extension is enabled
403 if editor_only
or shell_only
:
405 option
= "enable_editor"
407 option
= "enable_shell"
408 if self
.GetOption('extensions', extn
,option
,
409 default
=True, type='bool',
410 warn_on_default
=False):
411 activeExtns
.append(extn
)
413 activeExtns
.append(extn
)
418 def RemoveKeyBindNames(self
,extnNameList
):
419 #get rid of keybinding section names
423 if name
.endswith(('_bindings', '_cfgBindings')):
424 kbNameIndicies
.append(names
.index(name
))
425 kbNameIndicies
.sort()
426 kbNameIndicies
.reverse()
427 for index
in kbNameIndicies
: #delete each keybinding section name
431 def GetExtnNameForEvent(self
,virtualEvent
):
433 Returns the name of the extension that virtualEvent is bound in, or
434 None if not bound in any extension.
435 virtualEvent - string, name of the virtual event to test for, without
436 the enclosing '<< >>'
439 vEvent
='<<'+virtualEvent
+'>>'
440 for extn
in self
.GetExtensions(active_only
=0):
441 for event
in self
.GetExtensionKeys(extn
).keys():
446 def GetExtensionKeys(self
,extensionName
):
448 returns a dictionary of the configurable keybindings for a particular
449 extension,as they exist in the dictionary returned by GetCurrentKeySet;
450 that is, where previously used bindings are disabled.
452 keysName
=extensionName
+'_cfgBindings'
453 activeKeys
=self
.GetCurrentKeySet()
455 if self
.defaultCfg
['extensions'].has_section(keysName
):
456 eventNames
=self
.defaultCfg
['extensions'].GetOptionList(keysName
)
457 for eventName
in eventNames
:
458 event
='<<'+eventName
+'>>'
459 binding
=activeKeys
[event
]
460 extKeys
[event
]=binding
463 def __GetRawExtensionKeys(self
,extensionName
):
465 returns a dictionary of the configurable keybindings for a particular
466 extension, as defined in the configuration files, or an empty dictionary
467 if no bindings are found
469 keysName
=extensionName
+'_cfgBindings'
471 if self
.defaultCfg
['extensions'].has_section(keysName
):
472 eventNames
=self
.defaultCfg
['extensions'].GetOptionList(keysName
)
473 for eventName
in eventNames
:
474 binding
=self
.GetOption('extensions',keysName
,
475 eventName
,default
='').split()
476 event
='<<'+eventName
+'>>'
477 extKeys
[event
]=binding
480 def GetExtensionBindings(self
,extensionName
):
482 Returns a dictionary of all the event bindings for a particular
483 extension. The configurable keybindings are returned as they exist in
484 the dictionary returned by GetCurrentKeySet; that is, where re-used
485 keybindings are disabled.
487 bindsName
=extensionName
+'_bindings'
488 extBinds
=self
.GetExtensionKeys(extensionName
)
489 #add the non-configurable bindings
490 if self
.defaultCfg
['extensions'].has_section(bindsName
):
491 eventNames
=self
.defaultCfg
['extensions'].GetOptionList(bindsName
)
492 for eventName
in eventNames
:
493 binding
=self
.GetOption('extensions',bindsName
,
494 eventName
,default
='').split()
495 event
='<<'+eventName
+'>>'
496 extBinds
[event
]=binding
500 def GetKeyBinding(self
, keySetName
, eventStr
):
502 returns the keybinding for a specific event.
503 keySetName - string, name of key binding set
504 eventStr - string, the virtual event we want the binding for,
505 represented as a string, eg. '<<event>>'
507 eventName
=eventStr
[2:-2] #trim off the angle brackets
508 binding
=self
.GetOption('keys',keySetName
,eventName
,default
='').split()
511 def GetCurrentKeySet(self
):
512 result
= self
.GetKeySet(self
.CurrentKeys())
514 if macosxSupport
.runningAsOSXApp():
515 # We're using AquaTk, replace all keybingings that use the
516 # Alt key by ones that use the Option key because the former
517 # don't work reliably.
518 for k
, v
in result
.items():
519 v2
= [ x
.replace('<Alt-', '<Option-') for x
in v
]
525 def GetKeySet(self
,keySetName
):
527 Returns a dictionary of: all requested core keybindings, plus the
528 keybindings for all currently active extensions. If a binding defined
529 in an extension is already in use, that binding is disabled.
531 keySet
=self
.GetCoreKeys(keySetName
)
532 activeExtns
=self
.GetExtensions(active_only
=1)
533 for extn
in activeExtns
:
534 extKeys
=self
.__GetRawExtensionKeys
(extn
)
535 if extKeys
: #the extension defines keybindings
536 for event
in extKeys
.keys():
537 if extKeys
[event
] in keySet
.values():
538 #the binding is already in use
539 extKeys
[event
]='' #disable this binding
540 keySet
[event
]=extKeys
[event
] #add binding
543 def IsCoreBinding(self
,virtualEvent
):
545 returns true if the virtual event is bound in the core idle keybindings.
546 virtualEvent - string, name of the virtual event to test for, without
547 the enclosing '<< >>'
549 return ('<<'+virtualEvent
+'>>') in self
.GetCoreKeys().keys()
551 def GetCoreKeys(self
, keySetName
=None):
553 returns the requested set of core keybindings, with fallbacks if
555 Keybindings loaded from the config file(s) are loaded _over_ these
556 defaults, so if there is a problem getting any core binding there will
557 be an 'ultimate last resort fallback' to the CUA-ish bindings
561 '<<copy>>': ['<Control-c>', '<Control-C>'],
562 '<<cut>>': ['<Control-x>', '<Control-X>'],
563 '<<paste>>': ['<Control-v>', '<Control-V>'],
564 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
565 '<<center-insert>>': ['<Control-l>'],
566 '<<close-all-windows>>': ['<Control-q>'],
567 '<<close-window>>': ['<Alt-F4>'],
568 '<<do-nothing>>': ['<Control-x>'],
569 '<<end-of-file>>': ['<Control-d>'],
570 '<<python-docs>>': ['<F1>'],
571 '<<python-context-help>>': ['<Shift-F1>'],
572 '<<history-next>>': ['<Alt-n>'],
573 '<<history-previous>>': ['<Alt-p>'],
574 '<<interrupt-execution>>': ['<Control-c>'],
575 '<<view-restart>>': ['<F6>'],
576 '<<restart-shell>>': ['<Control-F6>'],
577 '<<open-class-browser>>': ['<Alt-c>'],
578 '<<open-module>>': ['<Alt-m>'],
579 '<<open-new-window>>': ['<Control-n>'],
580 '<<open-window-from-file>>': ['<Control-o>'],
581 '<<plain-newline-and-indent>>': ['<Control-j>'],
582 '<<print-window>>': ['<Control-p>'],
583 '<<redo>>': ['<Control-y>'],
584 '<<remove-selection>>': ['<Escape>'],
585 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
586 '<<save-window-as-file>>': ['<Alt-s>'],
587 '<<save-window>>': ['<Control-s>'],
588 '<<select-all>>': ['<Alt-a>'],
589 '<<toggle-auto-coloring>>': ['<Control-slash>'],
590 '<<undo>>': ['<Control-z>'],
591 '<<find-again>>': ['<Control-g>', '<F3>'],
592 '<<find-in-files>>': ['<Alt-F3>'],
593 '<<find-selection>>': ['<Control-F3>'],
594 '<<find>>': ['<Control-f>'],
595 '<<replace>>': ['<Control-h>'],
596 '<<goto-line>>': ['<Alt-g>'],
597 '<<smart-backspace>>': ['<Key-BackSpace>'],
598 '<<newline-and-indent>>': ['<Key-Return> <Key-KP_Enter>'],
599 '<<smart-indent>>': ['<Key-Tab>'],
600 '<<indent-region>>': ['<Control-Key-bracketright>'],
601 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
602 '<<comment-region>>': ['<Alt-Key-3>'],
603 '<<uncomment-region>>': ['<Alt-Key-4>'],
604 '<<tabify-region>>': ['<Alt-Key-5>'],
605 '<<untabify-region>>': ['<Alt-Key-6>'],
606 '<<toggle-tabs>>': ['<Alt-Key-t>'],
607 '<<change-indentwidth>>': ['<Alt-Key-u>'],
608 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
609 '<<del-word-right>>': ['<Control-Key-Delete>']
612 for event
in keyBindings
.keys():
613 binding
=self
.GetKeyBinding(keySetName
,event
)
615 keyBindings
[event
]=binding
616 else: #we are going to return a default, print warning
617 warning
=('\n Warning: configHandler.py - IdleConf.GetCoreKeys'
618 ' -\n problem retrieving key binding for event %r'
619 '\n from key set %r.\n'
620 ' returning default value: %r\n' %
621 (event
, keySetName
, keyBindings
[event
]))
623 sys
.stderr
.write(warning
)
628 def GetExtraHelpSourceList(self
,configSet
):
629 """Fetch list of extra help sources from a given configSet.
631 Valid configSets are 'user' or 'default'. Return a list of tuples of
632 the form (menu_item , path_to_help_file , option), or return the empty
633 list. 'option' is the sequence number of the help resource. 'option'
634 values determine the position of the menu items on the Help menu,
635 therefore the returned list must be sorted by 'option'.
639 if configSet
=='user':
640 cfgParser
=self
.userCfg
['main']
641 elif configSet
=='default':
642 cfgParser
=self
.defaultCfg
['main']
644 raise InvalidConfigSet
, 'Invalid configSet specified'
645 options
=cfgParser
.GetOptionList('HelpFiles')
646 for option
in options
:
647 value
=cfgParser
.Get('HelpFiles',option
,default
=';')
648 if value
.find(';')==-1: #malformed config entry with no ';'
649 menuItem
='' #make these empty
650 helpPath
='' #so value won't be added to list
651 else: #config entry contains ';' as expected
652 value
=string
.split(value
,';')
653 menuItem
=value
[0].strip()
654 helpPath
=value
[1].strip()
655 if menuItem
and helpPath
: #neither are empty strings
656 helpSources
.append( (menuItem
,helpPath
,option
) )
657 helpSources
.sort(self
.__helpsort
)
660 def __helpsort(self
, h1
, h2
):
661 if int(h1
[2]) < int(h2
[2]):
663 elif int(h1
[2]) > int(h2
[2]):
668 def GetAllExtraHelpSourcesList(self
):
670 Returns a list of tuples containing the details of all additional help
671 sources configured, or an empty list if there are none. Tuples are of
672 the format returned by GetExtraHelpSourceList.
674 allHelpSources
=( self
.GetExtraHelpSourceList('default')+
675 self
.GetExtraHelpSourceList('user') )
676 return allHelpSources
678 def LoadCfgFiles(self
):
680 load all configuration files.
682 for key
in self
.defaultCfg
.keys():
683 self
.defaultCfg
[key
].Load()
684 self
.userCfg
[key
].Load() #same keys
686 def SaveUserCfgFiles(self
):
688 write all loaded user configuration files back to disk
690 for key
in self
.userCfg
.keys():
691 self
.userCfg
[key
].Save()
696 if __name__
== '__main__':
699 for key
in cfg
.keys():
700 sections
=cfg
[key
].sections()
703 for section
in sections
:
704 options
=cfg
[key
].options(section
)
707 for option
in options
:
708 print option
, '=', cfg
[key
].Get(section
,option
)
709 dumpCfg(idleConf
.defaultCfg
)
710 dumpCfg(idleConf
.userCfg
)
711 print idleConf
.userCfg
['main'].Get('Theme','name')
712 #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')