Home | History | Annotate | Download | only in idlelib
      1 """Provides access to stored IDLE configuration information.
      2 
      3 Refer to the comments at the beginning of config-main.def for a description of
      4 the available configuration files and the design implemented to update user
      5 configuration information.  In particular, user configuration choices which
      6 duplicate the defaults will be removed from the user's configuration files,
      7 and if a file becomes empty, it will be deleted.
      8 
      9 The contents of the user files may be altered using the Options/Configure IDLE
     10 menu to access the configuration GUI (configDialog.py), or manually.
     11 
     12 Throughout this module there is an emphasis on returning useable defaults
     13 when a problem occurs in returning a requested configuration value back to
     14 idle. This is to allow IDLE to continue to function in spite of errors in
     15 the retrieval of config information. When a default is returned instead of
     16 a requested config value, a message is printed to stderr to aid in
     17 configuration problem notification and resolution.
     18 
     19 """
     20 import os
     21 import sys
     22 import string
     23 from idlelib import macosxSupport
     24 from ConfigParser import ConfigParser, NoOptionError, NoSectionError
     25 
     26 class InvalidConfigType(Exception): pass
     27 class InvalidConfigSet(Exception): pass
     28 class InvalidFgBg(Exception): pass
     29 class InvalidTheme(Exception): pass
     30 
     31 class 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 
     71 class 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 
    152 class 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 
    700 idleConf=IdleConf()
    701 
    702 ### module test
    703 if __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