configHandler.py 26.1 KB
Newer Older
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1 2 3 4 5 6
"""Provides access to stored IDLE configuration information.

Refer to the comments at the beginning of config-main.def for a description of
the available configuration files and the design implemented to update user
configuration information.  In particular, user configuration choices which
duplicate the defaults will be removed from the user's configuration files,
7
and if a file becomes empty, it will be deleted.
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
8 9 10 11 12 13 14 15 16 17 18

The contents of the user files may be altered using the Options/Configure IDLE
menu to access the configuration GUI (configDialog.py), or manually.

Throughout this module there is an emphasis on returning useable defaults
when a problem occurs in returning a requested configuration value back to
idle. This is to allow IDLE to continue to function in spite of errors in
the retrieval of config information. When a default is returned instead of
a requested config value, a message is printed to stderr to aid in
configuration problem notification and resolution.

19
"""
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
20 21 22
import os
import sys
import string
23 24
from ConfigParser import ConfigParser, NoOptionError, NoSectionError

25 26 27 28 29
class InvalidConfigType(Exception): pass
class InvalidConfigSet(Exception): pass
class InvalidFgBg(Exception): pass
class InvalidTheme(Exception): pass

30 31 32 33 34 35 36 37 38 39
class IdleConfParser(ConfigParser):
    """
    A ConfigParser specialised for idle configuration file handling
    """
    def __init__(self, cfgFile, cfgDefaults=None):
        """
        cfgFile - string, fully specified configuration file name
        """
        self.file=cfgFile
        ConfigParser.__init__(self,defaults=cfgDefaults)
40

41
    def Get(self, section, option, type=None, default=None):
42 43 44 45
        """
        Get an option value for given section/option or return default.
        If type is specified, return as type.
        """
46
        if type=='bool':
47
            getVal=self.getboolean
48
        elif type=='int':
49
            getVal=self.getint
50
        else:
51
            getVal=self.get
52
        if self.has_option(section,option):
53
            #return getVal(section, option, raw, vars, default)
54
            return getVal(section, option)
55 56
        else:
            return default
57 58 59 60 61

    def GetOptionList(self,section):
        """
        Get an option list for given section
        """
62
        if self.has_section(section):
63 64 65 66 67
            return self.options(section)
        else:  #return a default value
            return []

    def Load(self):
68 69
        """
        Load the configuration file from disk
70 71
        """
        self.read(self.file)
72

73 74
class IdleUserConfParser(IdleConfParser):
    """
75
    IdleConfigParser specialised for user configuration handling.
76
    """
77 78 79 80 81 82 83

    def AddSection(self,section):
        """
        if section doesn't exist, add it
        """
        if not self.has_section(section):
            self.add_section(section)
84

85 86 87 88 89 90
    def RemoveEmptySections(self):
        """
        remove any sections that have no options
        """
        for section in self.sections():
            if not self.GetOptionList(section):
91 92
                self.remove_section(section)

93 94 95 96 97 98 99 100 101 102
    def IsEmpty(self):
        """
        Remove empty sections and then return 1 if parser has no sections
        left, else return 0.
        """
        self.RemoveEmptySections()
        if self.sections():
            return 0
        else:
            return 1
103

104 105 106 107 108 109 110
    def RemoveOption(self,section,option):
        """
        If section/option exists, remove it.
        Returns 1 if option was removed, 0 otherwise.
        """
        if self.has_section(section):
            return self.remove_option(section,option)
111

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
    def SetOption(self,section,option,value):
        """
        Sets option to value, adding section if required.
        Returns 1 if option was added or changed, otherwise 0.
        """
        if self.has_option(section,option):
            if self.get(section,option)==value:
                return 0
            else:
                self.set(section,option,value)
                return 1
        else:
            if not self.has_section(section):
                self.add_section(section)
            self.set(section,option,value)
            return 1
128

129 130 131 132 133
    def RemoveFile(self):
        """
        Removes the user config file from disk if it exists.
        """
        if os.path.exists(self.file):
134 135
            os.remove(self.file)

136
    def Save(self):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
137 138 139 140 141
        """Update user configuration file.

        Remove empty sections. If resulting config isn't empty, write the file
        to disk. If config is empty, remove the file from disk if it exists.

142
        """
