PageRenderTime 91ms CodeModel.GetById 20ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 1ms

/Lib/idlelib/configHandler.py

http://unladen-swallow.googlecode.com/
Python | 712 lines | 630 code | 3 blank | 79 comment | 17 complexity | ff9dfa21b458e03165a41785799a2d5e MD5 | raw file
  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
 23import 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        if self.userCfg[configType].has_option(section,option):
241            return self.userCfg[configType].Get(section, option,
242                                                type=type, raw=raw)
243        elif self.defaultCfg[configType].has_option(section,option):
244            return self.defaultCfg[configType].Get(section, option,
245                                                   type=type, raw=raw)
246        else: #returning default, print warning
247            if warn_on_default:
248                warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
249                           ' problem retrieving configration option %r\n'
250                           ' from section %r.\n'
251                           ' returning default value: %r\n' %
252                           (option, section, default))
253                try:
254                    sys.stderr.write(warning)
255                except IOError:
256                    pass
257            return default
258
259    def SetOption(self, configType, section, option, value):
260        """In user's config file, set section's option to value.
261
262        """
263        self.userCfg[configType].SetOption(section, option, value)
264
265    def GetSectionList(self, configSet, configType):
266        """
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')
271        """
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]
278        else:
279            raise InvalidConfigSet, 'Invalid configSet specified'
280        return cfgParser.sections()
281
282    def GetHighlight(self, theme, element, fgBg=None):
283        """
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.
288        """
289        if self.defaultCfg['highlight'].has_section(theme):
290            themeDict=self.GetThemeDict('default',theme)
291        else:
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']
296        else:
297            back=themeDict[element+'-background']
298        highlight={"foreground": fore,"background": back}
299        if not fgBg: #return dict of both colours
300            return highlight
301        else: #return specified colour only
302            if fgBg == 'fg':
303                return highlight["foreground"]
304            if fgBg == 'bg':
305                return highlight["background"]
306            else:
307                raise InvalidFgBg, 'Invalid fgBg specified'
308
309    def GetThemeDict(self,type,themeName):
310        """
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.
317        """
318        if type == 'user':
319            cfgParser=self.userCfg['highlight']
320        elif type == 'default':
321            cfgParser=self.defaultCfg['highlight']
322        else:
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',
350                #shell window
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]))
365                try:
366                    sys.stderr.write(warning)
367                except IOError:
368                    pass
369            colour=cfgParser.Get(themeName,element,default=theme[element])
370            theme[element]=colour
371        return theme
372
373    def CurrentTheme(self):
374        """
375        Returns the name of the currently active theme
376        """
377        return self.GetOption('main','Theme','name',default='')
378
379    def CurrentKeys(self):
380        """
381        Returns the name of the currently active key set
382        """
383        return self.GetOption('main','Keys','name',default='')
384
385    def GetExtensions(self, active_only=True, editor_only=False, shell_only=False):
386        """
387        Gets a list of all idle extensions declared in the config files.
388        active_only - boolean, if true only return active (enabled) extensions
389        """
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
396                extns.append(extn)
397        if active_only:
398            activeExtns=[]
399            for extn in extns:
400                if self.GetOption('extensions', extn, 'enable', default=True,
401                                  type='bool'):
402                    #the extension is enabled
403                    if editor_only or shell_only:
404                        if editor_only:
405                            option = "enable_editor"
406                        else:
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)
412                    else:
413                        activeExtns.append(extn)
414            return activeExtns
415        else:
416            return extns
417
418    def RemoveKeyBindNames(self,extnNameList):
419        #get rid of keybinding section names
420        names=extnNameList
421        kbNameIndicies=[]
422        for name in 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
428            del(names[index])
429        return names
430
431    def GetExtnNameForEvent(self,virtualEvent):
432        """
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 '<< >>'
437        """
438        extName=None
439        vEvent='<<'+virtualEvent+'>>'
440        for extn in self.GetExtensions(active_only=0):
441            for event in self.GetExtensionKeys(extn).keys():
442                if event == vEvent:
443                    extName=extn
444        return extName
445
446    def GetExtensionKeys(self,extensionName):
447        """
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.
451        """
452        keysName=extensionName+'_cfgBindings'
453        activeKeys=self.GetCurrentKeySet()
454        extKeys={}
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
461        return extKeys
462
463    def __GetRawExtensionKeys(self,extensionName):
464        """
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
468        """
469        keysName=extensionName+'_cfgBindings'
470        extKeys={}
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
478        return extKeys
479
480    def GetExtensionBindings(self,extensionName):
481        """
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.
486        """
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
497
498        return extBinds
499
500    def GetKeyBinding(self, keySetName, eventStr):
501        """
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>>'
506        """
507        eventName=eventStr[2:-2] #trim off the angle brackets
508        binding=self.GetOption('keys',keySetName,eventName,default='').split()
509        return binding
510
511    def GetCurrentKeySet(self):
512        result = self.GetKeySet(self.CurrentKeys())
513
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 ]
520                if v != v2:
521                    result[k] = v2
522
523        return result
524
525    def GetKeySet(self,keySetName):
526        """
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.
530        """
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
541        return keySet
542
543    def IsCoreBinding(self,virtualEvent):
544        """
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 '<< >>'
548        """
549        return ('<<'+virtualEvent+'>>') in self.GetCoreKeys().keys()
550
551    def GetCoreKeys(self, keySetName=None):
552        """
553        returns the requested set of core keybindings, with fallbacks if
554        required.
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
558        defined here.
559        """
560        keyBindings={
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>']
610            }
611        if keySetName:
612            for event in keyBindings.keys():
613                binding=self.GetKeyBinding(keySetName,event)
614                if binding:
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]))
622                    try:
623                        sys.stderr.write(warning)
624                    except IOError:
625                        pass
626        return keyBindings
627
628    def GetExtraHelpSourceList(self,configSet):
629        """Fetch list of extra help sources from a given configSet.
630
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'.
636
637        """
638        helpSources=[]
639        if configSet=='user':
640            cfgParser=self.userCfg['main']
641        elif configSet=='default':
642            cfgParser=self.defaultCfg['main']
643        else:
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)
658        return helpSources
659
660    def __helpsort(self, h1, h2):
661        if int(h1[2]) < int(h2[2]):
662            return -1
663        elif int(h1[2]) > int(h2[2]):
664            return 1
665        else:
666            return 0
667
668    def GetAllExtraHelpSourcesList(self):
669        """
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.
673        """
674        allHelpSources=( self.GetExtraHelpSourceList('default')+
675                self.GetExtraHelpSourceList('user') )
676        return allHelpSources
677
678    def LoadCfgFiles(self):
679        """
680        load all configuration files.
681        """
682        for key in self.defaultCfg.keys():
683            self.defaultCfg[key].Load()
684            self.userCfg[key].Load() #same keys
685
686    def SaveUserCfgFiles(self):
687        """
688        write all loaded user configuration files back to disk
689        """
690        for key in self.userCfg.keys():
691            self.userCfg[key].Save()
692
693idleConf=IdleConf()
694
695### module test
696if __name__ == '__main__':
697    def dumpCfg(cfg):
698        print '\n',cfg,'\n'
699        for key in cfg.keys():
700            sections=cfg[key].sections()
701            print key
702            print sections
703            for section in sections:
704                options=cfg[key].options(section)
705                print section
706                print options
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')