1"""Provides access to stored IDLE configuration information.
2
3Refer to the comments at the beginning of config-main.def for a description of
4the available configuration files and the design implemented to update user
5configuration information.  In particular, user configuration choices which
6duplicate the defaults will be removed from the user's configuration files,
7and if a file becomes empty, it will be deleted.
8
9The contents of the user files may be altered using the Options/Configure IDLE
10menu to access the configuration GUI (configDialog.py), or manually.
11
12Throughout this module there is an emphasis on returning useable defaults
13when a problem occurs in returning a requested configuration value back to
14idle. This is to allow IDLE to continue to function in spite of errors in
15the retrieval of config information. When a default is returned instead of
16a requested config value, a message is printed to stderr to aid in
17configuration problem notification and resolution.
18
19"""
20import os
21import sys
22import string
23from idlelib import macosxSupport
24from ConfigParser import ConfigParser, NoOptionError, NoSectionError
25
26class InvalidConfigType(Exception): pass
27class InvalidConfigSet(Exception): pass
28class InvalidFgBg(Exception): pass
29class InvalidTheme(Exception): pass
30
31class IdleConfParser(ConfigParser):
32    """
33    A ConfigParser specialised for idle configuration file handling
34    """
35    def __init__(self, cfgFile, cfgDefaults=None):
36        """
37        cfgFile - string, fully specified configuration file name
38        """
39        self.file=cfgFile
40        ConfigParser.__init__(self,defaults=cfgDefaults)
41
42    def Get(self, section, option, type=None, default=None, raw=False):
43        """
44        Get an option value for given section/option or return default.
45        If type is specified, return as type.
46        """
47        if not self.has_option(section, option):
48            return default
49        if type=='bool':
50            return self.getboolean(section, option)
51        elif type=='int':
52            return self.getint(section, option)
53        else:
54            return self.get(section, option, raw=raw)
55
56    def GetOptionList(self,section):
57        """
58        Get an option list for given section
59        """
60        if self.has_section(section):
61            return self.options(section)
62        else:  #return a default value
63            return []
64
65    def Load(self):
66        """
67        Load the configuration file from disk
68        """
69        self.read(self.file)
70
71class IdleUserConfParser(IdleConfParser):
72    """
73    IdleConfigParser specialised for user configuration handling.
74    """
75
76    def AddSection(self,section):
77        """
78        if section doesn't exist, add it
79        """
80        if not self.has_section(section):
81            self.add_section(section)
82
83    def RemoveEmptySections(self):
84        """
85        remove any sections that have no options
86        """
87        for section in self.sections():
88            if not self.GetOptionList(section):
89                self.remove_section(section)
90
91    def IsEmpty(self):
92        """
93        Remove empty sections and then return 1 if parser has no sections
94        left, else return 0.
95        """
96        self.RemoveEmptySections()
97        if self.sections():
98            return 0
99        else:
100            return 1
101
102    def RemoveOption(self,section,option):
103        """
104        If section/option exists, remove it.
105        Returns 1 if option was removed, 0 otherwise.
106        """
107        if self.has_section(section):
108            return self.remove_option(section,option)
109
110    def SetOption(self,section,option,value):
111        """
112        Sets option to value, adding section if required.
113        Returns 1 if option was added or changed, otherwise 0.
114        """
115        if self.has_option(section,option):
116            if self.get(section,option)==value:
117                return 0
118            else:
119                self.set(section,option,value)
120                return 1
121        else:
122            if not self.has_section(section):
123                self.add_section(section)
124            self.set(section,option,value)
125            return 1
126
127    def RemoveFile(self):
128        """
129        Removes the user config file from disk if it exists.
130        """
131        if os.path.exists(self.file):
132            os.remove(self.file)
133
134    def Save(self):
135        """Update user configuration file.
136
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.
139
140        """
141        if not self.IsEmpty():
142            fname = self.file
143            try:
144                cfgFile = open(fname, 'w')
145            except IOError:
146                os.unlink(fname)
147                cfgFile = open(fname, 'w')
148            self.write(cfgFile)
149        else:
150            self.RemoveFile()
151
152class IdleConf:
153    """
154    holds config parsers for all idle config files:
155    default 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
160    user config  files
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
165    """
166    def __init__(self):
167        self.defaultCfg={}
168        self.userCfg={}
169        self.cfg={}
170        self.CreateConfigHandlers()
171        self.LoadCfgFiles()
172        #self.LoadCfg()
173
174    def CreateConfigHandlers(self):
175        """
176        set up a dictionary of config parsers for default and user
177        configurations respectively
178        """
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')
186        defCfgFiles={}
187        usrCfgFiles={}
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])
194
195    def GetUserCfgDir(self):
196        """
197        Creates (if required) and returns a filesystem directory for storing
198        user config files.
199
200        """
201        cfgDir = '.idlerc'
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')
207                try:
208                    sys.stderr.write(warn)
209                except IOError:
210                    pass
211                userDir = '~'
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):
217            try:
218                os.mkdir(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)
223                raise SystemExit
224        return userDir
225
226    def GetOption(self, configType, section, option, default=None, type=None,
227                  warn_on_default=True, raw=False):
228        """
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
237        printed to stderr.
238
239        """
240        try:
241            if self.userCfg[configType].has_option(section,option):
242                return self.userCfg[configType].Get(section, option,
243                                                    type=type, raw=raw)
244        except ValueError:
245            warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
246                       ' invalid %r value for configuration option %r\n'
247                       ' from section %r: %r\n' %
248                       (type, option, section,
249                        self.userCfg[configType].Get(section, option,
250                                                     raw=raw)))
251            try:
252                sys.stderr.write(warning)
253            except IOError:
254                pass
255        try:
256            if self.defaultCfg[configType].has_option(section,option):
257                return self.defaultCfg[configType].Get(section, option,
258                                                       type=type, raw=raw)
259        except ValueError:
260            pass
261        #returning default, print warning
262        if warn_on_default:
263            warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
264                       ' problem retrieving configuration option %r\n'
265                       ' from section %r.\n'
266                       ' returning default value: %r\n' %
267                       (option, section, default))
268            try:
269                sys.stderr.write(warning)
270            except IOError:
271                pass
272        return default
273
274    def SetOption(self, configType, section, option, value):
275        """In user's config file, set section's option to value.
276
277        """
278        self.userCfg[configType].SetOption(section, option, value)
279
280    def GetSectionList(self, configSet, configType):
281        """
282        Get a list of sections from either the user or default config for
283        the given config type.
284        configSet must be either 'user' or 'default'
285        configType must be one of ('main','extensions','highlight','keys')
286        """
287        if not (configType in ('main','extensions','highlight','keys')):
288            raise InvalidConfigType, 'Invalid configType specified'
289        if configSet == 'user':
290            cfgParser=self.userCfg[configType]
291        elif configSet == 'default':
292            cfgParser=self.defaultCfg[configType]
293        else:
294            raise InvalidConfigSet, 'Invalid configSet specified'
295        return cfgParser.sections()
296
297    def GetHighlight(self, theme, element, fgBg=None):
298        """
299        return individual highlighting theme elements.
300        fgBg - string ('fg'or'bg') or None, if None return a dictionary
301        containing fg and bg colours (appropriate for passing to Tkinter in,
302        e.g., a tag_config call), otherwise fg or bg colour only as specified.
303        """
304        if self.defaultCfg['highlight'].has_section(theme):
305            themeDict=self.GetThemeDict('default',theme)
306        else:
307            themeDict=self.GetThemeDict('user',theme)
308        fore=themeDict[element+'-foreground']
309        if element=='cursor': #there is no config value for cursor bg
310            back=themeDict['normal-background']
311        else:
312            back=themeDict[element+'-background']
313        highlight={"foreground": fore,"background": back}
314        if not fgBg: #return dict of both colours
315            return highlight
316        else: #return specified colour only
317            if fgBg == 'fg':
318                return highlight["foreground"]
319            if fgBg == 'bg':
320                return highlight["background"]
321            else:
322                raise InvalidFgBg, 'Invalid fgBg specified'
323
324    def GetThemeDict(self,type,themeName):
325        """
326        type - string, 'default' or 'user' theme type
327        themeName - string, theme name
328        Returns a dictionary which holds {option:value} for each element
329        in the specified theme. Values are loaded over a set of ultimate last
330        fallback defaults to guarantee that all theme elements are present in
331        a newly created theme.
332        """
333        if type == 'user':
334            cfgParser=self.userCfg['highlight']
335        elif type == 'default':
336            cfgParser=self.defaultCfg['highlight']
337        else:
338            raise InvalidTheme, 'Invalid theme type specified'
339        #foreground and background values are provded for each theme element
340        #(apart from cursor) even though all these values are not yet used
341        #by idle, to allow for their use in the future. Default values are
342        #generally black and white.
343        theme={ 'normal-foreground':'#000000',
344                'normal-background':'#ffffff',
345                'keyword-foreground':'#000000',
346                'keyword-background':'#ffffff',
347                'builtin-foreground':'#000000',
348                'builtin-background':'#ffffff',
349                'comment-foreground':'#000000',
350                'comment-background':'#ffffff',
351                'string-foreground':'#000000',
352                'string-background':'#ffffff',
353                'definition-foreground':'#000000',
354                'definition-background':'#ffffff',
355                'hilite-foreground':'#000000',
356                'hilite-background':'gray',
357                'break-foreground':'#ffffff',
358                'break-background':'#000000',
359                'hit-foreground':'#ffffff',
360                'hit-background':'#000000',
361                'error-foreground':'#ffffff',
362                'error-background':'#000000',
363                #cursor (only foreground can be set)
364                'cursor-foreground':'#000000',
365                #shell window
366                'stdout-foreground':'#000000',
367                'stdout-background':'#ffffff',
368                'stderr-foreground':'#000000',
369                'stderr-background':'#ffffff',
370                'console-foreground':'#000000',
371                'console-background':'#ffffff' }
372        for element in theme.keys():
373            if not cfgParser.has_option(themeName,element):
374                #we are going to return a default, print warning
375                warning=('\n Warning: configHandler.py - IdleConf.GetThemeDict'
376                           ' -\n problem retrieving theme element %r'
377                           '\n from theme %r.\n'
378                           ' returning default value: %r\n' %
379                           (element, themeName, theme[element]))
380                try:
381                    sys.stderr.write(warning)
382                except IOError:
383                    pass
384            colour=cfgParser.Get(themeName,element,default=theme[element])
385            theme[element]=colour
386        return theme
387
388    def CurrentTheme(self):
389        """
390        Returns the name of the currently active theme
391        """
392        return self.GetOption('main','Theme','name',default='')
393
394    def CurrentKeys(self):
395        """
396        Returns the name of the currently active key set
397        """
398        return self.GetOption('main','Keys','name',default='')
399
400    def GetExtensions(self, active_only=True, editor_only=False, shell_only=False):
401        """
402        Gets a list of all idle extensions declared in the config files.
403        active_only - boolean, if true only return active (enabled) extensions
404        """
405        extns=self.RemoveKeyBindNames(
406                self.GetSectionList('default','extensions'))
407        userExtns=self.RemoveKeyBindNames(
408                self.GetSectionList('user','extensions'))
409        for extn in userExtns:
410            if extn not in extns: #user has added own extension
411                extns.append(extn)
412        if active_only:
413            activeExtns=[]
414            for extn in extns:
415                if self.GetOption('extensions', extn, 'enable', default=True,
416                                  type='bool'):
417                    #the extension is enabled
418                    if editor_only or shell_only:
419                        if editor_only:
420                            option = "enable_editor"
421                        else:
422                            option = "enable_shell"
423                        if self.GetOption('extensions', extn,option,
424                                          default=True, type='bool',
425                                          warn_on_default=False):
426                            activeExtns.append(extn)
427                    else:
428                        activeExtns.append(extn)
429            return activeExtns
430        else:
431            return extns
432
433    def RemoveKeyBindNames(self,extnNameList):
434        #get rid of keybinding section names
435        names=extnNameList
436        kbNameIndicies=[]
437        for name in names:
438            if name.endswith(('_bindings', '_cfgBindings')):
439                kbNameIndicies.append(names.index(name))
440        kbNameIndicies.sort()
441        kbNameIndicies.reverse()
442        for index in kbNameIndicies: #delete each keybinding section name
443            del(names[index])
444        return names
445
446    def GetExtnNameForEvent(self,virtualEvent):
447        """
448        Returns the name of the extension that virtualEvent is bound in, or
449        None if not bound in any extension.
450        virtualEvent - string, name of the virtual event to test for, without
451                       the enclosing '<< >>'
452        """
453        extName=None
454        vEvent='<<'+virtualEvent+'>>'
455        for extn in self.GetExtensions(active_only=0):
456            for event in self.GetExtensionKeys(extn).keys():
457                if event == vEvent:
458                    extName=extn
459        return extName
460
461    def GetExtensionKeys(self,extensionName):
462        """
463        returns a dictionary of the configurable keybindings for a particular
464        extension,as they exist in the dictionary returned by GetCurrentKeySet;
465        that is, where previously used bindings are disabled.
466        """
467        keysName=extensionName+'_cfgBindings'
468        activeKeys=self.GetCurrentKeySet()
469        extKeys={}
470        if self.defaultCfg['extensions'].has_section(keysName):
471            eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
472            for eventName in eventNames:
473                event='<<'+eventName+'>>'
474                binding=activeKeys[event]
475                extKeys[event]=binding
476        return extKeys
477
478    def __GetRawExtensionKeys(self,extensionName):
479        """
480        returns a dictionary of the configurable keybindings for a particular
481        extension, as defined in the configuration files, or an empty dictionary
482        if no bindings are found
483        """
484        keysName=extensionName+'_cfgBindings'
485        extKeys={}
486        if self.defaultCfg['extensions'].has_section(keysName):
487            eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
488            for eventName in eventNames:
489                binding=self.GetOption('extensions',keysName,
490                        eventName,default='').split()
491                event='<<'+eventName+'>>'
492                extKeys[event]=binding
493        return extKeys
494
495    def GetExtensionBindings(self,extensionName):
496        """
497        Returns a dictionary of all the event bindings for a particular
498        extension. The configurable keybindings are returned as they exist in
499        the dictionary returned by GetCurrentKeySet; that is, where re-used
500        keybindings are disabled.
501        """
502        bindsName=extensionName+'_bindings'
503        extBinds=self.GetExtensionKeys(extensionName)
504        #add the non-configurable bindings
505        if self.defaultCfg['extensions'].has_section(bindsName):
506            eventNames=self.defaultCfg['extensions'].GetOptionList(bindsName)
507            for eventName in eventNames:
508                binding=self.GetOption('extensions',bindsName,
509                        eventName,default='').split()
510                event='<<'+eventName+'>>'
511                extBinds[event]=binding
512
513        return extBinds
514
515    def GetKeyBinding(self, keySetName, eventStr):
516        """
517        returns the keybinding for a specific event.
518        keySetName - string, name of key binding set
519        eventStr - string, the virtual event we want the binding for,
520                   represented as a string, eg. '<<event>>'
521        """
522        eventName=eventStr[2:-2] #trim off the angle brackets
523        binding=self.GetOption('keys',keySetName,eventName,default='').split()
524        return binding
525
526    def GetCurrentKeySet(self):
527        result = self.GetKeySet(self.CurrentKeys())
528
529        if macosxSupport.runningAsOSXApp():
530            # We're using AquaTk, replace all keybingings that use the
531            # Alt key by ones that use the Option key because the former
532            # don't work reliably.
533            for k, v in result.items():
534                v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
535                if v != v2:
536                    result[k] = v2
537
538        return result
539
540    def GetKeySet(self,keySetName):
541        """
542        Returns a dictionary of: all requested core keybindings, plus the
543        keybindings for all currently active extensions. If a binding defined
544        in an extension is already in use, that binding is disabled.
545        """
546        keySet=self.GetCoreKeys(keySetName)
547        activeExtns=self.GetExtensions(active_only=1)
548        for extn in activeExtns:
549            extKeys=self.__GetRawExtensionKeys(extn)
550            if extKeys: #the extension defines keybindings
551                for event in extKeys.keys():
552                    if extKeys[event] in keySet.values():
553                        #the binding is already in use
554                        extKeys[event]='' #disable this binding
555                    keySet[event]=extKeys[event] #add binding
556        return keySet
557
558    def IsCoreBinding(self,virtualEvent):
559        """
560        returns true if the virtual event is bound in the core idle keybindings.
561        virtualEvent - string, name of the virtual event to test for, without
562                       the enclosing '<< >>'
563        """
564        return ('<<'+virtualEvent+'>>') in self.GetCoreKeys().keys()
565
566    def GetCoreKeys(self, keySetName=None):
567        """
568        returns the requested set of core keybindings, with fallbacks if
569        required.
570        Keybindings loaded from the config file(s) are loaded _over_ these
571        defaults, so if there is a problem getting any core binding there will
572        be an 'ultimate last resort fallback' to the CUA-ish bindings
573        defined here.
574        """
575        keyBindings={
576            '<<copy>>': ['<Control-c>', '<Control-C>'],
577            '<<cut>>': ['<Control-x>', '<Control-X>'],
578            '<<paste>>': ['<Control-v>', '<Control-V>'],
579            '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
580            '<<center-insert>>': ['<Control-l>'],
581            '<<close-all-windows>>': ['<Control-q>'],
582            '<<close-window>>': ['<Alt-F4>'],
583            '<<do-nothing>>': ['<Control-x>'],
584            '<<end-of-file>>': ['<Control-d>'],
585            '<<python-docs>>': ['<F1>'],
586            '<<python-context-help>>': ['<Shift-F1>'],
587            '<<history-next>>': ['<Alt-n>'],
588            '<<history-previous>>': ['<Alt-p>'],
589            '<<interrupt-execution>>': ['<Control-c>'],
590            '<<view-restart>>': ['<F6>'],
591            '<<restart-shell>>': ['<Control-F6>'],
592            '<<open-class-browser>>': ['<Alt-c>'],
593            '<<open-module>>': ['<Alt-m>'],
594            '<<open-new-window>>': ['<Control-n>'],
595            '<<open-window-from-file>>': ['<Control-o>'],
596            '<<plain-newline-and-indent>>': ['<Control-j>'],
597            '<<print-window>>': ['<Control-p>'],
598            '<<redo>>': ['<Control-y>'],
599            '<<remove-selection>>': ['<Escape>'],
600            '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
601            '<<save-window-as-file>>': ['<Alt-s>'],
602            '<<save-window>>': ['<Control-s>'],
603            '<<select-all>>': ['<Alt-a>'],
604            '<<toggle-auto-coloring>>': ['<Control-slash>'],
605            '<<undo>>': ['<Control-z>'],
606            '<<find-again>>': ['<Control-g>', '<F3>'],
607            '<<find-in-files>>': ['<Alt-F3>'],
608            '<<find-selection>>': ['<Control-F3>'],
609            '<<find>>': ['<Control-f>'],
610            '<<replace>>': ['<Control-h>'],
611            '<<goto-line>>': ['<Alt-g>'],
612            '<<smart-backspace>>': ['<Key-BackSpace>'],
613            '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
614            '<<smart-indent>>': ['<Key-Tab>'],
615            '<<indent-region>>': ['<Control-Key-bracketright>'],
616            '<<dedent-region>>': ['<Control-Key-bracketleft>'],
617            '<<comment-region>>': ['<Alt-Key-3>'],
618            '<<uncomment-region>>': ['<Alt-Key-4>'],
619            '<<tabify-region>>': ['<Alt-Key-5>'],
620            '<<untabify-region>>': ['<Alt-Key-6>'],
621            '<<toggle-tabs>>': ['<Alt-Key-t>'],
622            '<<change-indentwidth>>': ['<Alt-Key-u>'],
623            '<<del-word-left>>': ['<Control-Key-BackSpace>'],
624            '<<del-word-right>>': ['<Control-Key-Delete>']
625            }
626        if keySetName:
627            for event in keyBindings.keys():
628                binding=self.GetKeyBinding(keySetName,event)
629                if binding:
630                    keyBindings[event]=binding
631                else: #we are going to return a default, print warning
632                    warning=('\n Warning: configHandler.py - IdleConf.GetCoreKeys'
633                               ' -\n problem retrieving key binding for event %r'
634                               '\n from key set %r.\n'
635                               ' returning default value: %r\n' %
636                               (event, keySetName, keyBindings[event]))
637                    try:
638                        sys.stderr.write(warning)
639                    except IOError:
640                        pass
641        return keyBindings
642
643    def GetExtraHelpSourceList(self,configSet):
644        """Fetch list of extra help sources from a given configSet.
645
646        Valid configSets are 'user' or 'default'.  Return a list of tuples of
647        the form (menu_item , path_to_help_file , option), or return the empty
648        list.  'option' is the sequence number of the help resource.  'option'
649        values determine the position of the menu items on the Help menu,
650        therefore the returned list must be sorted by 'option'.
651
652        """
653        helpSources=[]
654        if configSet=='user':
655            cfgParser=self.userCfg['main']
656        elif configSet=='default':
657            cfgParser=self.defaultCfg['main']
658        else:
659            raise InvalidConfigSet, 'Invalid configSet specified'
660        options=cfgParser.GetOptionList('HelpFiles')
661        for option in options:
662            value=cfgParser.Get('HelpFiles',option,default=';')
663            if value.find(';')==-1: #malformed config entry with no ';'
664                menuItem='' #make these empty
665                helpPath='' #so value won't be added to list
666            else: #config entry contains ';' as expected
667                value=string.split(value,';')
668                menuItem=value[0].strip()
669                helpPath=value[1].strip()
670            if menuItem and helpPath: #neither are empty strings
671                helpSources.append( (menuItem,helpPath,option) )
672        helpSources.sort(key=lambda x: int(x[2]))
673        return helpSources
674
675    def GetAllExtraHelpSourcesList(self):
676        """
677        Returns a list of tuples containing the details of all additional help
678        sources configured, or an empty list if there are none. Tuples are of
679        the format returned by GetExtraHelpSourceList.
680        """
681        allHelpSources=( self.GetExtraHelpSourceList('default')+
682                self.GetExtraHelpSourceList('user') )
683        return allHelpSources
684
685    def LoadCfgFiles(self):
686        """
687        load all configuration files.
688        """
689        for key in self.defaultCfg.keys():
690            self.defaultCfg[key].Load()
691            self.userCfg[key].Load() #same keys
692
693    def SaveUserCfgFiles(self):
694        """
695        write all loaded user configuration files back to disk
696        """
697        for key in self.userCfg.keys():
698            self.userCfg[key].Save()
699
700idleConf=IdleConf()
701
702### module test
703if __name__ == '__main__':
704    def dumpCfg(cfg):
705        print '\n',cfg,'\n'
706        for key in cfg.keys():
707            sections=cfg[key].sections()
708            print key
709            print sections
710            for section in sections:
711                options=cfg[key].options(section)
712                print section
713                print options
714                for option in options:
715                    print option, '=', cfg[key].Get(section,option)
716    dumpCfg(idleConf.defaultCfg)
717    dumpCfg(idleConf.userCfg)
718    print idleConf.userCfg['main'].Get('Theme','name')
719    #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')
720