143 144 145 146
        if not self.IsEmpty():
            cfgFile=open(self.file,'w')
            self.write(cfgFile)
        else:
147
            self.RemoveFile()
148 149 150 151 152 153 154 155 156 157

class IdleConf:
    """
    holds config parsers for all idle config files:
    default config files
        (idle install dir)/config-main.def
        (idle install dir)/config-extensions.def
        (idle install dir)/config-highlight.def
        (idle install dir)/config-keys.def
    user config  files
158 159 160 161
        (user home dir)/.idlerc/config-main.cfg
        (user home dir)/.idlerc/config-extensions.cfg
        (user home dir)/.idlerc/config-highlight.cfg
        (user home dir)/.idlerc/config-keys.cfg
162 163 164 165 166 167 168 169
    """
    def __init__(self):
        self.defaultCfg={}
        self.userCfg={}
        self.cfg={}
        self.CreateConfigHandlers()
        self.LoadCfgFiles()
        #self.LoadCfg()
170

171 172
    def CreateConfigHandlers(self):
        """
173
        set up a dictionary of config parsers for default and user
174 175 176 177
        configurations respectively
        """
        #build idle install path
        if __name__ != '__main__': # we were imported
178
            idleDir=os.path.dirname(__file__)
179
        else: # we were exec'ed (for testing only)
180 181
            idleDir=os.path.abspath(sys.path[0])
        userDir=self.GetUserCfgDir()
182 183 184 185
        configTypes=('main','extensions','highlight','keys')
        defCfgFiles={}
        usrCfgFiles={}
        for cfgType in configTypes: #build config file names
186 187
            defCfgFiles[cfgType]=os.path.join(idleDir,'config-'+cfgType+'.def')
            usrCfgFiles[cfgType]=os.path.join(userDir,'config-'+cfgType+'.cfg')
188 189 190
        for cfgType in configTypes: #create config parsers
            self.defaultCfg[cfgType]=IdleConfParser(defCfgFiles[cfgType])
            self.userCfg[cfgType]=IdleUserConfParser(usrCfgFiles[cfgType])
191

192 193
    def GetUserCfgDir(self):
        """
194
        Creates (if required) and returns a filesystem directory for storing
195 196 197 198 199 200 201 202 203 204 205 206 207
        user config files.
        """
        cfgDir='.idlerc'
        userDir=os.path.expanduser('~')
        if userDir != '~': #'HOME' exists as a key in os.environ
            if not os.path.exists(userDir):
                warn=('\n Warning: HOME environment variable points to\n '+
                        userDir+'\n but the path does not exist.\n')
                sys.stderr.write(warn)
                userDir='~'
        if userDir=='~': #we still don't have a home directory
            #traditionally idle has defaulted to os.getcwd(), is this adeqate?
            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
211 212 213 214 215 216
                os.mkdir(userDir)
            except IOError:
                warn=('\n Warning: unable to create user config directory\n '+
                        userDir+'\n')
                sys.stderr.write(warn)
        return userDir
217

218
    def GetOption(self, configType, section, option, default=None, type=None):
219
        """
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 223 224
        return as type. Firstly the user configuration is checked, with a
        fallback to the default configuration, and a final 'catch all'
        fallback to a useable passed-in default if the option isn't present in
225 226
        either the user or the default configuration.
        configType must be one of ('main','extensions','highlight','keys')
227
        If a default is returned a warning is printed to stderr.
228 229 230 231 232
        """
        if self.userCfg[configType].has_option(section,option):
            return self.userCfg[configType].Get(section, option, type=type)
        elif self.defaultCfg[configType].has_option(section,option):
            return self.defaultCfg[configType].Get(section, option, type=type)
233
        else: #returning default, print warning
234 235 236 237 238
            warning=('\n Warning: configHandler.py - IdleConf.GetOption -\n'+
                       ' problem retrieving configration option '+`option`+'\n'+
                       ' from section '+`section`+'.\n'+
                       ' returning default value: '+`default`+'\n')
            sys.stderr.write(warning)
239
            return default
240

241 242
    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')
247
        """
248
        if not (configType in ('main','extensions','highlight','keys')):
249
            raise InvalidConfigType, 'Invalid configType specified'
250 251 252 253 254
        if configSet == 'user':
            cfgParser=self.userCfg[configType]
        elif configSet == 'default':
            cfgParser=self.defaultCfg[configType]
        else:
255
            raise InvalidConfigSet, 'Invalid configSet specified'
256
        return cfgParser.sections()
257

258 259 260 261
    def GetHighlight(self, theme, element, fgBg=None):
        """
        return individual highlighting theme elements.
        fgBg - string ('fg'or'bg') or None, if None return a dictionary
262 263
        containing fg and bg colours (appropriate for passing to Tkinter in,
        e.g., a tag_config call), otherwise fg or bg colour only as specified.
264
        """
265 266 267 268 269 270 271
        if self.defaultCfg['highlight'].has_section(theme):
            themeDict=self.GetThemeDict('default',theme)
        else:
            themeDict=self.GetThemeDict('user',theme)
        fore=themeDict[element+'-foreground']
        if element=='cursor': #there is no config value for cursor bg
            back=themeDict['normal-background']
272
        else:
273
            back=themeDict[element+'-background']
274 275 276 277 278 279 280 281
        highlight={"foreground": fore,"background": back}
        if not fgBg: #return dict of both colours
            return highlight
        else: #return specified colour only
            if fgBg == 'fg':
                return highlight["foreground"]
            if fgBg == 'bg':
                return highlight["background"]
282
            else:
283
                raise InvalidFgBg, 'Invalid fgBg specified'
284

285 286 287 288 289 290
    def GetThemeDict(self,type,themeName):
        """
        type - string, 'default' or 'user' theme type
        themeName - string, theme name
        Returns a dictionary which holds {option:value} for each element
        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 293 294 295 296 297 298
        a newly created theme.
        """
        if type == 'user':
            cfgParser=self.userCfg['highlight']
        elif type == 'default':
            cfgParser=self.defaultCfg['highlight']
        else:
299
            raise InvalidTheme, 'Invalid theme type specified'
300 301 302 303
        #foreground and background values are provded for each theme element
        #(apart from cursor) even though all these values are not yet used
        #by idle, to allow for their use in the future. Default values are
        #generally black and white.
304 305 306 307 308 309
        theme={ 'normal-foreground':'#000000',
                'normal-background':'#ffffff',
                'keyword-foreground':'#000000',
                'keyword-background':'#ffffff',
                'comment-foreground':'#000000',
                'comment-background':'#ffffff',
310 311
                'string-foreground':'#000000',
                'string-background':'#ffffff',
312
                'definition-foreground':'#000000',
313 314 315 316 317 318 319 320
                'definition-background':'#ffffff',
                'hilite-foreground':'#000000',
                'hilite-background':'gray',
                'break-foreground':'#ffffff',
                'break-background':'#000000',
                'hit-foreground':'#ffffff',
                'hit-background':'#000000',
                'error-foreground':'#ffffff',
321 322 323
                'error-background':'#000000',
                #cursor (only foreground can be set)
                'cursor-foreground':'#000000',
324 325 326 327 328 329 330 331
                #shell window
                'stdout-foreground':'#000000',
                'stdout-background':'#ffffff',
                'stderr-foreground':'#000000',
                'stderr-background':'#ffffff',
                'console-foreground':'#000000',
                'console-background':'#ffffff' }
        for element in theme.keys():
332 333 334 335 336 337 338
            if not cfgParser.has_option(themeName,element):
                #we are going to return a default, print warning
                warning=('\n Warning: configHandler.py - IdleConf.GetThemeDict'+
                           ' -\n problem retrieving theme element '+`element`+
                           '\n from theme '+`themeName`+'.\n'+
                           ' returning default value: '+`theme[element]`+'\n')
                sys.stderr.write(warning)
339
            colour=cfgParser.Get(themeName,element,default=theme[element])
340 341
            theme[element]=colour
        return theme
342

343 344
    def CurrentTheme(self):
        """
345
        Returns the name of the currently active theme
346
        """
347
        return self.GetOption('main','Theme','name',default='')
348

349 350
    def CurrentKeys(self):
        """
351
        Returns the name of the currently active key set
352
        """
353
        return self.GetOption('main','Keys','name',default='')
354

355 356 357 358 359
    def GetExtensions(self, activeOnly=1):
        """
        Gets a list of all idle extensions declared in the config files.
        activeOnly - boolean, if true only return active (enabled) extensions
        """
360 361 362 363
        extns=self.RemoveKeyBindNames(
                self.GetSectionList('default','extensions'))
        userExtns=self.RemoveKeyBindNames(
                self.GetSectionList('user','extensions'))
364 365
        for extn in userExtns:
            if extn not in extns: #user has added own extension
366
                extns.append(extn)
367 368 369
        if activeOnly:
            activeExtns=[]
            for extn in extns:
370 371
                if self.GetOption('extensions',extn,'enable',default=1,
                    type='bool'):
372 373 374 375
                    #the extension is enabled
                    activeExtns.append(extn)
            return activeExtns
        else:
376
            return extns
377

378 379 380 381 382
    def RemoveKeyBindNames(self,extnNameList):
        #get rid of keybinding section names
        names=extnNameList
        kbNameIndicies=[]
        for name in names:
383 384
            if name.endswith('_bindings') or name.endswith('_cfgBindings'):
                kbNameIndicies.append(names.index(name))
385 386
        kbNameIndicies.sort()
        kbNameIndicies.reverse()
387
        for index in kbNameIndicies: #delete each keybinding section name
388 389
            del(names[index])
        return names
390

391 392 393 394 395 396 397 398 399 400 401 402 403 404
    def GetExtnNameForEvent(self,virtualEvent):
        """
        Returns the name of the extension that virtualEvent is bound in, or
        None if not bound in any extension.
        virtualEvent - string, name of the virtual event to test for, without
                       the enclosing '<< >>'
        """
        extName=None
        vEvent='<<'+virtualEvent+'>>'
        for extn in self.GetExtensions(activeOnly=0):
            for event in self.GetExtensionKeys(extn).keys():
                if event == vEvent:
                    extName=extn
        return extName
405

406 407 408 409
    def GetExtensionKeys(self,extensionName):
        """
        returns a dictionary of the configurable keybindings for a particular
        extension,as they exist in the dictionary returned by GetCurrentKeySet;
410
        that is, where previously used bindings are disabled.
411 412 413 414 415 416 417 418 419 420
        """
        keysName=extensionName+'_cfgBindings'
        activeKeys=self.GetCurrentKeySet()
        extKeys={}
        if self.defaultCfg['extensions'].has_section(keysName):
            eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
            for eventName in eventNames:
                event='<<'+eventName+'>>'
                binding=activeKeys[event]
                extKeys[event]=binding
421 422
        return extKeys

423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
    def __GetRawExtensionKeys(self,extensionName):
        """
        returns a dictionary of the configurable keybindings for a particular
        extension, as defined in the configuration files, or an empty dictionary
        if no bindings are found
        """
        keysName=extensionName+'_cfgBindings'
        extKeys={}
        if self.defaultCfg['extensions'].has_section(keysName):
            eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
            for eventName in eventNames:
                binding=self.GetOption('extensions',keysName,
                        eventName,default='').split()
                event='<<'+eventName+'>>'
                extKeys[event]=binding
438 439
        return extKeys

440 441 442 443
    def GetExtensionBindings(self,extensionName):
        """
        Returns a dictionary of all the event bindings for a particular
        extension. The configurable keybindings are returned as they exist in
444
        the dictionary returned by GetCurrentKeySet; that is, where re-used
445 446 447 448 449 450 451 452 453 454 455 456
        keybindings are disabled.
        """
        bindsName=extensionName+'_bindings'
        extBinds=self.GetExtensionKeys(extensionName)
        #add the non-configurable bindings
        if self.defaultCfg['extensions'].has_section(bindsName):
            eventNames=self.defaultCfg['extensions'].GetOptionList(bindsName)
            for eventName in eventNames:
                binding=self.GetOption('extensions',bindsName,
                        eventName,default='').split()
                event='<<'+eventName+'>>'
                extBinds[event]=binding
457 458 459

        return extBinds

460 461 462 463
    def GetKeyBinding(self, keySetName, eventStr):
        """
        returns the keybinding for a specific event.
        keySetName - string, name of key binding set
464
        eventStr - string, the virtual event we want the binding for,
465 466 467 468 469 470
                   represented as a string, eg. '<<event>>'
        """
        eventName=eventStr[2:-2] #trim off the angle brackets
        binding=self.GetOption('keys',keySetName,eventName,default='').split()
        return binding

471
    def GetCurrentKeySet(self):
472
        return self.GetKeySet(self.CurrentKeys())
473

474
    def GetKeySet(self,keySetName):
475
        """
476
        Returns a dictionary of: all requested core keybindings, plus the
477 478 479
        keybindings for all currently active extensions. If a binding defined
        in an extension is already in use, that binding is disabled.
        """
480
        keySet=self.GetCoreKeys(keySetName)
481 482 483 484 485
        activeExtns=self.GetExtensions(activeOnly=1)
        for extn in activeExtns:
            extKeys=self.__GetRawExtensionKeys(extn)
            if extKeys: #the extension defines keybindings
                for event in extKeys.keys():
486
                    if extKeys[event] in keySet.values():
487 488
                        #the binding is already in use
                        extKeys[event]='' #disable this binding
489 490 491
                    keySet[event]=extKeys[event] #add binding
        return keySet

492 493 494 495 496 497 498
    def IsCoreBinding(self,virtualEvent):
        """
        returns true if the virtual event is bound in the core idle keybindings.
        virtualEvent - string, name of the virtual event to test for, without
                       the enclosing '<< >>'
        """
        return ('<<'+virtualEvent+'>>') in self.GetCoreKeys().keys()
499

500
    def GetCoreKeys(self, keySetName=None):
501
        """
502 503
        returns the requested set of core keybindings, with fallbacks if
        required.
504 505 506 507
        Keybindings loaded from the config file(s) are loaded _over_ these
        defaults, so if there is a problem getting any core binding there will
        be an 'ultimate last resort fallback' to the CUA-ish bindings
        defined here.
508 509
        """
        keyBindings={
510 511 512
            '<<copy>>': ['<Control-c>', '<Control-C>'],
            '<<cut>>': ['<Control-x>', '<Control-X>'],
            '<<paste>>': ['<Control-v>', '<Control-V>'],
513 514 515 516
            '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
            '<<center-insert>>': ['<Control-l>'],
            '<<close-all-windows>>': ['<Control-q>'],
            '<<close-window>>': ['<Alt-F4>'],
517
            '<<do-nothing>>': ['<Control-x>'],
518 519
            '<<end-of-file>>': ['<Control-d>'],
            '<<python-docs>>': ['<F1>'],
520
            '<<python-context-help>>': ['<Shift-F1>'],
521 522 523
            '<<history-next>>': ['<Alt-n>'],
            '<<history-previous>>': ['<Alt-p>'],
            '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
524
            '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
525
            '<<restart-shell>>': ['<Control-F6>'],
526 527 528 529 530
            '<<open-class-browser>>': ['<Alt-c>'],
            '<<open-module>>': ['<Alt-m>'],
            '<<open-new-window>>': ['<Control-n>'],
            '<<open-window-from-file>>': ['<Control-o>'],
            '<<plain-newline-and-indent>>': ['<Control-j>'],
531
            '<<print-window>>': ['<Control-p>'],
532 533 534 535 536 537 538
            '<<redo>>': ['<Control-y>'],
            '<<remove-selection>>': ['<Escape>'],
            '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
            '<<save-window-as-file>>': ['<Alt-s>'],
            '<<save-window>>': ['<Control-s>'],
            '<<select-all>>': ['<Alt-a>'],
            '<<toggle-auto-coloring>>': ['<Control-slash>'],
539 540 541 542 543 544
            '<<undo>>': ['<Control-z>'],
            '<<find-again>>': ['<Control-g>', '<F3>'],
            '<<find-in-files>>': ['<Alt-F3>'],
            '<<find-selection>>': ['<Control-F3>'],
            '<<find>>': ['<Control-f>'],
            '<<replace>>': ['<Control-h>'],
545
            '<<goto-line>>': ['<Alt-g>'],
546 547 548 549 550 551 552 553 554 555 556 557
            '<<smart-backspace>>': ['<Key-BackSpace>'],
            '<<newline-and-indent>>': ['<Key-Return> <Key-KP_Enter>'],
            '<<smart-indent>>': ['<Key-Tab>'],
            '<<indent-region>>': ['<Control-Key-bracketright>'],
            '<<dedent-region>>': ['<Control-Key-bracketleft>'],
            '<<comment-region>>': ['<Alt-Key-3>'],
            '<<uncomment-region>>': ['<Alt-Key-4>'],
            '<<tabify-region>>': ['<Alt-Key-5>'],
            '<<untabify-region>>': ['<Alt-Key-6>'],
            '<<toggle-tabs>>': ['<Alt-Key-t>'],
            '<<change-indentwidth>>': ['<Alt-Key-u>']
            }
558
        if keySetName:
559 560
            for event in keyBindings.keys():
                binding=self.GetKeyBinding(keySetName,event)
561
                if binding:
562
                    keyBindings[event]=binding
563 564 565 566 567 568
                else: #we are going to return a default, print warning
                    warning=('\n Warning: configHandler.py - IdleConf.GetCoreKeys'+
                               ' -\n problem retrieving key binding for event '+
                               `event`+'\n from key set '+`keySetName`+'.\n'+
                               ' returning default value: '+`keyBindings[event]`+'\n')
                    sys.stderr.write(warning)
569
        return keyBindings
570

571
    def GetExtraHelpSourceList(self,configSet):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
572
        """Fetch list of extra help sources from a given configSet.
573

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
574 575 576 577 578 579
        Valid configSets are 'user' or 'default'.  Return a list of tuples of
        the form (menu_item , path_to_help_file , option), or return the empty
        list.  'option' is the sequence number of the help resource.  'option'
        values determine the position of the menu items on the Help menu,
        therefore the returned list must be sorted by 'option'.

580
        """
581 582 583
        helpSources=[]
        if configSet=='user':
            cfgParser=self.userCfg['main']
584
        elif configSet=='default':
585 586
            cfgParser=self.defaultCfg['main']
        else:
587
            raise InvalidConfigSet, 'Invalid configSet specified'
588 589 590 591 592 593 594 595 596 597 598 599
        options=cfgParser.GetOptionList('HelpFiles')
        for option in options:
            value=cfgParser.Get('HelpFiles',option,default=';')
            if value.find(';')==-1: #malformed config entry with no ';'
                menuItem='' #make these empty
                helpPath='' #so value won't be added to list
            else: #config entry contains ';' as expected
                value=string.split(value,';')
                menuItem=value[0].strip()
                helpPath=value[1].strip()
            if menuItem and helpPath: #neither are empty strings
                helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
600
        helpSources.sort(self.__helpsort)
601 602
        return helpSources

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
603 604 605 606 607 608 609 610
    def __helpsort(self, h1, h2):
        if int(h1[2]) < int(h2[2]):
            return -1
        elif int(h1[2]) > int(h2[2]):
            return 1
        else:
            return 0

611 612
    def GetAllExtraHelpSourcesList(self):
        """
613
        Returns a list of tuples containing the details of all additional help
614 615
        sources configured, or an empty list if there are none. Tuples are of
        the format returned by GetExtraHelpSourceList.
616 617
        """
        allHelpSources=( self.GetExtraHelpSourceList('default')+
618
                self.GetExtraHelpSourceList('user') )
619 620
        return allHelpSources

621
    def LoadCfgFiles(self):
622
        """
623 624 625
        load all configuration files.
        """
        for key in self.defaultCfg.keys():
626 627
            self.defaultCfg[key].Load()
            self.userCfg[key].Load() #same keys
628 629 630 631 632 633

    def SaveUserCfgFiles(self):
        """
        write all loaded user configuration files back to disk
        """
        for key in self.userCfg.keys():
634
            self.userCfg[key].Save()
635 636 637 638 639 640 641 642 643 644 645 646 647

idleConf=IdleConf()

### module test
if __name__ == '__main__':
    def dumpCfg(cfg):
        print '\n',cfg,'\n'
        for key in cfg.keys():
            sections=cfg[key].sections()
            print key
            print sections
            for section in sections:
                options=cfg[key].options(section)
648
                print section
649 650 651 652 653 654 655
                print options
                for option in options:
                    print option, '=', cfg[key].Get(section,option)
    dumpCfg(idleConf.defaultCfg)
    dumpCfg(idleConf.userCfg)
    print idleConf.userCfg['main'].Get('Theme','name')
    #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')